main.js (12488B) - Raw
1 const domConnectionStatus = document.getElementById("connectionStatus"); 2 const domFirefoxWebSocketBullshitExplainer = document.getElementById("firefoxWebSocketBullshitExplainer"); 3 4 const domMain = document.getElementsByTagName("main")[0]; 5 const domSummary = { 6 stepCount: document.getElementById("summaryStepCount"), 7 status: document.getElementById("summaryStatus"), 8 }; 9 const domButtonRebuild = document.getElementById("buttonRebuild"); 10 const domStepList = document.getElementById("stepList"); 11 let domSteps = []; 12 13 let wasm_promise = fetch("main.wasm"); 14 let wasm_exports = null; 15 16 const text_decoder = new TextDecoder(); 17 const text_encoder = new TextEncoder(); 18 19 domButtonRebuild.addEventListener("click", () => wasm_exports.rebuild()); 20 21 setConnectionStatus("Loading WebAssembly...", false); 22 WebAssembly.instantiateStreaming(wasm_promise, { 23 core: { 24 log: function(ptr, len) { 25 const msg = decodeString(ptr, len); 26 console.log(msg); 27 }, 28 panic: function (ptr, len) { 29 const msg = decodeString(ptr, len); 30 throw new Error("panic: " + msg); 31 }, 32 timestamp: function () { 33 return BigInt(new Date()); 34 }, 35 hello: hello, 36 updateBuildStatus: updateBuildStatus, 37 updateStepStatus: updateStepStatus, 38 sendWsMessage: (ptr, len) => ws.send(new Uint8Array(wasm_exports.memory.buffer, ptr, len)), 39 }, 40 fuzz: { 41 requestSources: fuzzRequestSources, 42 ready: fuzzReady, 43 updateStats: fuzzUpdateStats, 44 updateEntryPoints: fuzzUpdateEntryPoints, 45 updateSource: fuzzUpdateSource, 46 updateCoverage: fuzzUpdateCoverage, 47 }, 48 time_report: { 49 updateCompile: timeReportUpdateCompile, 50 updateGeneric: timeReportUpdateGeneric, 51 }, 52 }).then(function(obj) { 53 setConnectionStatus("Connecting to WebSocket...", true); 54 connectWebSocket(); 55 56 wasm_exports = obj.instance.exports; 57 window.wasm = obj; // for debugging 58 }); 59 60 function connectWebSocket() { 61 const host = document.location.host; 62 const pathname = document.location.pathname; 63 const isHttps = document.location.protocol === 'https:'; 64 const match = host.match(/^(.+):(\d+)$/); 65 const defaultPort = isHttps ? 443 : 80; 66 const port = match ? parseInt(match[2], 10) : defaultPort; 67 const hostName = match ? match[1] : host; 68 const wsProto = isHttps ? "wss:" : "ws:"; 69 const wsUrl = wsProto + '//' + hostName + ':' + port + pathname; 70 ws = new WebSocket(wsUrl); 71 ws.binaryType = "arraybuffer"; 72 ws.addEventListener('message', onWebSocketMessage, false); 73 ws.addEventListener('error', onWebSocketClose, false); 74 ws.addEventListener('close', onWebSocketClose, false); 75 ws.addEventListener('open', onWebSocketOpen, false); 76 } 77 function onWebSocketOpen() { 78 setConnectionStatus("Waiting for data...", false); 79 } 80 function onWebSocketMessage(ev) { 81 const jsArray = new Uint8Array(ev.data); 82 const ptr = wasm_exports.message_begin(jsArray.length); 83 const wasmArray = new Uint8Array(wasm_exports.memory.buffer, ptr, jsArray.length); 84 wasmArray.set(jsArray); 85 wasm_exports.message_end(); 86 } 87 function onWebSocketClose() { 88 setConnectionStatus("WebSocket connection closed. Re-connecting...", true); 89 ws.removeEventListener('message', onWebSocketMessage, false); 90 ws.removeEventListener('error', onWebSocketClose, false); 91 ws.removeEventListener('close', onWebSocketClose, false); 92 ws.removeEventListener('open', onWebSocketOpen, false); 93 ws = null; 94 setTimeout(connectWebSocket, 1000); 95 } 96 97 function setConnectionStatus(msg, is_websocket_connect) { 98 domConnectionStatus.textContent = msg; 99 if (msg.length > 0) { 100 domConnectionStatus.classList.remove("hidden"); 101 domMain.classList.add("hidden"); 102 } else { 103 domConnectionStatus.classList.add("hidden"); 104 domMain.classList.remove("hidden"); 105 } 106 if (is_websocket_connect) { 107 domFirefoxWebSocketBullshitExplainer.classList.remove("hidden"); 108 } else { 109 domFirefoxWebSocketBullshitExplainer.classList.add("hidden"); 110 } 111 } 112 113 function hello( 114 steps_len, 115 build_status, 116 time_report, 117 ) { 118 domSummary.stepCount.textContent = steps_len; 119 updateBuildStatus(build_status); 120 setConnectionStatus("", false); 121 122 { 123 let entries = []; 124 for (let i = 0; i < steps_len; i += 1) { 125 const step_name = unwrapString(wasm_exports.stepName(i)); 126 const code = document.createElement("code"); 127 code.textContent = step_name; 128 const li = document.createElement("li"); 129 li.appendChild(code); 130 entries.push(li); 131 } 132 domStepList.replaceChildren(...entries); 133 for (let i = 0; i < steps_len; i += 1) { 134 updateStepStatus(i); 135 } 136 } 137 138 if (time_report) timeReportReset(steps_len); 139 fuzzReset(); 140 } 141 142 function updateBuildStatus(s) { 143 let text; 144 let active = false; 145 let reset_time_reports = false; 146 if (s == 0) { 147 text = "Idle"; 148 } else if (s == 1) { 149 text = "Watching for changes..."; 150 } else if (s == 2) { 151 text = "Running..."; 152 active = true; 153 reset_time_reports = true; 154 } else if (s == 3) { 155 text = "Starting fuzzer..."; 156 active = true; 157 } else { 158 console.log(`bad build status: ${s}`); 159 } 160 domSummary.status.textContent = text; 161 if (active) { 162 domSummary.status.classList.add("status-running"); 163 domSummary.status.classList.remove("status-idle"); 164 domButtonRebuild.disabled = true; 165 } else { 166 domSummary.status.classList.remove("status-running"); 167 domSummary.status.classList.add("status-idle"); 168 domButtonRebuild.disabled = false; 169 } 170 if (reset_time_reports) { 171 // Grey out and collapse all the time reports 172 for (const time_report_host of domTimeReportList.children) { 173 const details = time_report_host.shadowRoot.querySelector(":host > details"); 174 details.classList.add("pending"); 175 details.open = false; 176 } 177 } 178 } 179 function updateStepStatus(step_idx) { 180 const li = domStepList.children[step_idx]; 181 const step_status = wasm_exports.stepStatus(step_idx); 182 li.classList.remove("step-wip", "step-success", "step-failure"); 183 if (step_status == 0) { 184 // pending 185 } else if (step_status == 1) { 186 li.classList.add("step-wip"); 187 } else if (step_status == 2) { 188 li.classList.add("step-success"); 189 } else if (step_status == 3) { 190 li.classList.add("step-failure"); 191 } else { 192 console.log(`bad step status: ${step_status}`); 193 } 194 } 195 196 function decodeString(ptr, len) { 197 if (len === 0) return ""; 198 return text_decoder.decode(new Uint8Array(wasm_exports.memory.buffer, ptr, len)); 199 } 200 function getU32Array(ptr, len) { 201 if (len === 0) return new Uint32Array(); 202 return new Uint32Array(wasm_exports.memory.buffer, ptr, len); 203 } 204 function unwrapString(bigint) { 205 const ptr = Number(bigint & 0xffffffffn); 206 const len = Number(bigint >> 32n); 207 return decodeString(ptr, len); 208 } 209 210 const time_report_entry_template = document.getElementById("timeReportEntryTemplate").content; 211 const domTimeReport = document.getElementById("timeReport"); 212 const domTimeReportList = document.getElementById("timeReportList"); 213 function timeReportReset(steps_len) { 214 let entries = []; 215 for (let i = 0; i < steps_len; i += 1) { 216 const step_name = unwrapString(wasm_exports.stepName(i)); 217 const host = document.createElement("div"); 218 const shadow = host.attachShadow({ mode: "open" }); 219 shadow.appendChild(time_report_entry_template.cloneNode(true)); 220 shadow.querySelector(":host > details").classList.add("pending"); 221 const slotted_name = document.createElement("code"); 222 slotted_name.setAttribute("slot", "step-name"); 223 slotted_name.textContent = step_name; 224 host.appendChild(slotted_name); 225 entries.push(host); 226 } 227 domTimeReportList.replaceChildren(...entries); 228 domTimeReport.classList.remove("hidden"); 229 } 230 function timeReportUpdateCompile( 231 step_idx, 232 inner_html_ptr, 233 inner_html_len, 234 file_table_html_ptr, 235 file_table_html_len, 236 decl_table_html_ptr, 237 decl_table_html_len, 238 use_llvm, 239 ) { 240 const inner_html = decodeString(inner_html_ptr, inner_html_len); 241 const file_table_html = decodeString(file_table_html_ptr, file_table_html_len); 242 const decl_table_html = decodeString(decl_table_html_ptr, decl_table_html_len); 243 244 const host = domTimeReportList.children.item(step_idx); 245 const shadow = host.shadowRoot; 246 247 shadow.querySelector(":host > details").classList.remove("pending", "no-llvm"); 248 249 shadow.getElementById("genericReport").classList.add("hidden"); 250 shadow.getElementById("compileReport").classList.remove("hidden"); 251 252 if (!use_llvm) shadow.querySelector(":host > details").classList.add("no-llvm"); 253 host.innerHTML = inner_html; 254 shadow.getElementById("fileTableBody").innerHTML = file_table_html; 255 shadow.getElementById("declTableBody").innerHTML = decl_table_html; 256 } 257 function timeReportUpdateGeneric( 258 step_idx, 259 inner_html_ptr, 260 inner_html_len, 261 ) { 262 const inner_html = decodeString(inner_html_ptr, inner_html_len); 263 const host = domTimeReportList.children.item(step_idx); 264 const shadow = host.shadowRoot; 265 shadow.querySelector(":host > details").classList.remove("pending", "no-llvm"); 266 shadow.getElementById("genericReport").classList.remove("hidden"); 267 shadow.getElementById("compileReport").classList.add("hidden"); 268 host.innerHTML = inner_html; 269 } 270 271 const fuzz_entry_template = document.getElementById("fuzzEntryTemplate").content; 272 const domFuzz = document.getElementById("fuzz"); 273 const domFuzzStatus = document.getElementById("fuzzStatus"); 274 const domFuzzEntries = document.getElementById("fuzzEntries"); 275 let domFuzzInstance = null; 276 function fuzzRequestSources() { 277 domFuzzStatus.classList.remove("hidden"); 278 domFuzzStatus.textContent = "Loading sources tarball..."; 279 fetch("sources.tar").then(function(response) { 280 if (!response.ok) throw new Error("unable to download sources"); 281 domFuzzStatus.textContent = "Parsing fuzz test sources..."; 282 return response.arrayBuffer(); 283 }).then(function(buffer) { 284 if (buffer.length === 0) throw new Error("sources.tar was empty"); 285 const js_array = new Uint8Array(buffer); 286 const ptr = wasm_exports.alloc(js_array.length); 287 const wasm_array = new Uint8Array(wasm_exports.memory.buffer, ptr, js_array.length); 288 wasm_array.set(js_array); 289 wasm_exports.fuzzUnpackSources(ptr, js_array.length); 290 domFuzzStatus.textContent = ""; 291 domFuzzStatus.classList.add("hidden"); 292 }); 293 } 294 function fuzzReady() { 295 domFuzz.classList.remove("hidden"); 296 297 // TODO: multiple fuzzer instances 298 if (domFuzzInstance !== null) return; 299 300 const host = document.createElement("div"); 301 const shadow = host.attachShadow({ mode: "open" }); 302 shadow.appendChild(fuzz_entry_template.cloneNode(true)); 303 304 domFuzzInstance = host; 305 domFuzzEntries.appendChild(host); 306 } 307 function fuzzReset() { 308 domFuzz.classList.add("hidden"); 309 domFuzzEntries.replaceChildren(); 310 domFuzzInstance = null; 311 } 312 function fuzzUpdateStats(stats_html_ptr, stats_html_len) { 313 if (domFuzzInstance === null) throw new Error("fuzzUpdateStats called when fuzzer inactive"); 314 const stats_html = decodeString(stats_html_ptr, stats_html_len); 315 const host = domFuzzInstance; 316 host.innerHTML = stats_html; 317 } 318 function fuzzUpdateEntryPoints(entry_points_html_ptr, entry_points_html_len) { 319 if (domFuzzInstance === null) throw new Error("fuzzUpdateEntryPoints called when fuzzer inactive"); 320 const entry_points_html = decodeString(entry_points_html_ptr, entry_points_html_len); 321 const domEntryPointList = domFuzzInstance.shadowRoot.getElementById("entryPointList"); 322 domEntryPointList.innerHTML = entry_points_html; 323 } 324 function fuzzUpdateSource(source_html_ptr, source_html_len) { 325 if (domFuzzInstance === null) throw new Error("fuzzUpdateSource called when fuzzer inactive"); 326 const source_html = decodeString(source_html_ptr, source_html_len); 327 const domSourceText = domFuzzInstance.shadowRoot.getElementById("sourceText"); 328 domSourceText.innerHTML = source_html; 329 domFuzzInstance.shadowRoot.getElementById("source").classList.remove("hidden"); 330 } 331 function fuzzUpdateCoverage(covered_ptr, covered_len) { 332 if (domFuzzInstance === null) throw new Error("fuzzUpdateCoverage called when fuzzer inactive"); 333 const shadow = domFuzzInstance.shadowRoot; 334 const domSourceText = shadow.getElementById("sourceText"); 335 const covered = getU32Array(covered_ptr, covered_len); 336 for (let i = 0; i < domSourceText.children.length; i += 1) { 337 const childDom = domSourceText.children[i]; 338 if (childDom.id != null && childDom.id[0] == "l") { 339 childDom.classList.add("l"); 340 childDom.classList.remove("c"); 341 } 342 } 343 for (const sli of covered) { 344 shadow.getElementById(`l${sli}`).classList.add("c"); 345 } 346 }