Commit 3d48602c99
Changed files (4)
lib
docs
wasm
fuzzer
lib/docs/wasm/html_render.zig
@@ -16,6 +16,16 @@ pub const RenderSourceOptions = struct {
skip_comments: bool = false,
collapse_whitespace: bool = false,
fn_link: Decl.Index = .none,
+ /// Assumed to be sorted ascending.
+ source_location_annotations: []const Annotation = &.{},
+ /// Concatenated with dom_id.
+ annotation_prefix: []const u8 = "l",
+};
+
+pub const Annotation = struct {
+ file_byte_offset: u32,
+ /// Concatenated with annotation_prefix.
+ dom_id: u32,
};
pub fn fileSourceHtml(
@@ -51,6 +61,8 @@ pub fn fileSourceHtml(
}
}
+ var next_annotate_index: usize = 0;
+
for (
token_tags[start_token..end_token],
token_starts[start_token..end_token],
@@ -74,6 +86,18 @@ pub fn fileSourceHtml(
if (tag == .eof) break;
const slice = ast.tokenSlice(token_index);
cursor = start + slice.len;
+
+ // Insert annotations.
+ while (true) {
+ if (next_annotate_index >= options.source_location_annotations.len) break;
+ const next_annotation = options.source_location_annotations[next_annotate_index];
+ if (cursor < next_annotation.file_byte_offset) break;
+ try out.writer(gpa).print("<span id=\"{s}{d}\"></span>", .{
+ options.annotation_prefix, next_annotation.dom_id,
+ });
+ next_annotate_index += 1;
+ }
+
switch (tag) {
.eof => unreachable,
lib/fuzzer/wasm/main.zig
@@ -280,12 +280,71 @@ const SourceLocationIndex = enum(u32) {
) error{ OutOfMemory, SourceUnavailable }!void {
const walk_file_index = sli.toWalkFile() orelse return error.SourceUnavailable;
const root_node = walk_file_index.findRootDecl().get().ast_node;
- html_render.fileSourceHtml(walk_file_index, out, root_node, .{}) catch |err| {
+ var annotations: std.ArrayListUnmanaged(html_render.Annotation) = .{};
+ defer annotations.deinit(gpa);
+ try computeSourceAnnotations(sli.ptr().file, walk_file_index, &annotations, coverage_source_locations.items);
+ html_render.fileSourceHtml(walk_file_index, out, root_node, .{
+ .source_location_annotations = annotations.items,
+ }) catch |err| {
fatal("unable to render source: {s}", .{@errorName(err)});
};
}
};
+fn computeSourceAnnotations(
+ cov_file_index: Coverage.File.Index,
+ walk_file_index: Walk.File.Index,
+ annotations: *std.ArrayListUnmanaged(html_render.Annotation),
+ source_locations: []const Coverage.SourceLocation,
+) !void {
+ // Collect all the source locations from only this file into this array
+ // first, then sort by line, col, so that we can collect annotations with
+ // O(N) time complexity.
+ var locs: std.ArrayListUnmanaged(SourceLocationIndex) = .{};
+ defer locs.deinit(gpa);
+
+ for (source_locations, 0..) |sl, sli_usize| {
+ if (sl.file != cov_file_index) continue;
+ const sli: SourceLocationIndex = @enumFromInt(sli_usize);
+ try locs.append(gpa, sli);
+ }
+
+ std.mem.sortUnstable(SourceLocationIndex, locs.items, {}, struct {
+ pub fn lessThan(context: void, lhs: SourceLocationIndex, rhs: SourceLocationIndex) bool {
+ _ = context;
+ const lhs_ptr = lhs.ptr();
+ const rhs_ptr = rhs.ptr();
+ if (lhs_ptr.line < rhs_ptr.line) return true;
+ if (lhs_ptr.line > rhs_ptr.line) return false;
+ return lhs_ptr.column < rhs_ptr.column;
+ }
+ }.lessThan);
+
+ const source = walk_file_index.get_ast().source;
+ var line: usize = 1;
+ var column: usize = 1;
+ var next_loc_index: usize = 0;
+ for (source, 0..) |byte, offset| {
+ if (byte == '\n') {
+ line += 1;
+ column = 1;
+ } else {
+ column += 1;
+ }
+ while (true) {
+ if (next_loc_index >= locs.items.len) return;
+ const next_sli = locs.items[next_loc_index];
+ const next_sl = next_sli.ptr();
+ if (next_sl.line > line or (next_sl.line == line and next_sl.column > column)) break;
+ try annotations.append(gpa, .{
+ .file_byte_offset = offset,
+ .dom_id = @intFromEnum(next_sli),
+ });
+ next_loc_index += 1;
+ }
+ }
+}
+
var coverage = Coverage.init;
/// Index of type `SourceLocationIndex`.
var coverage_source_locations: std.ArrayListUnmanaged(Coverage.SourceLocation) = .{};
lib/fuzzer/index.html
@@ -52,6 +52,14 @@
cursor: default;
}
+ .l {
+ display: inline-block;
+ background: white;
+ width: 1em;
+ height: 1em;
+ border-radius: 1em;
+ }
+
.tok-kw {
color: #333;
font-weight: bold;
lib/fuzzer/main.js
@@ -33,7 +33,7 @@
throw new Error("panic: " + msg);
},
emitSourceIndexChange: onSourceIndexChange,
- emitCoverageUpdate: renderStats,
+ emitCoverageUpdate: onCoverageUpdate,
emitEntryPointsUpdate: renderStats,
},
}).then(function(obj) {
@@ -112,7 +112,7 @@
}
function onWebSocketOpen() {
- console.log("web socket opened");
+ //console.log("web socket opened");
}
function onWebSocketMessage(ev) {
@@ -141,6 +141,11 @@
if (curNavLocation != null) renderSource(curNavLocation);
}
+ function onCoverageUpdate() {
+ renderStats();
+ renderCoverage();
+ }
+
function render() {
domStatus.classList.add("hidden");
}
@@ -166,6 +171,15 @@
domSectStats.classList.remove("hidden");
}
+ function renderCoverage() {
+ 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");
+ }
+ }
+ }
+
function resizeDomList(listDom, desiredLen, templateHtml) {
for (let i = listDom.childElementCount; i < desiredLen; i += 1) {
listDom.insertAdjacentHTML('beforeend', templateHtml);
@@ -190,12 +204,10 @@
domSectSource.classList.remove("hidden");
const slDom = document.getElementById("l" + sourceLocationIndex);
- if (slDom != null) {
- slDom.scrollIntoView({
- behavior: "smooth",
- block: "center",
- });
- }
+ slDom.scrollIntoView({
+ behavior: "smooth",
+ block: "center",
+ });
}
function decodeString(ptr, len) {