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