fuzzer web UI: navigate by source location index

This will help scroll the point of interest into view
This commit is contained in:
Andrew Kelley 2024-08-05 14:10:43 -07:00
parent db69641061
commit ef4c2193fc
2 changed files with 89 additions and 50 deletions

View File

@ -68,6 +68,8 @@
} }
function navigate(location_hash) { function navigate(location_hash) {
domSectSource.classList.add("hidden");
curNavLocation = null; curNavLocation = null;
curNavSearch = null; curNavSearch = null;
@ -82,23 +84,19 @@
curNavSearch = decodeURIComponent(query.substring(qpos + 1)); curNavSearch = decodeURIComponent(query.substring(qpos + 1));
} }
if (nonSearchPart.length > 0) { if (nonSearchPart[0] == "l") {
curNavLocation = nonSearchPart; curNavLocation = +nonSearchPart.substring(1);
renderSource(curNavLocation);
} }
} }
render(); render();
if (curNavLocation != null) {
// TODO
// scrollToSourceLocation(findSourceLocationIndex(curNavLocation));
}
} }
function connectWebSocket() { function connectWebSocket() {
const host = window.document.location.host; const host = document.location.host;
const pathname = window.document.location.pathname; const pathname = document.location.pathname;
const isHttps = window.document.location.protocol === 'https:'; const isHttps = document.location.protocol === 'https:';
const match = host.match(/^(.+):(\d+)$/); const match = host.match(/^(.+):(\d+)$/);
const defaultPort = isHttps ? 443 : 80; const defaultPort = isHttps ? 443 : 80;
const port = match ? parseInt(match[2], 10) : defaultPort; const port = match ? parseInt(match[2], 10) : defaultPort;
@ -139,17 +137,12 @@
} }
function onSourceIndexChange() { function onSourceIndexChange() {
console.log("source location index metadata updated");
render(); render();
if (curNavLocation != null) renderSource(curNavLocation);
} }
function render() { function render() {
domStatus.classList.add("hidden"); domStatus.classList.add("hidden");
domSectSource.classList.add("hidden");
if (curNavLocation != null) {
renderSource(curNavLocation.split(":")[0]);
}
} }
function renderStats() { function renderStats() {
@ -186,22 +179,23 @@
return ((Number(a) / Number(b)) * 100).toFixed(1); return ((Number(a) / Number(b)) * 100).toFixed(1);
} }
function renderSource(path) { function renderSource(sourceLocationIndex) {
const decl_index = findFileRoot(path); const pathName = unwrapString(wasm_exports.sourceLocationPath(sourceLocationIndex));
if (decl_index == null) throw new Error("file not found: " + path); if (pathName.length === 0) return;
const h2 = domSectSource.children[0]; const h2 = domSectSource.children[0];
h2.innerText = path; h2.innerText = pathName;
domSourceText.innerHTML = declSourceHtml(decl_index); domSourceText.innerHTML = unwrapString(wasm_exports.sourceLocationFileHtml(sourceLocationIndex));
domSectSource.classList.remove("hidden"); domSectSource.classList.remove("hidden");
}
function findFileRoot(path) { const slDom = document.getElementById("l" + sourceLocationIndex);
setInputString(path); if (slDom != null) {
const result = wasm_exports.find_file_root(); slDom.scrollIntoView({
if (result === -1) return null; behavior: "smooth",
return result; block: "center",
});
}
} }
function decodeString(ptr, len) { function decodeString(ptr, len) {
@ -224,10 +218,6 @@
wasmArray.set(jsArray); wasmArray.set(jsArray);
} }
function declSourceHtml(decl_index) {
return unwrapString(wasm_exports.decl_source_html(decl_index));
}
function unwrapString(bigint) { function unwrapString(bigint) {
const ptr = Number(bigint & 0xffffffffn); const ptr = Number(bigint & 0xffffffffn);
const len = Number(bigint >> 32n); const len = Number(bigint >> 32n);

View File

@ -235,7 +235,59 @@ export fn entryPoints() Slice(u32) {
return Slice(u32).init(entry_points.items); return Slice(u32).init(entry_points.items);
} }
/// Index into `coverage_source_locations`.
const SourceLocationIndex = enum(u32) {
_,
fn haveCoverage(sli: SourceLocationIndex) bool {
return @intFromEnum(sli) < coverage_source_locations.items.len;
}
fn ptr(sli: SourceLocationIndex) *Coverage.SourceLocation {
return &coverage_source_locations.items[@intFromEnum(sli)];
}
fn sourceLocationLinkHtml(
sli: SourceLocationIndex,
out: *std.ArrayListUnmanaged(u8),
) Allocator.Error!void {
const sl = sli.ptr();
try out.writer(gpa).print("<a href=\"#l{d}\">", .{@intFromEnum(sli)});
try sli.appendPath(out);
try out.writer(gpa).print(":{d}:{d}</a>", .{ sl.line, sl.column });
}
fn appendPath(sli: SourceLocationIndex, out: *std.ArrayListUnmanaged(u8)) Allocator.Error!void {
const sl = sli.ptr();
const file = coverage.fileAt(sl.file);
const file_name = coverage.stringAt(file.basename);
const dir_name = coverage.stringAt(coverage.directories.keys()[file.directory_index]);
try html_render.appendEscaped(out, dir_name);
try out.appendSlice(gpa, "/");
try html_render.appendEscaped(out, file_name);
}
fn toWalkFile(sli: SourceLocationIndex) ?Walk.File.Index {
var buf: std.ArrayListUnmanaged(u8) = .{};
defer buf.deinit(gpa);
sli.appendPath(&buf) catch @panic("OOM");
return @enumFromInt(Walk.files.getIndex(buf.items) orelse return null);
}
fn fileHtml(
sli: SourceLocationIndex,
out: *std.ArrayListUnmanaged(u8),
) 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| {
fatal("unable to render source: {s}", .{@errorName(err)});
};
}
};
var coverage = Coverage.init; var coverage = Coverage.init;
/// Index of type `SourceLocationIndex`.
var coverage_source_locations: std.ArrayListUnmanaged(Coverage.SourceLocation) = .{}; var coverage_source_locations: std.ArrayListUnmanaged(Coverage.SourceLocation) = .{};
/// Contains the most recent coverage update message, unmodified. /// Contains the most recent coverage update message, unmodified.
var recent_coverage_update: std.ArrayListUnmanaged(u8) = .{}; var recent_coverage_update: std.ArrayListUnmanaged(u8) = .{};
@ -263,27 +315,24 @@ fn updateCoverage(
try coverage.directories.reIndexContext(gpa, .{ .string_bytes = coverage.string_bytes.items }); try coverage.directories.reIndexContext(gpa, .{ .string_bytes = coverage.string_bytes.items });
} }
export fn sourceLocationLinkHtml(index: u32) String { export fn sourceLocationLinkHtml(index: SourceLocationIndex) String {
string_result.clearRetainingCapacity(); string_result.clearRetainingCapacity();
sourceLocationLinkHtmlFallible(index, &string_result) catch @panic("OOM"); index.sourceLocationLinkHtml(&string_result) catch @panic("OOM");
return String.init(string_result.items); return String.init(string_result.items);
} }
fn sourceLocationLinkHtmlFallible(index: u32, out: *std.ArrayListUnmanaged(u8)) Allocator.Error!void { /// Returns empty string if coverage metadata is not available for this source location.
const sl = coverage_source_locations.items[index]; export fn sourceLocationPath(sli: SourceLocationIndex) String {
const file = coverage.fileAt(sl.file); string_result.clearRetainingCapacity();
const file_name = coverage.stringAt(file.basename); if (sli.haveCoverage()) sli.appendPath(&string_result) catch @panic("OOM");
const dir_name = coverage.stringAt(coverage.directories.keys()[file.directory_index]); return String.init(string_result.items);
}
out.clearRetainingCapacity();
try out.appendSlice(gpa, "<a href=\"#"); export fn sourceLocationFileHtml(sli: SourceLocationIndex) String {
_ = html_render.missing_feature_url_escape; string_result.clearRetainingCapacity();
try out.writer(gpa).print("{s}/{s}:{d}:{d}", .{ sli.fileHtml(&string_result) catch |err| switch (err) {
dir_name, file_name, sl.line, sl.column, error.OutOfMemory => @panic("OOM"),
}); error.SourceUnavailable => {},
try out.appendSlice(gpa, "\">"); };
try html_render.appendEscaped(out, dir_name); return String.init(string_result.items);
try out.appendSlice(gpa, "/");
try html_render.appendEscaped(out, file_name);
try out.writer(gpa).print(":{d}:{d}</a>", .{ sl.line, sl.column });
} }