diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2024-09-09 20:37:03 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2024-09-11 13:41:29 -0700 |
| commit | 0cdccff51912359b7ec5afa57fbbd5bb69d8f3a2 (patch) | |
| tree | 6221333ab3422901dc9e27e8745b7966bc47e377 /lib/fuzzer/web/main.js | |
| parent | 9bc731b30a0be771a8128bab25d873f9212643a9 (diff) | |
| download | zig-0cdccff51912359b7ec5afa57fbbd5bb69d8f3a2.tar.gz zig-0cdccff51912359b7ec5afa57fbbd5bb69d8f3a2.zip | |
fuzzer: move web files into separate directory
Diffstat (limited to 'lib/fuzzer/web/main.js')
| -rw-r--r-- | lib/fuzzer/web/main.js | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/lib/fuzzer/web/main.js b/lib/fuzzer/web/main.js new file mode 100644 index 0000000000..ce02276f98 --- /dev/null +++ b/lib/fuzzer/web/main.js @@ -0,0 +1,249 @@ +(function() { + const domStatus = document.getElementById("status"); + const domSectSource = document.getElementById("sectSource"); + const domSectStats = document.getElementById("sectStats"); + const domSourceText = document.getElementById("sourceText"); + const domStatTotalRuns = document.getElementById("statTotalRuns"); + const domStatUniqueRuns = document.getElementById("statUniqueRuns"); + const domStatCoverage = document.getElementById("statCoverage"); + const domStatLowestStack = document.getElementById("statLowestStack"); + const domEntryPointsList = document.getElementById("entryPointsList"); + + let wasm_promise = fetch("main.wasm"); + let sources_promise = fetch("sources.tar").then(function(response) { + if (!response.ok) throw new Error("unable to download sources"); + return response.arrayBuffer(); + }); + var wasm_exports = null; + var curNavSearch = null; + var curNavLocation = null; + + const text_decoder = new TextDecoder(); + const text_encoder = new TextEncoder(); + + domStatus.textContent = "Loading WebAssembly..."; + WebAssembly.instantiateStreaming(wasm_promise, { + js: { + log: function(ptr, len) { + const msg = decodeString(ptr, len); + console.log(msg); + }, + panic: function (ptr, len) { + const msg = decodeString(ptr, len); + throw new Error("panic: " + msg); + }, + emitSourceIndexChange: onSourceIndexChange, + emitCoverageUpdate: onCoverageUpdate, + emitEntryPointsUpdate: renderStats, + }, + }).then(function(obj) { + wasm_exports = obj.instance.exports; + window.wasm = obj; // for debugging + domStatus.textContent = "Loading sources tarball..."; + + sources_promise.then(function(buffer) { + domStatus.textContent = "Parsing sources..."; + const js_array = new Uint8Array(buffer); + const ptr = wasm_exports.alloc(js_array.length); + const wasm_array = new Uint8Array(wasm_exports.memory.buffer, ptr, js_array.length); + wasm_array.set(js_array); + wasm_exports.unpack(ptr, js_array.length); + + window.addEventListener('popstate', onPopState, false); + onHashChange(null); + + domStatus.textContent = "Waiting for server to send source location metadata..."; + connectWebSocket(); + }); + }); + + function onPopState(ev) { + onHashChange(ev.state); + } + + function onHashChange(state) { + history.replaceState({}, ""); + navigate(location.hash); + if (state == null) window.scrollTo({top: 0}); + } + + function navigate(location_hash) { + domSectSource.classList.add("hidden"); + + curNavLocation = null; + curNavSearch = null; + + if (location_hash.length > 1 && location_hash[0] === '#') { + const query = location_hash.substring(1); + const qpos = query.indexOf("?"); + let nonSearchPart; + if (qpos === -1) { + nonSearchPart = query; + } else { + nonSearchPart = query.substring(0, qpos); + curNavSearch = decodeURIComponent(query.substring(qpos + 1)); + } + + if (nonSearchPart[0] == "l") { + curNavLocation = +nonSearchPart.substring(1); + renderSource(curNavLocation); + } + } + + render(); + } + + function connectWebSocket() { + const host = document.location.host; + const pathname = document.location.pathname; + const isHttps = document.location.protocol === 'https:'; + const match = host.match(/^(.+):(\d+)$/); + const defaultPort = isHttps ? 443 : 80; + const port = match ? parseInt(match[2], 10) : defaultPort; + const hostName = match ? match[1] : host; + const wsProto = isHttps ? "wss:" : "ws:"; + const wsUrl = wsProto + '//' + hostName + ':' + port + pathname; + ws = new WebSocket(wsUrl); + ws.binaryType = "arraybuffer"; + ws.addEventListener('message', onWebSocketMessage, false); + ws.addEventListener('error', timeoutThenCreateNew, false); + ws.addEventListener('close', timeoutThenCreateNew, false); + ws.addEventListener('open', onWebSocketOpen, false); + } + + function onWebSocketOpen() { + //console.log("web socket opened"); + } + + function onWebSocketMessage(ev) { + wasmOnMessage(ev.data); + } + + function timeoutThenCreateNew() { + ws.removeEventListener('message', onWebSocketMessage, false); + ws.removeEventListener('error', timeoutThenCreateNew, false); + ws.removeEventListener('close', timeoutThenCreateNew, false); + ws.removeEventListener('open', onWebSocketOpen, false); + ws = null; + setTimeout(connectWebSocket, 1000); + } + + function wasmOnMessage(data) { + const jsArray = new Uint8Array(data); + const ptr = wasm_exports.message_begin(jsArray.length); + const wasmArray = new Uint8Array(wasm_exports.memory.buffer, ptr, jsArray.length); + wasmArray.set(jsArray); + wasm_exports.message_end(); + } + + function onSourceIndexChange() { + render(); + if (curNavLocation != null) renderSource(curNavLocation); + } + + function onCoverageUpdate() { + renderStats(); + renderCoverage(); + } + + function render() { + domStatus.classList.add("hidden"); + } + + function renderStats() { + const totalRuns = wasm_exports.totalRuns(); + const uniqueRuns = wasm_exports.uniqueRuns(); + const totalSourceLocations = wasm_exports.totalSourceLocations(); + const coveredSourceLocations = wasm_exports.coveredSourceLocations(); + domStatTotalRuns.innerText = totalRuns; + domStatUniqueRuns.innerText = uniqueRuns + " (" + percent(uniqueRuns, totalRuns) + "%)"; + domStatCoverage.innerText = coveredSourceLocations + " / " + totalSourceLocations + " (" + percent(coveredSourceLocations, totalSourceLocations) + "%)"; + domStatLowestStack.innerText = unwrapString(wasm_exports.lowestStack()); + + const entryPoints = unwrapInt32Array(wasm_exports.entryPoints()); + resizeDomList(domEntryPointsList, entryPoints.length, "<li></li>"); + for (let i = 0; i < entryPoints.length; i += 1) { + const liDom = domEntryPointsList.children[i]; + liDom.innerHTML = unwrapString(wasm_exports.sourceLocationLinkHtml(entryPoints[i])); + } + + + domSectStats.classList.remove("hidden"); + } + + function renderCoverage() { + if (curNavLocation == null) return; + const sourceLocationIndex = curNavLocation; + + for (let i = 0; i < domSourceText.children.length; i += 1) { + const childDom = domSourceText.children[i]; + if (childDom.id != null && childDom.id[0] == "l") { + childDom.classList.add("l"); + childDom.classList.remove("c"); + } + } + const coveredList = unwrapInt32Array(wasm_exports.sourceLocationFileCoveredList(sourceLocationIndex)); + for (let i = 0; i < coveredList.length; i += 1) { + document.getElementById("l" + coveredList[i]).classList.add("c"); + } + } + + function resizeDomList(listDom, desiredLen, templateHtml) { + for (let i = listDom.childElementCount; i < desiredLen; i += 1) { + listDom.insertAdjacentHTML('beforeend', templateHtml); + } + while (desiredLen < listDom.childElementCount) { + listDom.removeChild(listDom.lastChild); + } + } + + function percent(a, b) { + return ((Number(a) / Number(b)) * 100).toFixed(1); + } + + function renderSource(sourceLocationIndex) { + const pathName = unwrapString(wasm_exports.sourceLocationPath(sourceLocationIndex)); + if (pathName.length === 0) return; + + const h2 = domSectSource.children[0]; + h2.innerText = pathName; + domSourceText.innerHTML = unwrapString(wasm_exports.sourceLocationFileHtml(sourceLocationIndex)); + + domSectSource.classList.remove("hidden"); + + // Empirically, Firefox needs this requestAnimationFrame in order for the scrollIntoView to work. + requestAnimationFrame(function() { + const slDom = document.getElementById("l" + sourceLocationIndex); + if (slDom != null) slDom.scrollIntoView({ + behavior: "smooth", + block: "center", + }); + }); + } + + function decodeString(ptr, len) { + if (len === 0) return ""; + return text_decoder.decode(new Uint8Array(wasm_exports.memory.buffer, ptr, len)); + } + + function unwrapInt32Array(bigint) { + const ptr = Number(bigint & 0xffffffffn); + const len = Number(bigint >> 32n); + if (len === 0) return new Uint32Array(); + return new Uint32Array(wasm_exports.memory.buffer, ptr, len); + } + + function setInputString(s) { + const jsArray = text_encoder.encode(s); + const len = jsArray.length; + const ptr = wasm_exports.set_input_string(len); + const wasmArray = new Uint8Array(wasm_exports.memory.buffer, ptr, len); + wasmArray.set(jsArray); + } + + function unwrapString(bigint) { + const ptr = Number(bigint & 0xffffffffn); + const len = Number(bigint >> 32n); + return decodeString(ptr, len); + } +})(); |
