| author | |
| committer | |
| log | 3d48602c995b90e40c6e70ace1e0cdcdeea2eac4 |
| tree | a6904c4c91273ee2750dc9d51c5cc2cc1757faff |
| parent | ef4c2193fc4b048da2afd3861bedb50451a31358 |
4 files changed, 112 insertions(+), 9 deletions(-)
lib/docs/wasm/html_render.zig+24| ... | ... | @@ -16,6 +16,16 @@ pub const RenderSourceOptions = struct { |
| 16 | 16 | skip_comments: bool = false, |
| 17 | 17 | collapse_whitespace: bool = false, |
| 18 | 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 | ||
| 25 | pub const Annotation = struct { | |
| 26 | file_byte_offset: u32, | |
| 27 | /// Concatenated with annotation_prefix. | |
| 28 | dom_id: u32, | |
| 19 | 29 | }; |
| 20 | 30 | |
| 21 | 31 | pub fn fileSourceHtml( |
| ... | ... | @@ -51,6 +61,8 @@ pub fn fileSourceHtml( |
| 51 | 61 | } |
| 52 | 62 | } |
| 53 | 63 | |
| 64 | var next_annotate_index: usize = 0; | |
| 65 | ||
| 54 | 66 | for ( |
| 55 | 67 | token_tags[start_token..end_token], |
| 56 | 68 | token_starts[start_token..end_token], |
| ... | ... | @@ -74,6 +86,18 @@ pub fn fileSourceHtml( |
| 74 | 86 | if (tag == .eof) break; |
| 75 | 87 | const slice = ast.tokenSlice(token_index); |
| 76 | 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 | 101 | switch (tag) { |
| 78 | 102 | .eof => unreachable, |
| 79 | 103 |
lib/fuzzer/index.html+8| ... | ... | @@ -52,6 +52,14 @@ |
| 52 | 52 | cursor: default; |
| 53 | 53 | } |
| 54 | 54 | |
| 55 | .l { | |
| 56 | display: inline-block; | |
| 57 | background: white; | |
| 58 | width: 1em; | |
| 59 | height: 1em; | |
| 60 | border-radius: 1em; | |
| 61 | } | |
| 62 | ||
| 55 | 63 | .tok-kw { |
| 56 | 64 | color: #333; |
| 57 | 65 | font-weight: bold; |
lib/fuzzer/main.js+20-8| ... | ... | @@ -33,7 +33,7 @@ |
| 33 | 33 | throw new Error("panic: " + msg); |
| 34 | 34 | }, |
| 35 | 35 | emitSourceIndexChange: onSourceIndexChange, |
| 36 | emitCoverageUpdate: renderStats, | |
| 36 | emitCoverageUpdate: onCoverageUpdate, | |
| 37 | 37 | emitEntryPointsUpdate: renderStats, |
| 38 | 38 | }, |
| 39 | 39 | }).then(function(obj) { |
| ... | ... | @@ -112,7 +112,7 @@ |
| 112 | 112 | } |
| 113 | 113 | |
| 114 | 114 | function onWebSocketOpen() { |
| 115 | console.log("web socket opened"); | |
| 115 | //console.log("web socket opened"); | |
| 116 | 116 | } |
| 117 | 117 | |
| 118 | 118 | function onWebSocketMessage(ev) { |
| ... | ... | @@ -141,6 +141,11 @@ |
| 141 | 141 | if (curNavLocation != null) renderSource(curNavLocation); |
| 142 | 142 | } |
| 143 | 143 | |
| 144 | function onCoverageUpdate() { | |
| 145 | renderStats(); | |
| 146 | renderCoverage(); | |
| 147 | } | |
| 148 | ||
| 144 | 149 | function render() { |
| 145 | 150 | domStatus.classList.add("hidden"); |
| 146 | 151 | } |
| ... | ... | @@ -166,6 +171,15 @@ |
| 166 | 171 | domSectStats.classList.remove("hidden"); |
| 167 | 172 | } |
| 168 | 173 | |
| 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 | 183 | function resizeDomList(listDom, desiredLen, templateHtml) { |
| 170 | 184 | for (let i = listDom.childElementCount; i < desiredLen; i += 1) { |
| 171 | 185 | listDom.insertAdjacentHTML('beforeend', templateHtml); |
| ... | ... | @@ -190,12 +204,10 @@ |
| 190 | 204 | domSectSource.classList.remove("hidden"); |
| 191 | 205 | |
| 192 | 206 | const slDom = document.getElementById("l" + sourceLocationIndex); |
| 193 | if (slDom != null) { | |
| 194 | slDom.scrollIntoView({ | |
| 195 | behavior: "smooth", | |
| 196 | block: "center", | |
| 197 | }); | |
| 198 | } | |
| 207 | slDom.scrollIntoView({ | |
| 208 | behavior: "smooth", | |
| 209 | block: "center", | |
| 210 | }); | |
| 199 | 211 | } |
| 200 | 212 | |
| 201 | 213 | function decodeString(ptr, len) { |
lib/fuzzer/wasm/main.zig+60-1| ... | ... | @@ -280,12 +280,71 @@ const SourceLocationIndex = enum(u32) { |
| 280 | 280 | ) error{ OutOfMemory, SourceUnavailable }!void { |
| 281 | 281 | const walk_file_index = sli.toWalkFile() orelse return error.SourceUnavailable; |
| 282 | 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 | 289 | fatal("unable to render source: {s}", .{@errorName(err)}); |
| 285 | 290 | }; |
| 286 | 291 | } |
| 287 | 292 | }; |
| 288 | 293 | |
| 294 | fn 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 | ||
| 289 | 348 | var coverage = Coverage.init; |
| 290 | 349 | /// Index of type `SourceLocationIndex`. |
| 291 | 350 | var coverage_source_locations: std.ArrayListUnmanaged(Coverage.SourceLocation) = .{}; |