authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-05 14:10:43-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-07 00:48:32-07:00
logef4c2193fc4b048da2afd3861bedb50451a31358
tree2ff2e98e4e5a4621387c14825543d547731524a7
parentdb69641061ae300baabd6107aa22da60e78f0f38

fuzzer web UI: navigate by source location index

This will help scroll the point of interest into view

2 files changed, 89 insertions(+), 50 deletions(-)

lib/fuzzer/main.js+21-31
...@@ -68,6 +68,8 @@...@@ -68,6 +68,8 @@
68 }68 }
6969
70 function navigate(location_hash) {70 function navigate(location_hash) {
71 domSectSource.classList.add("hidden");
72
71 curNavLocation = null;73 curNavLocation = null;
72 curNavSearch = null;74 curNavSearch = null;
7375
...@@ -82,23 +84,19 @@...@@ -82,23 +84,19 @@
82 curNavSearch = decodeURIComponent(query.substring(qpos + 1));84 curNavSearch = decodeURIComponent(query.substring(qpos + 1));
83 }85 }
8486
85 if (nonSearchPart.length > 0) {87 if (nonSearchPart[0] == "l") {
86 curNavLocation = nonSearchPart;88 curNavLocation = +nonSearchPart.substring(1);
89 renderSource(curNavLocation);
87 }90 }
88 }91 }
8992
90 render();93 render();
91
92 if (curNavLocation != null) {
93 // TODO
94 // scrollToSourceLocation(findSourceLocationIndex(curNavLocation));
95 }
96 }94 }
9795
98 function connectWebSocket() {96 function connectWebSocket() {
99 const host = window.document.location.host;97 const host = document.location.host;
100 const pathname = window.document.location.pathname;98 const pathname = document.location.pathname;
101 const isHttps = window.document.location.protocol === 'https:';99 const isHttps = document.location.protocol === 'https:';
102 const match = host.match(/^(.+):(\d+)$/);100 const match = host.match(/^(.+):(\d+)$/);
103 const defaultPort = isHttps ? 443 : 80;101 const defaultPort = isHttps ? 443 : 80;
104 const port = match ? parseInt(match[2], 10) : defaultPort;102 const port = match ? parseInt(match[2], 10) : defaultPort;
...@@ -139,17 +137,12 @@...@@ -139,17 +137,12 @@
139 }137 }
140138
141 function onSourceIndexChange() {139 function onSourceIndexChange() {
142 console.log("source location index metadata updated");
143 render();140 render();
141 if (curNavLocation != null) renderSource(curNavLocation);
144 }142 }
145143
146 function render() {144 function render() {
147 domStatus.classList.add("hidden");145 domStatus.classList.add("hidden");
148 domSectSource.classList.add("hidden");
149
150 if (curNavLocation != null) {
151 renderSource(curNavLocation.split(":")[0]);
152 }
153 }146 }
154147
155 function renderStats() {148 function renderStats() {
...@@ -186,22 +179,23 @@...@@ -186,22 +179,23 @@
186 return ((Number(a) / Number(b)) * 100).toFixed(1);179 return ((Number(a) / Number(b)) * 100).toFixed(1);
187 }180 }
188181
189 function renderSource(path) {182 function renderSource(sourceLocationIndex) {
190 const decl_index = findFileRoot(path);183 const pathName = unwrapString(wasm_exports.sourceLocationPath(sourceLocationIndex));
191 if (decl_index == null) throw new Error("file not found: " + path);184 if (pathName.length === 0) return;
192185
193 const h2 = domSectSource.children[0];186 const h2 = domSectSource.children[0];
194 h2.innerText = path;187 h2.innerText = pathName;
195 domSourceText.innerHTML = declSourceHtml(decl_index);188 domSourceText.innerHTML = unwrapString(wasm_exports.sourceLocationFileHtml(sourceLocationIndex));
196189
197 domSectSource.classList.remove("hidden");190 domSectSource.classList.remove("hidden");
198 }
199191
200 function findFileRoot(path) {192 const slDom = document.getElementById("l" + sourceLocationIndex);
201 setInputString(path);193 if (slDom != null) {
202 const result = wasm_exports.find_file_root();194 slDom.scrollIntoView({
203 if (result === -1) return null;195 behavior: "smooth",
204 return result;196 block: "center",
197 });
198 }
205 }199 }
206200
207 function decodeString(ptr, len) {201 function decodeString(ptr, len) {
...@@ -224,10 +218,6 @@...@@ -224,10 +218,6 @@
224 wasmArray.set(jsArray);218 wasmArray.set(jsArray);
225 }219 }
226220
227 function declSourceHtml(decl_index) {
228 return unwrapString(wasm_exports.decl_source_html(decl_index));
229 }
230
231 function unwrapString(bigint) {221 function unwrapString(bigint) {
232 const ptr = Number(bigint & 0xffffffffn);222 const ptr = Number(bigint & 0xffffffffn);
233 const len = Number(bigint >> 32n);223 const len = Number(bigint >> 32n);
lib/fuzzer/wasm/main.zig+68-19
...@@ -235,7 +235,59 @@ export fn entryPoints() Slice(u32) {...@@ -235,7 +235,59 @@ export fn entryPoints() Slice(u32) {
235 return Slice(u32).init(entry_points.items);235 return Slice(u32).init(entry_points.items);
236}236}
237237
238/// Index into `coverage_source_locations`.
239const SourceLocationIndex = enum(u32) {
240 _,
241
242 fn haveCoverage(sli: SourceLocationIndex) bool {
243 return @intFromEnum(sli) < coverage_source_locations.items.len;
244 }
245
246 fn ptr(sli: SourceLocationIndex) *Coverage.SourceLocation {
247 return &coverage_source_locations.items[@intFromEnum(sli)];
248 }
249
250 fn sourceLocationLinkHtml(
251 sli: SourceLocationIndex,
252 out: *std.ArrayListUnmanaged(u8),
253 ) Allocator.Error!void {
254 const sl = sli.ptr();
255 try out.writer(gpa).print("<a href=\"#l{d}\">", .{@intFromEnum(sli)});
256 try sli.appendPath(out);
257 try out.writer(gpa).print(":{d}:{d}</a>", .{ sl.line, sl.column });
258 }
259
260 fn appendPath(sli: SourceLocationIndex, out: *std.ArrayListUnmanaged(u8)) Allocator.Error!void {
261 const sl = sli.ptr();
262 const file = coverage.fileAt(sl.file);
263 const file_name = coverage.stringAt(file.basename);
264 const dir_name = coverage.stringAt(coverage.directories.keys()[file.directory_index]);
265 try html_render.appendEscaped(out, dir_name);
266 try out.appendSlice(gpa, "/");
267 try html_render.appendEscaped(out, file_name);
268 }
269
270 fn toWalkFile(sli: SourceLocationIndex) ?Walk.File.Index {
271 var buf: std.ArrayListUnmanaged(u8) = .{};
272 defer buf.deinit(gpa);
273 sli.appendPath(&buf) catch @panic("OOM");
274 return @enumFromInt(Walk.files.getIndex(buf.items) orelse return null);
275 }
276
277 fn fileHtml(
278 sli: SourceLocationIndex,
279 out: *std.ArrayListUnmanaged(u8),
280 ) error{ OutOfMemory, SourceUnavailable }!void {
281 const walk_file_index = sli.toWalkFile() orelse return error.SourceUnavailable;
282 const root_node = walk_file_index.findRootDecl().get().ast_node;
283 html_render.fileSourceHtml(walk_file_index, out, root_node, .{}) catch |err| {
284 fatal("unable to render source: {s}", .{@errorName(err)});
285 };
286 }
287};
288
238var coverage = Coverage.init;289var coverage = Coverage.init;
290/// Index of type `SourceLocationIndex`.
239var coverage_source_locations: std.ArrayListUnmanaged(Coverage.SourceLocation) = .{};291var coverage_source_locations: std.ArrayListUnmanaged(Coverage.SourceLocation) = .{};
240/// Contains the most recent coverage update message, unmodified.292/// Contains the most recent coverage update message, unmodified.
241var recent_coverage_update: std.ArrayListUnmanaged(u8) = .{};293var recent_coverage_update: std.ArrayListUnmanaged(u8) = .{};
...@@ -263,27 +315,24 @@ fn updateCoverage(...@@ -263,27 +315,24 @@ fn updateCoverage(
263 try coverage.directories.reIndexContext(gpa, .{ .string_bytes = coverage.string_bytes.items });315 try coverage.directories.reIndexContext(gpa, .{ .string_bytes = coverage.string_bytes.items });
264}316}
265317
266export fn sourceLocationLinkHtml(index: u32) String {318export fn sourceLocationLinkHtml(index: SourceLocationIndex) String {
267 string_result.clearRetainingCapacity();319 string_result.clearRetainingCapacity();
268 sourceLocationLinkHtmlFallible(index, &string_result) catch @panic("OOM");320 index.sourceLocationLinkHtml(&string_result) catch @panic("OOM");
269 return String.init(string_result.items);321 return String.init(string_result.items);
270}322}
271323
272fn sourceLocationLinkHtmlFallible(index: u32, out: *std.ArrayListUnmanaged(u8)) Allocator.Error!void {324/// Returns empty string if coverage metadata is not available for this source location.
273 const sl = coverage_source_locations.items[index];325export fn sourceLocationPath(sli: SourceLocationIndex) String {
274 const file = coverage.fileAt(sl.file);326 string_result.clearRetainingCapacity();
275 const file_name = coverage.stringAt(file.basename);327 if (sli.haveCoverage()) sli.appendPath(&string_result) catch @panic("OOM");
276 const dir_name = coverage.stringAt(coverage.directories.keys()[file.directory_index]);328 return String.init(string_result.items);
277329}
278 out.clearRetainingCapacity();330
279 try out.appendSlice(gpa, "<a href=\"#");331export fn sourceLocationFileHtml(sli: SourceLocationIndex) String {
280 _ = html_render.missing_feature_url_escape;332 string_result.clearRetainingCapacity();
281 try out.writer(gpa).print("{s}/{s}:{d}:{d}", .{333 sli.fileHtml(&string_result) catch |err| switch (err) {
282 dir_name, file_name, sl.line, sl.column,334 error.OutOfMemory => @panic("OOM"),
283 });335 error.SourceUnavailable => {},
284 try out.appendSlice(gpa, "\">");336 };
285 try html_render.appendEscaped(out, dir_name);337 return String.init(string_result.items);
286 try out.appendSlice(gpa, "/");
287 try html_render.appendEscaped(out, file_name);
288 try out.writer(gpa).print(":{d}:{d}</a>", .{ sl.line, sl.column });
289}338}