authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-05 15:32:19-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-07 00:48:32-07:00
log3d48602c995b90e40c6e70ace1e0cdcdeea2eac4
treea6904c4c91273ee2750dc9d51c5cc2cc1757faff
parentef4c2193fc4b048da2afd3861bedb50451a31358

fuzzer web UI: annotated PCs in source view


4 files changed, 112 insertions(+), 9 deletions(-)

lib/docs/wasm/html_render.zig+24
...@@ -16,6 +16,16 @@ pub const RenderSourceOptions = struct {...@@ -16,6 +16,16 @@ pub const RenderSourceOptions = struct {
16 skip_comments: bool = false,16 skip_comments: bool = false,
17 collapse_whitespace: bool = false,17 collapse_whitespace: bool = false,
18 fn_link: Decl.Index = .none,18 fn_link: Decl.Index = .none,
19 /// Assumed to be sorted ascending.
20 source_location_annotations: []const Annotation = &.{},
21 /// Concatenated with dom_id.
22 annotation_prefix: []const u8 = "l",
23};
24
25pub const Annotation = struct {
26 file_byte_offset: u32,
27 /// Concatenated with annotation_prefix.
28 dom_id: u32,
19};29};
2030
21pub fn fileSourceHtml(31pub fn fileSourceHtml(
...@@ -51,6 +61,8 @@ pub fn fileSourceHtml(...@@ -51,6 +61,8 @@ pub fn fileSourceHtml(
51 }61 }
52 }62 }
5363
64 var next_annotate_index: usize = 0;
65
54 for (66 for (
55 token_tags[start_token..end_token],67 token_tags[start_token..end_token],
56 token_starts[start_token..end_token],68 token_starts[start_token..end_token],
...@@ -74,6 +86,18 @@ pub fn fileSourceHtml(...@@ -74,6 +86,18 @@ pub fn fileSourceHtml(
74 if (tag == .eof) break;86 if (tag == .eof) break;
75 const slice = ast.tokenSlice(token_index);87 const slice = ast.tokenSlice(token_index);
76 cursor = start + slice.len;88 cursor = start + slice.len;
89
90 // Insert annotations.
91 while (true) {
92 if (next_annotate_index >= options.source_location_annotations.len) break;
93 const next_annotation = options.source_location_annotations[next_annotate_index];
94 if (cursor < next_annotation.file_byte_offset) break;
95 try out.writer(gpa).print("<span id=\"{s}{d}\"></span>", .{
96 options.annotation_prefix, next_annotation.dom_id,
97 });
98 next_annotate_index += 1;
99 }
100
77 switch (tag) {101 switch (tag) {
78 .eof => unreachable,102 .eof => unreachable,
79103
lib/fuzzer/index.html+8
...@@ -52,6 +52,14 @@...@@ -52,6 +52,14 @@
52 cursor: default;52 cursor: default;
53 }53 }
5454
55 .l {
56 display: inline-block;
57 background: white;
58 width: 1em;
59 height: 1em;
60 border-radius: 1em;
61 }
62
55 .tok-kw {63 .tok-kw {
56 color: #333;64 color: #333;
57 font-weight: bold;65 font-weight: bold;
lib/fuzzer/main.js+20-8
...@@ -33,7 +33,7 @@...@@ -33,7 +33,7 @@
33 throw new Error("panic: " + msg);33 throw new Error("panic: " + msg);
34 },34 },
35 emitSourceIndexChange: onSourceIndexChange,35 emitSourceIndexChange: onSourceIndexChange,
36 emitCoverageUpdate: renderStats,36 emitCoverageUpdate: onCoverageUpdate,
37 emitEntryPointsUpdate: renderStats,37 emitEntryPointsUpdate: renderStats,
38 },38 },
39 }).then(function(obj) {39 }).then(function(obj) {
...@@ -112,7 +112,7 @@...@@ -112,7 +112,7 @@
112 }112 }
113113
114 function onWebSocketOpen() {114 function onWebSocketOpen() {
115 console.log("web socket opened");115 //console.log("web socket opened");
116 }116 }
117117
118 function onWebSocketMessage(ev) {118 function onWebSocketMessage(ev) {
...@@ -141,6 +141,11 @@...@@ -141,6 +141,11 @@
141 if (curNavLocation != null) renderSource(curNavLocation);141 if (curNavLocation != null) renderSource(curNavLocation);
142 }142 }
143143
144 function onCoverageUpdate() {
145 renderStats();
146 renderCoverage();
147 }
148
144 function render() {149 function render() {
145 domStatus.classList.add("hidden");150 domStatus.classList.add("hidden");
146 }151 }
...@@ -166,6 +171,15 @@...@@ -166,6 +171,15 @@
166 domSectStats.classList.remove("hidden");171 domSectStats.classList.remove("hidden");
167 }172 }
168173
174 function renderCoverage() {
175 for (let i = 0; i < domSourceText.children.length; i += 1) {
176 const childDom = domSourceText.children[i];
177 if (childDom.id != null && childDom.id[0] == "l") {
178 childDom.classList.add("l");
179 }
180 }
181 }
182
169 function resizeDomList(listDom, desiredLen, templateHtml) {183 function resizeDomList(listDom, desiredLen, templateHtml) {
170 for (let i = listDom.childElementCount; i < desiredLen; i += 1) {184 for (let i = listDom.childElementCount; i < desiredLen; i += 1) {
171 listDom.insertAdjacentHTML('beforeend', templateHtml);185 listDom.insertAdjacentHTML('beforeend', templateHtml);
...@@ -190,12 +204,10 @@...@@ -190,12 +204,10 @@
190 domSectSource.classList.remove("hidden");204 domSectSource.classList.remove("hidden");
191205
192 const slDom = document.getElementById("l" + sourceLocationIndex);206 const slDom = document.getElementById("l" + sourceLocationIndex);
193 if (slDom != null) {207 slDom.scrollIntoView({
194 slDom.scrollIntoView({208 behavior: "smooth",
195 behavior: "smooth",209 block: "center",
196 block: "center",210 });
197 });
198 }
199 }211 }
200212
201 function decodeString(ptr, len) {213 function decodeString(ptr, len) {
lib/fuzzer/wasm/main.zig+60-1
...@@ -280,12 +280,71 @@ const SourceLocationIndex = enum(u32) {...@@ -280,12 +280,71 @@ const SourceLocationIndex = enum(u32) {
280 ) error{ OutOfMemory, SourceUnavailable }!void {280 ) error{ OutOfMemory, SourceUnavailable }!void {
281 const walk_file_index = sli.toWalkFile() orelse return error.SourceUnavailable;281 const walk_file_index = sli.toWalkFile() orelse return error.SourceUnavailable;
282 const root_node = walk_file_index.findRootDecl().get().ast_node;282 const root_node = walk_file_index.findRootDecl().get().ast_node;
283 html_render.fileSourceHtml(walk_file_index, out, root_node, .{}) catch |err| {283 var annotations: std.ArrayListUnmanaged(html_render.Annotation) = .{};
284 defer annotations.deinit(gpa);
285 try computeSourceAnnotations(sli.ptr().file, walk_file_index, &annotations, coverage_source_locations.items);
286 html_render.fileSourceHtml(walk_file_index, out, root_node, .{
287 .source_location_annotations = annotations.items,
288 }) catch |err| {
284 fatal("unable to render source: {s}", .{@errorName(err)});289 fatal("unable to render source: {s}", .{@errorName(err)});
285 };290 };
286 }291 }
287};292};
288293
294fn computeSourceAnnotations(
295 cov_file_index: Coverage.File.Index,
296 walk_file_index: Walk.File.Index,
297 annotations: *std.ArrayListUnmanaged(html_render.Annotation),
298 source_locations: []const Coverage.SourceLocation,
299) !void {
300 // Collect all the source locations from only this file into this array
301 // first, then sort by line, col, so that we can collect annotations with
302 // O(N) time complexity.
303 var locs: std.ArrayListUnmanaged(SourceLocationIndex) = .{};
304 defer locs.deinit(gpa);
305
306 for (source_locations, 0..) |sl, sli_usize| {
307 if (sl.file != cov_file_index) continue;
308 const sli: SourceLocationIndex = @enumFromInt(sli_usize);
309 try locs.append(gpa, sli);
310 }
311
312 std.mem.sortUnstable(SourceLocationIndex, locs.items, {}, struct {
313 pub fn lessThan(context: void, lhs: SourceLocationIndex, rhs: SourceLocationIndex) bool {
314 _ = context;
315 const lhs_ptr = lhs.ptr();
316 const rhs_ptr = rhs.ptr();
317 if (lhs_ptr.line < rhs_ptr.line) return true;
318 if (lhs_ptr.line > rhs_ptr.line) return false;
319 return lhs_ptr.column < rhs_ptr.column;
320 }
321 }.lessThan);
322
323 const source = walk_file_index.get_ast().source;
324 var line: usize = 1;
325 var column: usize = 1;
326 var next_loc_index: usize = 0;
327 for (source, 0..) |byte, offset| {
328 if (byte == '\n') {
329 line += 1;
330 column = 1;
331 } else {
332 column += 1;
333 }
334 while (true) {
335 if (next_loc_index >= locs.items.len) return;
336 const next_sli = locs.items[next_loc_index];
337 const next_sl = next_sli.ptr();
338 if (next_sl.line > line or (next_sl.line == line and next_sl.column > column)) break;
339 try annotations.append(gpa, .{
340 .file_byte_offset = offset,
341 .dom_id = @intFromEnum(next_sli),
342 });
343 next_loc_index += 1;
344 }
345 }
346}
347
289var coverage = Coverage.init;348var coverage = Coverage.init;
290/// Index of type `SourceLocationIndex`.349/// Index of type `SourceLocationIndex`.
291var coverage_source_locations: std.ArrayListUnmanaged(Coverage.SourceLocation) = .{};350var coverage_source_locations: std.ArrayListUnmanaged(Coverage.SourceLocation) = .{};