Skip to content

introduce a fuzz testing web interface #20958

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 34 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
97643c1
fuzzer: track code coverage from all runs
andrewrk Jul 30, 2024
ffc050e
fuzzer: log errors and move deduplicated runs to shared mem
andrewrk Jul 30, 2024
e0ffac4
introduce a web interface for fuzzing
andrewrk Aug 1, 2024
107b272
fuzzer: share zig to html rendering with autodocs
andrewrk Aug 1, 2024
2e12b45
introduce tool for dumping coverage file
andrewrk Aug 2, 2024
de47acd
code coverage dumping tool basic implementation
andrewrk Aug 3, 2024
66954e8
std.debug.FixedBufferReader is fine
andrewrk Aug 3, 2024
1792258
std.debug.Dwarf: precompute .debug_line table
andrewrk Aug 3, 2024
c2ab461
std.Debug.Info: remove std.Progress integration
andrewrk Aug 3, 2024
53aa9d7
std.debug.Info.resolveSourceLocations: O(N) implementation
andrewrk Aug 4, 2024
5f92a03
README: update how std lib docs are found in a release build
andrewrk Aug 4, 2024
517cfb0
fuzzing: progress towards web UI
andrewrk Aug 4, 2024
d36c182
std.posix: add some more void bits
andrewrk Aug 4, 2024
b9fd0ee
add std.http.WebSocket
andrewrk Aug 4, 2024
2292563
std.debug.Coverage: use extern structs
andrewrk Aug 4, 2024
dec7e45
fuzzer web UI: receive coverage information
andrewrk Aug 4, 2024
f56d113
fuzzer web ui: render stats
andrewrk Aug 4, 2024
6e6164f
fuzzer web ui: add coverage stat
andrewrk Aug 4, 2024
e64a009
fuzzer web ui: introduce entry points
andrewrk Aug 5, 2024
db69641
fuzzing web ui: make entry point links clickable
andrewrk Aug 5, 2024
ef4c219
fuzzer web UI: navigate by source location index
andrewrk Aug 5, 2024
3d48602
fuzzer web UI: annotated PCs in source view
andrewrk Aug 5, 2024
38227e9
fuzzer web UI: render PCs with red or green depending on coverage
andrewrk Aug 5, 2024
bfc2ee0
fuzzer web ui: resolve cwd in sources.tar
andrewrk Aug 6, 2024
895fa87
dump-cov: show seen PCs
andrewrk Aug 6, 2024
1484f17
fuzzer web ui: fail scrolling into view gracefully
andrewrk Aug 6, 2024
5f5a7b5
wasm zig source rendering: fix annotation location off-by-one
andrewrk Aug 6, 2024
529df8c
libfuzzer: fix looking at wrong memory for pc counters
andrewrk Aug 6, 2024
8dae629
update branch for latest std.sort changes
andrewrk Aug 6, 2024
40edd11
std.debug: fix compile errors on windows and macos
andrewrk Aug 6, 2024
ff503ed
Compilation: fix not showing sub-errors for autodocs
andrewrk Aug 6, 2024
904fcda
Compilation: fix -femit-docs
andrewrk Aug 6, 2024
2a651ea
build runner: --fuzz not yet supported on Windows
andrewrk Aug 6, 2024
d721d9a
update coff_dwarf standalone test to new API
andrewrk Aug 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ Documentation** corresponding to the version of Zig that you are using by
following the appropriate link on the
[download page](https://ziglang.org/download).

Otherwise, you're looking at a release of Zig, and you can find documentation
here:

* doc/langref.html
* doc/std/index.html
Otherwise, you're looking at a release of Zig, so you can find the language
reference at `doc/langref.html`, and the standard library documentation by
running `zig std`, which will open a browser tab.

## Installation

Expand Down
37 changes: 36 additions & 1 deletion lib/compiler/build_runner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ const runner = @This();
pub const root = @import("@build");
pub const dependencies = @import("@dependencies");

pub const std_options: std.Options = .{
.side_channels_mitigations = .none,
.http_disable_tls = true,
.crypto_fork_safety = false,
};

pub fn main() !void {
// Here we use an ArenaAllocator backed by a page allocator because a build is a short-lived,
// one shot program. We don't need to waste time freeing memory and finding places to squish
Expand Down Expand Up @@ -106,6 +112,7 @@ pub fn main() !void {
var watch = false;
var fuzz = false;
var debounce_interval_ms: u16 = 50;
var listen_port: u16 = 0;

while (nextArg(args, &arg_idx)) |arg| {
if (mem.startsWith(u8, arg, "-Z")) {
Expand Down Expand Up @@ -203,6 +210,14 @@ pub fn main() !void {
next_arg, @errorName(err),
});
};
} else if (mem.eql(u8, arg, "--port")) {
const next_arg = nextArg(args, &arg_idx) orelse
fatalWithHint("expected u16 after '{s}'", .{arg});
listen_port = std.fmt.parseUnsigned(u16, next_arg, 10) catch |err| {
fatal("unable to parse port '{s}' as unsigned 16-bit integer: {s}\n", .{
next_arg, @errorName(err),
});
};
} else if (mem.eql(u8, arg, "--debug-log")) {
const next_arg = nextArgOrFatal(args, &arg_idx);
try debug_log_scopes.append(next_arg);
Expand Down Expand Up @@ -403,7 +418,27 @@ pub fn main() !void {
else => return err,
};
if (fuzz) {
Fuzz.start(&run.thread_pool, run.step_stack.keys(), run.ttyconf, main_progress_node);
switch (builtin.os.tag) {
// Current implementation depends on two things that need to be ported to Windows:
// * Memory-mapping to share data between the fuzzer and build runner.
// * COFF/PE support added to `std.debug.Info` (it needs a batching API for resolving
// many addresses to source locations).
.windows => fatal("--fuzz not yet implemented for {s}", .{@tagName(builtin.os.tag)}),
else => {},
}
const listen_address = std.net.Address.parseIp("127.0.0.1", listen_port) catch unreachable;
try Fuzz.start(
gpa,
arena,
global_cache_directory,
zig_lib_directory,
zig_exe,
&run.thread_pool,
run.step_stack.keys(),
run.ttyconf,
listen_address,
main_progress_node,
);
}

if (!watch) return cleanExit();
Expand Down
9 changes: 4 additions & 5 deletions lib/compiler/std-docs.zig
Original file line number Diff line number Diff line change
Expand Up @@ -275,10 +275,6 @@ fn buildWasmBinary(
) ![]const u8 {
const gpa = context.gpa;

const main_src_path = try std.fs.path.join(arena, &.{
context.zig_lib_directory, "docs", "wasm", "main.zig",
});

var argv: std.ArrayListUnmanaged([]const u8) = .{};

try argv.appendSlice(arena, &.{
Expand All @@ -298,7 +294,10 @@ fn buildWasmBinary(
"--name",
"autodoc",
"-rdynamic",
main_src_path,
"--dep",
"Walk",
try std.fmt.allocPrint(arena, "-Mroot={s}/docs/wasm/main.zig", .{context.zig_lib_directory}),
try std.fmt.allocPrint(arena, "-MWalk={s}/docs/wasm/Walk.zig", .{context.zig_lib_directory}),
"--listen=-",
});

Expand Down
34 changes: 33 additions & 1 deletion lib/compiler/test_runner.zig
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! Default test runner for unit tests.
const builtin = @import("builtin");

const std = @import("std");
const io = std.io;
const testing = std.testing;
const assert = std.debug.assert;

pub const std_options = .{
.logFn = log,
Expand All @@ -28,19 +30,26 @@ pub fn main() void {
@panic("unable to parse command line args");

var listen = false;
var opt_cache_dir: ?[]const u8 = null;

for (args[1..]) |arg| {
if (std.mem.eql(u8, arg, "--listen=-")) {
listen = true;
} else if (std.mem.startsWith(u8, arg, "--seed=")) {
testing.random_seed = std.fmt.parseUnsigned(u32, arg["--seed=".len..], 0) catch
@panic("unable to parse --seed command line argument");
} else if (std.mem.startsWith(u8, arg, "--cache-dir")) {
opt_cache_dir = arg["--cache-dir=".len..];
} else {
@panic("unrecognized command line argument");
}
}

fba.reset();
if (builtin.fuzz) {
const cache_dir = opt_cache_dir orelse @panic("missing --cache-dir=[path] argument");
fuzzer_init(FuzzerSlice.fromSlice(cache_dir));
}

if (listen) {
return mainServer() catch @panic("internal test runner failure");
Expand All @@ -59,6 +68,11 @@ fn mainServer() !void {
});
defer server.deinit();

if (builtin.fuzz) {
const coverage_id = fuzzer_coverage_id();
try server.serveU64Message(.coverage_id, coverage_id);
}

while (true) {
const hdr = try server.receiveMessage();
switch (hdr.tag) {
Expand Down Expand Up @@ -129,7 +143,9 @@ fn mainServer() !void {
});
},
.start_fuzzing => {
if (!builtin.fuzz) unreachable;
const index = try server.receiveBody_u32();
var first = true;
const test_fn = builtin.test_functions[index];
while (true) {
testing.allocator_instance = .{};
Expand All @@ -148,6 +164,10 @@ fn mainServer() !void {
};
if (!is_fuzz_test) @panic("missed call to std.testing.fuzzInput");
if (log_err_count != 0) @panic("error logs detected");
if (first) {
first = false;
try server.serveU64Message(.fuzz_start_addr, entry_addr);
}
}
},

Expand Down Expand Up @@ -315,20 +335,32 @@ const FuzzerSlice = extern struct {
ptr: [*]const u8,
len: usize,

/// Inline to avoid fuzzer instrumentation.
inline fn toSlice(s: FuzzerSlice) []const u8 {
return s.ptr[0..s.len];
}

/// Inline to avoid fuzzer instrumentation.
inline fn fromSlice(s: []const u8) FuzzerSlice {
return .{ .ptr = s.ptr, .len = s.len };
}
};

var is_fuzz_test: bool = undefined;
var entry_addr: usize = 0;

extern fn fuzzer_next() FuzzerSlice;
extern fn fuzzer_init(cache_dir: FuzzerSlice) void;
extern fn fuzzer_coverage_id() u64;

pub fn fuzzInput(options: testing.FuzzInputOptions) []const u8 {
@disableInstrumentation();
if (crippled) return "";
is_fuzz_test = true;
if (builtin.fuzz) return fuzzer_next().toSlice();
if (builtin.fuzz) {
if (entry_addr == 0) entry_addr = @returnAddress();
return fuzzer_next().toSlice();
}
if (options.corpus.len == 0) return "";
var prng = std.Random.DefaultPrng.init(testing.random_seed);
const random = prng.random();
Expand Down
18 changes: 9 additions & 9 deletions lib/docs/wasm/Decl.zig
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
const Decl = @This();
const std = @import("std");
const Ast = std.zig.Ast;
const Walk = @import("Walk.zig");
const gpa = std.heap.wasm_allocator;
const assert = std.debug.assert;
const log = std.log;
const Oom = error{OutOfMemory};

ast_node: Ast.Node.Index,
file: Walk.File.Index,
/// The decl whose namespace this is in.
Expand Down Expand Up @@ -215,12 +224,3 @@ pub fn find(search_string: []const u8) Decl.Index {
}
return current_decl_index;
}

const Decl = @This();
const std = @import("std");
const Ast = std.zig.Ast;
const Walk = @import("Walk.zig");
const gpa = std.heap.wasm_allocator;
const assert = std.debug.assert;
const log = std.log;
const Oom = error{OutOfMemory};
20 changes: 11 additions & 9 deletions lib/docs/wasm/Walk.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
//! Find and annotate identifiers with links to their declarations.

const Walk = @This();
const std = @import("std");
const Ast = std.zig.Ast;
const assert = std.debug.assert;
const log = std.log;
const gpa = std.heap.wasm_allocator;
const Oom = error{OutOfMemory};

pub const Decl = @import("Decl.zig");

pub var files: std.StringArrayHashMapUnmanaged(File) = .{};
pub var decls: std.ArrayListUnmanaged(Decl) = .{};
pub var modules: std.StringArrayHashMapUnmanaged(File.Index) = .{};
Expand Down Expand Up @@ -1120,15 +1131,6 @@ pub fn isPrimitiveNonType(name: []const u8) bool {
// try w.root();
//}

const Walk = @This();
const std = @import("std");
const Ast = std.zig.Ast;
const assert = std.debug.assert;
const Decl = @import("Decl.zig");
const log = std.log;
const gpa = std.heap.wasm_allocator;
const Oom = error{OutOfMemory};

fn shrinkToFit(m: anytype) void {
m.shrinkAndFree(gpa, m.entries.len);
}
Loading