authorgravatar for goon.pri.low@gmail.comKendall Condon <goon.pri.low@gmail.com> 2026-03-26 18:25:17-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-03 12:27:34+02:00
logd8ba173e5eec11a7483660c6516b41cb3cf38802
tree4bb52a45dece1465ff9dc00d5f565f355c09951a
parentd34b868bcf759a81275fe51b9c2faeb15bf7bedd

multiprocess fuzzing

- New Features -- Multiprocess Fuzzing The fuzzer now is able to utilize multiple cores. This is controllable with the `-j` build option. Limited fuzzing still uses one core. -- Fuzzing Infinite Mode When provided multiple tests, the fuzzer now switches between them and prioritizes the most effective and interesting ones. Over time already explored tests will become barely run compared to tests yielding new inputs. -- Crash Dumps Crashing inputs are now saved to a file indicated by the crash message. It is recommended to use these files to reproduce the crash using `std.testing.FuzzInputOptions.corpus` and @embedFile. - Design Each fuzzing process is assigned an instance id which has the following uses: * In conjunction with the pc hash and running test index, they uniquely identify input files in the case of a crash. * It is combined with the test seed for a unique rng seed. * Instance 0 is solely responsible for syncing the filesystem corpus. When new inputs are found, they are sent to the build server. It then distributes the new input to the other instances. Each instance has a concurrent poller managed by the test runner which sends received inputs to libfuzzer. (note that this is affected by #31718 and so can (rarely) deadlock) For fuzzing infinite mode, the test runner now receives a list of tests from the build server. The fuzzer runs tests in batches of one second, approximated in cycles by the previous batch's run speed. Tests finding new inputs or with few runs are given a higher run chance. The baseline run chance is based off the recency of the last find and the number of pcs the test has hit.

11 files changed, 1695 insertions(+), 469 deletions(-)

lib/compiler/build_runner.zig+1
...@@ -424,6 +424,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -424,6 +424,7 @@ pub fn main(init: process.Init.Minimal) !void {
424 fatal("unable to parse jobs count '{s}': {t}", .{ text, err });424 fatal("unable to parse jobs count '{s}': {t}", .{ text, err });
425 if (n < 1) fatal("number of jobs must be at least 1", .{});425 if (n < 1) fatal("number of jobs must be at least 1", .{});
426 threaded.setAsyncLimit(.limited(n));426 threaded.setAsyncLimit(.limited(n));
427 graph.max_jobs = n;
427 } else if (mem.eql(u8, arg, "--")) {428 } else if (mem.eql(u8, arg, "--")) {
428 builder.args = argsRest(args, arg_idx);429 builder.args = argsRest(args, arg_idx);
429 break;430 break;
lib/compiler/test_runner.zig+191-59
...@@ -6,6 +6,7 @@ const Io = std.Io;...@@ -6,6 +6,7 @@ const Io = std.Io;
6const fatal = std.process.fatal;6const fatal = std.process.fatal;
7const testing = std.testing;7const testing = std.testing;
8const assert = std.debug.assert;8const assert = std.debug.assert;
9const panic = std.debug.panic;
9const fuzz_abi = std.Build.abi.fuzz;10const fuzz_abi = std.Build.abi.fuzz;
1011
11pub const std_options: std.Options = .{12pub const std_options: std.Options = .{
...@@ -17,6 +18,8 @@ var fba: std.heap.FixedBufferAllocator = .init(&fba_buffer);...@@ -17,6 +18,8 @@ var fba: std.heap.FixedBufferAllocator = .init(&fba_buffer);
17var fba_buffer: [8192]u8 = undefined;18var fba_buffer: [8192]u8 = undefined;
18var stdin_buffer: [4096]u8 = undefined;19var stdin_buffer: [4096]u8 = undefined;
19var stdout_buffer: [4096]u8 = undefined;20var stdout_buffer: [4096]u8 = undefined;
21var stdin_reader: Io.File.Reader = undefined;
22var stdout_writer: Io.File.Writer = undefined;
20const runner_threaded_io: Io = Io.Threaded.global_single_threaded.io();23const runner_threaded_io: Io = Io.Threaded.global_single_threaded.io();
2124
22/// Keep in sync with logic in `std.Build.addRunArtifact` which decides whether25/// Keep in sync with logic in `std.Build.addRunArtifact` which decides whether
...@@ -38,10 +41,10 @@ pub fn main(init: std.process.Init.Minimal) void {...@@ -38,10 +41,10 @@ pub fn main(init: std.process.Init.Minimal) void {
38 }41 }
3942
40 if (need_simple) {43 if (need_simple) {
41 return mainSimple() catch |err| std.debug.panic("test failure: {t}", .{err});44 return mainSimple() catch |err| panic("test failure: {t}", .{err});
42 }45 }
4346
44 const args = init.args.toSlice(fba.allocator()) catch |err| std.debug.panic("unable to parse command line args: {t}", .{err});47 const args = init.args.toSlice(fba.allocator()) catch |err| panic("unable to parse command line args: {t}", .{err});
4548
46 var listen = false;49 var listen = false;
47 var opt_cache_dir: ?[]const u8 = null;50 var opt_cache_dir: ?[]const u8 = null;
...@@ -55,7 +58,7 @@ pub fn main(init: std.process.Init.Minimal) void {...@@ -55,7 +58,7 @@ pub fn main(init: std.process.Init.Minimal) void {
55 } else if (std.mem.startsWith(u8, arg, "--cache-dir")) {58 } else if (std.mem.startsWith(u8, arg, "--cache-dir")) {
56 opt_cache_dir = arg["--cache-dir=".len..];59 opt_cache_dir = arg["--cache-dir=".len..];
57 } else {60 } else {
58 std.debug.panic("unrecognized command line argument: {s}", .{arg});61 panic("unrecognized command line argument: {s}", .{arg});
59 }62 }
60 }63 }
6164
...@@ -65,7 +68,7 @@ pub fn main(init: std.process.Init.Minimal) void {...@@ -65,7 +68,7 @@ pub fn main(init: std.process.Init.Minimal) void {
65 }68 }
6669
67 if (listen) {70 if (listen) {
68 return mainServer(init) catch |err| std.debug.panic("internal test runner failure: {t}", .{err});71 return mainServer(init) catch |err| panic("internal test runner failure: {t}", .{err});
69 } else {72 } else {
70 return mainTerminal(init);73 return mainTerminal(init);
71 }74 }
...@@ -73,24 +76,14 @@ pub fn main(init: std.process.Init.Minimal) void {...@@ -73,24 +76,14 @@ pub fn main(init: std.process.Init.Minimal) void {
7376
74fn mainServer(init: std.process.Init.Minimal) !void {77fn mainServer(init: std.process.Init.Minimal) !void {
75 @disableInstrumentation();78 @disableInstrumentation();
76 var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io, &stdin_buffer);79 stdin_reader = .initStreaming(.stdin(), runner_threaded_io, &stdin_buffer);
77 var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io, &stdout_buffer);80 stdout_writer = .initStreaming(.stdout(), runner_threaded_io, &stdout_buffer);
78 var server = try std.zig.Server.init(.{81 var server = try std.zig.Server.init(.{
79 .in = &stdin_reader.interface,82 .in = &stdin_reader.interface,
80 .out = &stdout_writer.interface,83 .out = &stdout_writer.interface,
81 .zig_version = builtin.zig_version_string,84 .zig_version = builtin.zig_version_string,
82 });85 });
8386
84 if (builtin.fuzz) {
85 const coverage = fuzz_abi.fuzzer_coverage();
86 try server.serveCoverageIdMessage(
87 coverage.id,
88 coverage.runs,
89 coverage.unique,
90 coverage.seen,
91 );
92 }
93
94 while (true) {87 while (true) {
95 const hdr = try server.receiveMessage();88 const hdr = try server.receiveMessage();
96 switch (hdr.tag) {89 switch (hdr.tag) {
...@@ -180,48 +173,75 @@ fn mainServer(init: std.process.Init.Minimal) !void {...@@ -180,48 +173,75 @@ fn mainServer(init: std.process.Init.Minimal) !void {
180 // since they are not present.173 // since they are not present.
181 if (!builtin.fuzz) unreachable;174 if (!builtin.fuzz) unreachable;
182175
183 const index: u32 = @intCast(index: {176 var gpa_instance: std.heap.DebugAllocator(.{}) = .init;
184 testing.allocator_instance = .{};177 defer if (gpa_instance.deinit() == .leak) {
185 defer if (testing.allocator_instance.deinit() == .leak) {178 @panic("internal test runner memory leak");
186 @panic("internal test runner memory leak");179 };
187 };180 const gpa = gpa_instance.allocator();
188181 var io_instance: Io.Threaded = .init(gpa, .{
189 const name_len = try server.receiveBody_u32();182 .argv0 = .init(init.args),
190 const name = try server.in.readAlloc(testing.allocator, @intCast(name_len));183 .environ = init.environ,
191 defer testing.allocator.free(name);
192 for (0.., builtin.test_functions) |i, test_fn| {
193 if (std.mem.eql(u8, name, test_fn.name)) {
194 break :index i;
195 }
196 } else {
197 std.debug.panic("fuzz test {s} no longer exists", .{name});
198 }
199 });184 });
185 defer io_instance.deinit();
186 const io = io_instance.io();
187
200 const mode: fuzz_abi.LimitKind = @enumFromInt(try server.receiveBody_u8());188 const mode: fuzz_abi.LimitKind = @enumFromInt(try server.receiveBody_u8());
201 const amount_or_instance = try server.receiveBody_u64();189 const amount_or_instance = try server.receiveBody_u64();
190 const main_instance = mode == .iterations or amount_or_instance == 0;
191
192 if (main_instance) {
193 const coverage = fuzz_abi.fuzzer_coverage();
194 try server.serveCoverageIdMessage(
195 coverage.id,
196 coverage.runs,
197 coverage.unique,
198 coverage.seen,
199 );
200 }
202201
203 const test_fn = builtin.test_functions[index];202 const n_tests: u32 = try server.receiveBody_u32();
204 const entry_addr = @intFromPtr(test_fn.func);203 const test_indexes = try gpa.alloc(u32, n_tests);
204 defer gpa.free(test_indexes);
205 fuzz_runner = .{
206 .indexes = test_indexes,
207 .server = &server,
208 .gpa = gpa,
209 .io = io,
210 .input_poller = undefined,
211 };
205212
206 try server.serveU64Message(.fuzz_start_addr, fuzz_abi.fuzzer_unslide_address(entry_addr));213 {
207 defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);214 var large_name_buf: std.ArrayList(u8) = .empty;
208 is_fuzz_test = false;215 defer large_name_buf.deinit(gpa);
209 fuzz_test_index = index;216 for (test_indexes) |*i| {
210 fuzz_mode = mode;217 const name_len = try server.receiveBody_u32();
211 fuzz_amount_or_instance = amount_or_instance;218 const name = if (name_len <= server.in.buffer.len)
219 try server.in.take(name_len)
220 else large_name: {
221 try large_name_buf.resize(gpa, name_len);
222 try server.in.readSliceAll(large_name_buf.items);
223 break :large_name large_name_buf.items;
224 };
225
226 for (0.., builtin.test_functions) |test_i, test_fn| {
227 if (std.mem.eql(u8, name, test_fn.name)) {
228 i.* = @intCast(test_i);
229 break;
230 }
231 } else {
232 panic("fuzz test {s} no longer exists", .{name});
233 }
212234
213 test_fn.func() catch |err| switch (err) {235 if (main_instance) {
214 error.SkipZigTest => return,236 const relocated_entry_addr = @intFromPtr(builtin.test_functions[i.*].func);
215 else => {237 const entry_addr = fuzz_abi.fuzzer_unslide_address(relocated_entry_addr);
216 if (@errorReturnTrace()) |trace| {238 try server.serveU64Message(.fuzz_start_addr, entry_addr);
217 std.debug.dumpStackTrace(trace);
218 }239 }
219 std.debug.print("failed with error.{t}\n", .{err});240 }
220 std.process.exit(1);241 }
221 },242
222 };243 fuzz_abi.fuzzer_main(n_tests, testing.random_seed, mode, amount_or_instance);
223 if (!is_fuzz_test) @panic("missed call to std.testing.fuzz");244
224 if (log_err_count != 0) @panic("error logs detected");
225 assert(mode != .forever);245 assert(mode != .forever);
226 std.process.exit(0);246 std.process.exit(0);
227 },247 },
...@@ -382,16 +402,126 @@ pub fn mainSimple() anyerror!void {...@@ -382,16 +402,126 @@ pub fn mainSimple() anyerror!void {
382 passed += 1;402 passed += 1;
383 }403 }
384 if (enable_print) {404 if (enable_print) {
385 var stdout_writer = stdout.writer(runner_threaded_io, &.{});405 var unbuffered_stdout_writer = stdout.writer(runner_threaded_io, &.{});
386 stdout_writer.interface.print("{} passed, {} skipped, {} failed\n", .{ passed, skipped, failed }) catch {};406 unbuffered_stdout_writer.interface.print(
407 "{} passed, {} skipped, {} failed\n",
408 .{ passed, skipped, failed },
409 ) catch {};
387 }410 }
388 if (failed != 0) std.process.exit(1);411 if (failed != 0) std.process.exit(1);
389}412}
390413
391var is_fuzz_test: bool = undefined;414var is_fuzz_test: bool = undefined;
392var fuzz_test_index: u32 = undefined;415var fuzz_runner: if (builtin.fuzz) struct {
393var fuzz_mode: fuzz_abi.LimitKind = undefined;416 indexes: []u32,
394var fuzz_amount_or_instance: u64 = undefined;417 server: *std.zig.Server,
418 gpa: std.mem.Allocator,
419 io: Io,
420 input_poller: Io.Future(Io.Cancelable!void),
421
422 comptime {
423 assert(builtin.fuzz); // `fuzz_runner` was analyzed in non-fuzzing compilation
424 }
425
426 export fn runner_test_run(i: u32) void {
427 @disableInstrumentation();
428
429 fuzz_runner.server.serveU32Message(.fuzz_test_change, i) catch |e| switch (e) {
430 error.WriteFailed => panic("failed to write to stdout: {t}", .{stdout_writer.err.?}),
431 };
432
433 testing.allocator_instance = .{};
434 defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);
435 is_fuzz_test = false;
436
437 builtin.test_functions[fuzz_runner.indexes[i]].func() catch |err| switch (err) {
438 error.SkipZigTest => return,
439 else => {
440 if (@errorReturnTrace()) |trace| {
441 std.debug.dumpStackTrace(trace);
442 }
443 std.debug.print("failed with error.{t}\n", .{err});
444 std.process.exit(1);
445 },
446 };
447
448 if (!is_fuzz_test) @panic("missed call to std.testing.fuzz");
449 if (log_err_count != 0) @panic("error logs detected");
450 }
451
452 export fn runner_test_name(i: u32) fuzz_abi.Slice {
453 @disableInstrumentation();
454 return .fromSlice(builtin.test_functions[fuzz_runner.indexes[i]].name);
455 }
456
457 export fn runner_broadcast_input(test_i: u32, bytes_slice: fuzz_abi.Slice) void {
458 @disableInstrumentation();
459 const bytes = bytes_slice.toSlice();
460 fuzz_runner.server.serveBroadcastFuzzInputMessage(test_i, bytes) catch |e| switch (e) {
461 error.WriteFailed => panic("failed to write to stdout: {t}", .{stdout_writer.err.?}),
462 };
463 }
464
465 export fn runner_start_input_poller() void {
466 @disableInstrumentation();
467 const future = fuzz_runner.io.concurrent(inputPoller, .{}) catch |e| switch (e) {
468 error.ConcurrencyUnavailable => @panic("failed to spawn concurrent fuzz input poller"),
469 };
470 fuzz_runner.input_poller = future;
471 }
472
473 export fn runner_stop_input_poller() void {
474 @disableInstrumentation();
475 assert(fuzz_runner.input_poller.cancel(fuzz_runner.io) == error.Canceled);
476 }
477
478 export fn runner_futex_wait(ptr: *const u32, expected: u32) bool {
479 @disableInstrumentation();
480 return fuzz_runner.io.futexWait(u32, ptr, expected) == error.Canceled;
481 }
482
483 export fn runner_futex_wake(ptr: *const u32, waiters: u32) void {
484 @disableInstrumentation();
485 fuzz_runner.io.futexWake(u32, ptr, waiters);
486 }
487
488 fn inputPoller() Io.Cancelable!void {
489 @disableInstrumentation();
490 switch (inputPollerInner()) {
491 error.Canceled => return error.Canceled,
492 error.ReadFailed => {
493 if (stdin_reader.err.? == error.Canceled) return error.Canceled;
494 panic("failed to read from stdin: {t}", .{stdin_reader.err.?});
495 },
496 error.EndOfStream => @panic("unexpected end of stdin"),
497 }
498 }
499
500 fn inputPollerInner() (Io.Cancelable || Io.Reader.Error) {
501 @disableInstrumentation();
502 const server = fuzz_runner.server;
503 var large_bytes_list: std.ArrayList(u8) = .empty;
504 defer large_bytes_list.deinit(fuzz_runner.gpa);
505 while (true) {
506 const hdr = try server.receiveMessage();
507 if (hdr.tag != .new_fuzz_input) {
508 panic("unexpected message: {x}\n", .{@intFromEnum(hdr.tag)});
509 }
510 const test_i = try server.receiveBody_u32();
511 const input_len = hdr.bytes_len - 4;
512 const bytes = if (input_len <= server.in.buffer.len)
513 try server.in.take(input_len)
514 else bytes: {
515 large_bytes_list.resize(fuzz_runner.gpa, @intCast(input_len)) catch @panic("OOM");
516 try server.in.readSliceAll(large_bytes_list.items);
517 break :bytes large_bytes_list.items;
518 };
519 if (fuzz_abi.fuzzer_receive_input(test_i, .fromSlice(bytes))) {
520 return error.Canceled;
521 }
522 }
523 }
524} else void = undefined;
395525
396pub fn fuzz(526pub fn fuzz(
397 context: anytype,527 context: anytype,
...@@ -448,16 +578,18 @@ pub fn fuzz(...@@ -448,16 +578,18 @@ pub fn fuzz(
448 return false;578 return false;
449 }579 }
450 };580 };
581
451 if (builtin.fuzz) {582 if (builtin.fuzz) {
583 // Preserve the calling test's allocator state
452 const prev_allocator_state = testing.allocator_instance;584 const prev_allocator_state = testing.allocator_instance;
453 testing.allocator_instance = .{};585 testing.allocator_instance = .{};
454 defer testing.allocator_instance = prev_allocator_state;586 defer testing.allocator_instance = prev_allocator_state;
455 global.ctx = context;
456587
457 fuzz_abi.fuzzer_set_test(&global.test_one, .fromSlice(builtin.test_functions[fuzz_test_index].name));588 global.ctx = context;
589 fuzz_abi.fuzzer_set_test(&global.test_one);
458 for (options.corpus) |elem|590 for (options.corpus) |elem|
459 fuzz_abi.fuzzer_new_input(.fromSlice(elem));591 fuzz_abi.fuzzer_new_input(.fromSlice(elem));
460 fuzz_abi.fuzzer_main(fuzz_mode, fuzz_amount_or_instance);592 fuzz_abi.fuzzer_start_test();
461 return;593 return;
462 }594 }
463595
lib/fuzzer.zig+855-307
...@@ -13,7 +13,7 @@ pub const std_options = std.Options{...@@ -13,7 +13,7 @@ pub const std_options = std.Options{
13 .logFn = logOverride,13 .logFn = logOverride,
14};14};
1515
16const io = std.Io.Threaded.global_single_threaded.io();16const io = Io.Threaded.global_single_threaded.io();
1717
18fn logOverride(18fn logOverride(
19 comptime level: std.log.Level,19 comptime level: std.log.Level,
...@@ -77,23 +77,27 @@ const Executable = struct {...@@ -77,23 +77,27 @@ const Executable = struct {
77 panic("failed to create directory 'v': {t}", .{e});77 panic("failed to create directory 'v': {t}", .{e});
78 defer v.close(io);78 defer v.close(io);
7979
80 const coverage_file, const populate = if (v.createFile(io, &file_name, .{80 // Since acquiring locks in createFile is not gauraunteed to be atomic, it is not possible
81 // to ensure if we create the file we obtain an exclusive lock to populate it since another
82 // process may acquire a shared lock between the file being created and the lock request.
83 //
84 // Instead, the length will be used to determine if the file needs populated, and no
85 // process will acquire a shared lock before the coverage file is known to have been
86 // exclusively locked (i.e. is already locked). This means another process than the
87 // one which created the file could populate it, which is fine.
88 const coverage_file = v.createFile(io, &file_name, .{
81 .read = true,89 .read = true,
82 // If we create the file, we want to block other processes while we populate it90 .truncate = false,
83 .lock = .exclusive,91 }) catch |e| panic("failed to open coverage file '{s}': {t}", .{ &file_name, e });
84 .exclusive = true,92
85 })) |f|93 const maybe_populate = coverage_file.tryLock(io, .exclusive) catch |e| panic(
86 .{ f, true }94 "failed to acquire exclusive lock coverage file '{s}': {t}",
87 else |e| switch (e) {95 .{ &file_name, e },
88 error.PathAlreadyExists => .{ v.openFile(io, &file_name, .{96 );
89 .mode = .read_write,97 if (!maybe_populate) {
90 .lock = .shared,98 coverage_file.lock(io, .shared) catch |e|
91 }) catch |e2| panic(99 panic("failed to acquire share lock coverage file '{s}': {t}", .{ &file_name, e });
92 "failed to open existing coverage file '{s}': {t}",100 }
93 .{ &file_name, e2 },
94 ), false },
95 else => panic("failed to create coverage file '{s}': {t}", .{ &file_name, e }),
96 };
97101
98 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize);102 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize);
99 comptime assert(abi.SeenPcsHeader.trailing[1] == .pc_addr);103 comptime assert(abi.SeenPcsHeader.trailing[1] == .pc_addr);
...@@ -102,16 +106,21 @@ const Executable = struct {...@@ -102,16 +106,21 @@ const Executable = struct {
102 pc_bitset_usizes * @sizeOf(usize) +106 pc_bitset_usizes * @sizeOf(usize) +
103 pcs.len * @sizeOf(usize);107 pcs.len * @sizeOf(usize);
104108
105 if (populate) {109 var populate: bool = false;
110 const size = coverage_file.length(io) catch |e|
111 panic("failed to stat coverage file '{s}': {t}", .{ &file_name, e });
112 if (size == 0 and maybe_populate) {
106 coverage_file.setLength(io, coverage_file_len) catch |e|113 coverage_file.setLength(io, coverage_file_len) catch |e|
107 panic("failed to resize new coverage file '{s}': {t}", .{ &file_name, e });114 panic("failed to resize new coverage file '{s}': {t}", .{ &file_name, e });
108 } else {115 populate = true;
109 const size = coverage_file.length(io) catch |e|116 } else if (size != coverage_file_len) {
110 panic("failed to stat coverage file '{s}': {t}", .{ &file_name, e });117 panic(
111 if (size != coverage_file_len) panic(
112 "incompatible existing coverage file '{s}' (differing lengths: {} != {})",118 "incompatible existing coverage file '{s}' (differing lengths: {} != {})",
113 .{ &file_name, size, coverage_file_len },119 .{ &file_name, size, coverage_file_len },
114 );120 );
121 } else if (maybe_populate) {
122 coverage_file.lock(io, .shared) catch |e|
123 panic("failed to demote lock for coverage file '{s}': {t}", .{ &file_name, e });
115 }124 }
116125
117 var io_map = coverage_file.createMemoryMap(io, .{ .len = coverage_file_len }) catch |e|126 var io_map = coverage_file.createMemoryMap(io, .{ .len = coverage_file_len }) catch |e|
...@@ -228,6 +237,13 @@ const Executable = struct {...@@ -228,6 +237,13 @@ const Executable = struct {
228 return self;237 return self;
229 }238 }
230239
240 /// Asserts `buf[0..2]` is "in"
241 fn inputFileName(buf: *[10]u8, i: u32) []u8 {
242 assert(buf[0..2].* == "in".*);
243 const hex = std.fmt.bufPrint(buf[2..], "{x}", .{i}) catch unreachable;
244 return buf[0 .. 2 + hex.len];
245 }
246
231 pub fn pcBitsetIterator(self: Executable) PcBitsetIterator {247 pub fn pcBitsetIterator(self: Executable) PcBitsetIterator {
232 return .{ .pc_counters = self.pc_counters };248 return .{ .pc_counters = self.pc_counters };
233 }249 }
...@@ -263,32 +279,16 @@ const Executable = struct {...@@ -263,32 +279,16 @@ const Executable = struct {
263};279};
264280
265const Fuzzer = struct {281const Fuzzer = struct {
282 tests: []Test,
283 test_i: u32,
284 test_one: abi.TestOne,
285
266 // The default PRNG is not used here since going through `Random` can be very expensive286 // The default PRNG is not used here since going through `Random` can be very expensive
267 // since LLVM often fails to devirtualize and inline `fill`. Additionally, optimization287 // since LLVM often fails to devirtualize and inline `fill`. Additionally, optimization
268 // is simpler since integers are not serialized then deserialized in the random stream.288 // is simpler since integers are not serialized then deserialized in the random stream.
269 //289 //
270 // This acounts for a 30% performance improvement with LLVM 21.290 // This acounts for a 30% performance improvement with LLVM 21.
271 xoshiro: std.Random.Xoshiro256,291 xoshiro: std.Random.Xoshiro256,
272 test_one: abi.TestOne,
273
274 seen_pcs: []usize,
275 bests: struct {
276 len: u32,
277 quality_buf: []Input.Best,
278 input_buf: []Input.Best.Map,
279 },
280 seen_uids: std.ArrayHashMapUnmanaged(Uid, struct {
281 slices: union {
282 ints: std.ArrayList([]u64),
283 bytes: std.ArrayList(Input.Data.Bytes),
284 },
285 }, Uid.hashmap_ctx, false),
286
287 /// Past inputs leading to new pc or uid hits.
288 /// These are randomly mutated in round-robin fashion.
289 corpus: std.MultiArrayList(Input),
290 corpus_pos: Input.Index,
291
292 bytes_input: std.testing.Smith,292 bytes_input: std.testing.Smith,
293 input_builder: Input.Builder,293 input_builder: Input.Builder,
294 /// Number of data calls the current run has made.294 /// Number of data calls the current run has made.
...@@ -319,13 +319,140 @@ const Fuzzer = struct {...@@ -319,13 +319,140 @@ const Fuzzer = struct {
319 },319 },
320320
321 /// As values are provided to the Smith, they are appended to this. If the test321 /// As values are provided to the Smith, they are appended to this. If the test
322 /// crashes, this can be recovered and used to obtain the crashing values.322 /// crashes, this can be recovered and used to obtain the crashing values. It is
323 /// also used to rerun fresh inputs.
323 mmap_input: MemoryMappedInput,324 mmap_input: MemoryMappedInput,
324 /// Filesystem directory containing found inputs for future runs325 /// The instance is responsible for updating the filesystem corpus.
325 corpus_dir: Io.Dir,326 ///
326 /// The values in `corpus` past this point directly correspond to what is found327 /// Since different fuzzer instances can be out of sync due to finding inputs before recieving
327 /// in `corpus_dir`.328 /// others and nondeterministic tests, the filesystem is only based off the first instance.
328 start_corpus_dir: u32,329 main_instance: bool,
330
331 const Test = struct {
332 const NameHash = u64;
333 const dirname_len = @sizeOf(NameHash) * 2;
334
335 seen_pcs: []usize,
336 bests: struct {
337 len: u32,
338 quality_buf: []Input.Best,
339 input_buf: []Input.Best.Map,
340 },
341 seen_uids: std.ArrayHashMapUnmanaged(Uid, struct {
342 slices: union {
343 ints: std.ArrayList([]u64),
344 bytes: std.ArrayList(Input.Data.Bytes),
345 },
346 }, Uid.hashmap_ctx, false),
347
348 /// Past inputs leading to new pc or uid hits.
349 /// These are randomly mutated in round-robin fashion.
350 corpus: std.MultiArrayList(Input),
351 corpus_pos: Input.Index,
352 /// If this is `math.maxInt(u32)` (reserved), it means the corpus has not been loaded from
353 /// the filesystem.
354 ///
355 /// If `main_instance` is set, the values in `corpus` after this are mirrored to the
356 /// filesystem.
357 start_mut_corpus: u32,
358 dirname: [dirname_len]u8,
359 /// Ensures only one fuzzer writes to the corpus.
360 ///
361 /// Undefined if this is not the main instance.
362 lock_file: Io.File,
363 received: Received,
364
365 limit: ?u64,
366 /// A batch is the amount of cycles approximently for one second of runtime.
367 ///
368 /// This value is set to the previous batch's runs per second or run limit.
369 batch_cycles: u32,
370 batches: u64,
371 batches_since_find: u64,
372 seen_pc_count: u32,
373 };
374
375 const Received = struct {
376 state: State,
377 /// Stream of inputs with each prefixed with a u32 length
378 inputs: std.ArrayList(u8),
379
380 pub const empty: Received = .{
381 .state = .{
382 .pending = false,
383 .read_lock = false,
384 .write_lock = false,
385 },
386 .inputs = .empty,
387 };
388
389 pub const State = packed struct(u32) {
390 pending: bool,
391 read_lock: bool,
392 /// If set in conjucation with `read_lock`, then there is a waiter on state.
393 write_lock: bool,
394 _: u29 = 0,
395
396 pub fn hasPending(s: *State) bool {
397 return @atomicLoad(State, s, .monotonic).pending;
398 }
399
400 pub fn startReadIfPending(s: *State) bool {
401 return @cmpxchgWeak(
402 State,
403 s,
404 .{ .pending = true, .read_lock = false, .write_lock = false },
405 .{ .pending = true, .read_lock = true, .write_lock = false },
406 .acquire,
407 .monotonic,
408 ) == null;
409 }
410
411 pub fn finishRead(s: *State) void {
412 const prev = @atomicRmw(State, s, .And, .{
413 .pending = false,
414 .read_lock = false,
415 .write_lock = true,
416 }, .release);
417 assert(prev.read_lock);
418 if (prev.write_lock) {
419 abi.runner_futex_wake(@ptrCast(s), 1);
420 }
421 }
422
423 /// Returns if cancelation is requested.
424 pub fn startWrite(s: *State) bool {
425 var prev = @atomicRmw(State, s, .Or, .{
426 .pending = false,
427 .read_lock = false,
428 .write_lock = true,
429 }, .acquire);
430 assert(!prev.write_lock);
431 while (prev.read_lock) {
432 if (abi.runner_futex_wait(@ptrCast(s), @bitCast(prev))) {
433 s.* = undefined; // fuzzer is exiting
434 return true;
435 }
436 // Still need `.acquire` ordering so @atomicRmw is necessary
437 prev = @atomicRmw(State, s, .Or, .{
438 .pending = false,
439 .read_lock = false,
440 .write_lock = false,
441 }, .acquire);
442 assert(prev.write_lock);
443 }
444 return false;
445 }
446
447 pub fn finishWrite(s: *State) void {
448 @atomicStore(State, s, .{
449 .pending = true,
450 .read_lock = false,
451 .write_lock = false,
452 }, .release);
453 }
454 };
455 };
329456
330 const SeqCopy = union {457 const SeqCopy = union {
331 order_i: u32,458 order_i: u32,
...@@ -480,7 +607,9 @@ const Fuzzer = struct {...@@ -480,7 +607,9 @@ const Fuzzer = struct {
480 .total_ints = 0,607 .total_ints = 0,
481 .total_bytes = 0,608 .total_bytes = 0,
482 .weighted_len = 0,609 .weighted_len = 0,
483 .smithed_len = 4,610 // The - 1 is because we check that `smithed_len` does not overflow a u32;
611 // however, `MemoryMappedInput` allows up to `1 << 32`.
612 .smithed_len = @sizeOf(abi.MmapInputHeader) - 1,
484 };613 };
485614
486 pub fn addInt(b: *Builder, uid: Uid, int: u64) void {615 pub fn addInt(b: *Builder, uid: Uid, int: u64) void {
...@@ -591,7 +720,7 @@ const Fuzzer = struct {...@@ -591,7 +720,7 @@ const Fuzzer = struct {
591 b.total_ints = 0;720 b.total_ints = 0;
592 b.total_bytes = 0;721 b.total_bytes = 0;
593 b.weighted_len = 0;722 b.weighted_len = 0;
594 b.smithed_len = 4;723 b.smithed_len = Builder.init.smithed_len;
595 return input;724 return input;
596 }725 }
597726
...@@ -604,31 +733,128 @@ const Fuzzer = struct {...@@ -604,31 +733,128 @@ const Fuzzer = struct {
604 }733 }
605 }734 }
606 b.uid_slices.clearRetainingCapacity();735 b.uid_slices.clearRetainingCapacity();
736 b.bytes_table.clearRetainingCapacity();
607 b.total_ints = 0;737 b.total_ints = 0;
608 b.total_bytes = 0;738 b.total_bytes = 0;
609 b.weighted_len = 0;739 b.weighted_len = 0;
610 b.smithed_len = 4;740 b.smithed_len = Builder.init.smithed_len;
741 }
742
743 /// Asserts the structure is reset
744 pub fn deinit(b: *Builder) void {
745 assert(b.uid_slices.entries.len == 0);
746 b.uid_slices.deinit(gpa);
747 b.bytes_table.deinit(gpa);
748 b.* = undefined;
611 }749 }
612 };750 };
613 };751 };
614752
615 pub fn init() Fuzzer {753 pub fn init(n_tests: u32, seed: u64, instance_id: u32, limit: ?u64) Fuzzer {
616 if (exec.pc_counters.len > math.maxInt(u32)) @panic("too many pcs");754 const pcs = exec.pc_counters.len;
617 const f: Fuzzer = .{755 if (pcs > math.maxInt(u32)) @panic("too many pcs");
618 .xoshiro = .init(0),756
619 .test_one = undefined,757 const mmap_input = map: {
758 // Find a free input file. `instance_id` should give one that is not in use;
759 // however, this may not be the case if there are multiple libfuzzers running.
760 var input_i = instance_id;
761 const input_f = while (true) {
762 var name_buf: [10]u8 = undefined;
763 name_buf[0..2].* = "in".*;
764 const hex = std.fmt.bufPrint(name_buf[2..], "{x}", .{input_i}) catch unreachable;
765 const name = name_buf[0 .. 2 + hex.len];
766
767 if (exec.cache_f.createFile(io, name, .{
768 .read = true,
769 .truncate = false,
770 .lock = .exclusive,
771 .lock_nonblocking = true,
772 })) |f| {
773 break f;
774 } else |e| switch (e) {
775 // To ensure no input file is unused to avoid the number of input files
776 // growing indefinitely across runs, they are linearly searched through.
777 //
778 // This could be avoided by creating a shared file holding the current number
779 // of input files in use; however, using multiple libfuzzers is uncommon and
780 // there should not be that many input files to search through anyways.
781 error.WouldBlock => input_i += 1,
782 else => panic("failed to create file '{s}': {t}", .{ name, e }),
783 }
784 };
785 break :map MemoryMappedInput.init(input_f, instance_id, input_i);
786 };
620787
621 .seen_pcs = gpa.alloc(usize, bitsetUsizes(exec.pc_counters.len)) catch @panic("OOM"),788 const tests = gpa.alloc(Test, n_tests) catch @panic("OOM");
622 .bests = .{789 const seen_pcs_len = bitsetUsizes(pcs);
623 .len = 0,790 var seen_pcs_bufs = gpa.alloc(usize, seen_pcs_len * n_tests) catch @panic("OOM");
624 .quality_buf = gpa.alloc(Input.Best, exec.pc_counters.len) catch @panic("OOM"),791 var best_quality_bufs = gpa.alloc(Input.Best, pcs * n_tests) catch @panic("OOM");
625 .input_buf = gpa.alloc(Input.Best.Map, exec.pc_counters.len) catch @panic("OOM"),792 var best_input_bufs = gpa.alloc(Input.Best.Map, pcs * n_tests) catch @panic("OOM");
626 },793 @memset(seen_pcs_bufs, 0);
627 .seen_uids = .empty,794 for (0.., tests) |i, *t| {
795 const name = abi.runner_test_name(@intCast(i)).toSlice();
796 // A hash is used as the dirname instead of the actual test name since the test name
797 // may be not allowed by the filesystem or have a special meaning (e.g. absolute /
798 // relative paths).
799 const dirname = std.fmt.hex(std.hash.Wyhash.hash(0, name));
800
801 const lock_file = file: {
802 if (instance_id != 0) break :file undefined;
803
804 exec.cache_f.createDir(io, &dirname, .default_dir) catch |e| switch (e) {
805 error.PathAlreadyExists => {},
806 else => panic("failed to create directory '{s}': {t}", .{ &dirname, e }),
807 };
808
809 var cname: CorpusFileName = .fromTest(dirname);
810 const lock_name = cname.syncLockName();
811 break :file exec.cache_f.createFile(io, lock_name, .{
812 .truncate = false,
813 .lock = .exclusive,
814 .lock_nonblocking = true,
815 }) catch |e| switch (e) {
816 error.WouldBlock => panic("corpus of '{s}' is in use by another fuzzer", .{name}),
817 else => panic("failed to create file '{s}': {t}", .{ lock_name, e }),
818 };
819 };
820
821 t.* = .{
822 .seen_pcs = seen_pcs_bufs[0..seen_pcs_len],
823 .bests = .{
824 .len = 0,
825 .quality_buf = best_quality_bufs[0..pcs],
826 .input_buf = best_input_bufs[0..pcs],
827 },
828 .seen_uids = .empty,
829
830 .corpus = .empty,
831 .corpus_pos = @enumFromInt(0),
832 .start_mut_corpus = math.maxInt(u32),
833 .dirname = dirname,
834 .lock_file = lock_file,
835 .received = .empty,
836
837 .limit = limit,
838 .batch_cycles = 1,
839 .batches = 0,
840 .batches_since_find = 0,
841 .seen_pc_count = 0,
842 };
843 t.corpus.append(gpa, .none) catch @panic("OOM"); // Also ensures the corpus is not empty
844 seen_pcs_bufs = seen_pcs_bufs[seen_pcs_len..];
845 best_quality_bufs = best_quality_bufs[pcs..];
846 best_input_bufs = best_input_bufs[pcs..];
847 }
848 assert(seen_pcs_bufs.len == 0);
849 assert(best_quality_bufs.len == 0);
850 assert(best_input_bufs.len == 0);
628851
629 .corpus = .empty,852 return .{
630 .corpus_pos = undefined,853 .tests = tests,
854 .test_i = undefined,
855 .test_one = undefined,
631856
857 .xoshiro = .init(seed),
632 .bytes_input = undefined,858 .bytes_input = undefined,
633 .input_builder = .init,859 .input_builder = .init,
634 .req_values = undefined,860 .req_values = undefined,
...@@ -636,97 +862,144 @@ const Fuzzer = struct {...@@ -636,97 +862,144 @@ const Fuzzer = struct {
636 .uid_data_i = .empty,862 .uid_data_i = .empty,
637 .mut_data = undefined,863 .mut_data = undefined,
638864
639 .mmap_input = undefined,865 .mmap_input = mmap_input,
640 .corpus_dir = undefined,866 .main_instance = instance_id == 0,
641 .start_corpus_dir = undefined,
642 };867 };
643 @memset(f.seen_pcs, 0);
644 return f;
645 }868 }
646869
647 /// May only be called after `f.setTest` has been called870 pub fn deinit(f: *Fuzzer) void {
648 pub fn reset(f: *Fuzzer) void {871 const pcs = exec.pc_counters.len;
649 f.test_one = undefined;872 const n_tests = f.tests.len;
650873 gpa.free(f.tests[0].seen_pcs.ptr[0 .. bitsetUsizes(pcs) * n_tests]);
651 @memset(f.seen_pcs, 0);874 gpa.free(f.tests[0].bests.quality_buf.ptr[0 .. pcs * n_tests]);
652 f.bests.len = 0;875 gpa.free(f.tests[0].bests.input_buf.ptr[0 .. pcs * n_tests]);
653 @memset(f.bests.quality_buf, undefined);876 for (f.tests) |*t| {
654 @memset(f.bests.input_buf, undefined);877 const seen_uids = t.seen_uids.entries.slice();
655 for (f.seen_uids.keys(), f.seen_uids.values()) |uid, *u| {878 for (seen_uids.items(.key), seen_uids.items(.value)) |uid, *data| {
656 switch (uid.kind) {879 switch (uid.kind) {
657 .int => u.slices.ints.deinit(gpa),880 .int => data.slices.ints.deinit(gpa),
658 .bytes => u.slices.bytes.deinit(gpa),881 .bytes => data.slices.bytes.deinit(gpa),
882 }
883 }
884 t.seen_uids.deinit(gpa);
885 const corpus = t.corpus.slice();
886 // The first input is `Input.none` and so is skipped as `deinit` is illegal.
887 for (1..corpus.len) |i| {
888 var in = corpus.get(i);
889 in.deinit();
659 }890 }
891 if (f.main_instance) {
892 t.lock_file.close(io);
893 }
894 t.received.inputs.deinit(gpa);
660 }895 }
661 f.seen_uids.clearRetainingCapacity();896 gpa.free(f.tests);
897 f.input_builder.deinit();
898 f.mmap_input.deinit();
899 f.* = undefined;
900 }
662901
663 f.corpus.clearRetainingCapacity();902 pub fn ensureCorpusLoaded(f: *Fuzzer) void {
664 f.corpus_pos = undefined;903 const t = &f.tests[f.test_i];
904 if (t.start_mut_corpus != math.maxInt(u32)) return;
665905
666 f.uid_data_i.clearRetainingCapacity();906 const start_mut: u32 = @intCast(t.corpus.len);
907 if (!f.main_instance) {
908 // Inputs can be culled as added since filesystem synchronacy is not required
909 t.start_mut_corpus = start_mut;
910 }
667911
668 f.mmap_input.deinit();912 read_corpus: {
669 f.corpus_dir.close(io);913 var cname: CorpusFileName = .fromTest(t.dirname);
670 f.start_corpus_dir = undefined;
671 }
672914
673 pub fn setTest(f: *Fuzzer, test_one: abi.TestOne, unit_test_name: []const u8) void {915 const readlock_name = cname.readLockName();
674 f.test_one = test_one;916 const readlock_file = exec.cache_f.createFile(io, readlock_name, .{
675 f.corpus_dir = exec.cache_f.createDirPathOpen(io, unit_test_name, .{}) catch |e|
676 panic("failed to open directory '{s}': {t}", .{ unit_test_name, e });
677 f.mmap_input = map: {
678 const input = f.corpus_dir.createFile(io, "in", .{
679 .read = true,
680 .truncate = false,917 .truncate = false,
681 // In case any other fuzz tests are running under the same test name,918 .lock = .shared,
682 // the input file is exclusively locked to ensures only one proceeds.
683 .lock = .exclusive,
684 .lock_nonblocking = true,
685 }) catch |e| switch (e) {919 }) catch |e| switch (e) {
686 error.WouldBlock => @panic("input file 'in' is in use by another fuzzing process"),920 // FileNotFound means the corpus directory does not exist, which means it is empty
687 else => panic("failed to create input file 'in': {t}", .{e}),921 error.FileNotFound => break :read_corpus,
922 else => panic("failed to open '{s}': {t}", .{ readlock_name, e }),
688 };923 };
924 defer readlock_file.close(io);
925
926 var input_buf: std.ArrayList(u8) = .empty;
927 defer input_buf.deinit(gpa);
928 var i: u32 = 0;
929 while (true) {
930 const name = cname.inputName(i);
931 const input_file = exec.cache_f.openFile(io, name, .{}) catch |e| switch (e) {
932 error.FileNotFound => break,
933 else => panic("failed to open input file '{s}': {t}", .{ name, e }),
934 };
935
936 const len = input_file.length(io) catch |e|
937 panic("failed to get length of '{s}': {t}", .{ name, e });
938 const ulen = math.cast(usize, len) orelse @panic("OOM");
939 input_buf.resize(gpa, ulen) catch @panic("OOM");
940
941 var r = input_file.readerStreaming(io, &.{});
942 r.interface.readSliceAll(input_buf.items) catch |e| switch (e) {
943 error.ReadFailed => panic(
944 "failed to read from input file '{s}': {t}",
945 .{ name, r.err.? },
946 ),
947 error.EndOfStream => panic(
948 "input file '{s}' ended before its reported length",
949 .{name},
950 ),
951 };
952 f.newInputExternal(input_buf.items);
689953
690 var size = input.length(io) catch |e| panic("failed to stat input file 'in': {t}", .{e});954 i += 1; // Cannot overflow due to corpus 32-bit size limit
691 if (size < std.heap.page_size_max) {
692 size = std.heap.page_size_max;
693 input.setLength(io, size) catch |e| panic("failed to resize input file 'in': {t}", .{e});
694 }955 }
956 }
695957
696 break :map MemoryMappedInput.init(input, size) catch |e|958 if (f.main_instance) {
697 panic("failed to memmap input file 'in': {t}", .{e});959 t.start_mut_corpus = start_mut;
698 };
699960
700 // Perform a dry-run of the stored input in case it might reproduce a crash.961 // Cull old inputs
701 const len = mem.readInt(u32, f.mmap_input.mmap.memory[0..4], .little);962 const ref = t.corpus.items(.ref);
702 if (len < f.mmap_input.mmap.memory[4..].len) {963 var i: usize = t.start_mut_corpus;
703 f.mmap_input.len = len;964 while (i < t.corpus.len) {
704 _ = f.runBytes(f.mmap_input.inputSlice(), .bytes_dry);965 if (ref[i].best_i_len == 0) {
705 f.mmap_input.clearRetainingCapacity();966 f.removeInput(@enumFromInt(i));
967 } else {
968 i += 1;
969 }
970 }
706 }971 }
972
973 t.corpus_pos = @enumFromInt(0);
707 }974 }
708975
709 pub fn loadCorpus(f: *Fuzzer) void {976 const CorpusFileName = struct {
710 f.corpus_pos = @enumFromInt(f.corpus.len);977 buf: [Test.dirname_len + 9]u8,
711 f.corpus.append(gpa, .none) catch @panic("OOM"); // Also ensures the corpus is not empty978
712 f.start_corpus_dir = @intCast(f.corpus.len);979 pub fn fromTest(dirname: [Test.dirname_len]u8) CorpusFileName {
713 while (true) {980 var n: CorpusFileName = undefined;
714 var name_buf: [8]u8 = undefined;981 n.buf[0..dirname.len].* = dirname;
715 const name = f.corpusFileName(&name_buf, @enumFromInt(f.corpus.len));982 n.buf[dirname.len] = Io.Dir.path.sep;
716 const bytes = f.corpus_dir.readFileAlloc(io, name, gpa, .unlimited) catch |e| switch (e) {983 return n;
717 error.FileNotFound => break,
718 else => panic("failed to read corpus file '{s}': {t}", .{ name, e }),
719 };
720 defer gpa.free(bytes);
721 f.newInputExternal(bytes);
722 }984 }
723 f.corpus_pos = @enumFromInt(0);
724 }
725985
726 fn corpusFileName(f: *Fuzzer, buf: *[8]u8, i: Input.Index) []u8 {986 pub fn readLockName(n: *CorpusFileName) []u8 {
727 const dir_i = @intFromEnum(i) - f.start_corpus_dir;987 const basename = "readlock";
728 return std.fmt.bufPrint(buf, "{x}", .{dir_i}) catch unreachable;988 n.buf[Test.dirname_len + 1 ..][0..basename.len].* = basename.*;
729 }989 return n.buf[0 .. Test.dirname_len + 1 + basename.len];
990 }
991
992 pub fn syncLockName(n: *CorpusFileName) []u8 {
993 const basename = "synclock";
994 n.buf[Test.dirname_len + 1 ..][0..basename.len].* = basename.*;
995 return n.buf[0 .. Test.dirname_len + 1 + basename.len];
996 }
997
998 pub fn inputName(n: *CorpusFileName, i: u32) []u8 {
999 const hex = std.fmt.bufPrint(n.buf[Test.dirname_len + 1 ..][0..8], "{x}", .{i}) catch unreachable;
1000 return n.buf[0 .. Test.dirname_len + 1 + hex.len];
1001 }
1002 };
7301003
731 fn rngInt(f: *Fuzzer, T: type) T {1004 fn rngInt(f: *Fuzzer, T: type) T {
732 comptime assert(@bitSizeOf(T) <= 64);1005 comptime assert(@bitSizeOf(T) <= 64);
...@@ -749,13 +1022,14 @@ const Fuzzer = struct {...@@ -749,13 +1022,14 @@ const Fuzzer = struct {
749 };1022 };
7501023
751 fn isFresh(f: *Fuzzer) bool {1024 fn isFresh(f: *Fuzzer) bool {
1025 const t = &f.tests[f.test_i];
752 // Store as a bool instead of returning immediately to aid optimizations1026 // Store as a bool instead of returning immediately to aid optimizations
753 // by reducing branching since a fresh input is the unlikely case.1027 // by reducing branching since a fresh input is the unlikely case.
754 var fresh: bool = false;1028 var fresh: bool = false;
7551029
756 var n_pcs: u32 = 0;1030 var n_pcs: u32 = 0;
757 var hit_pcs = exec.pcBitsetIterator();1031 var hit_pcs = exec.pcBitsetIterator();
758 for (f.seen_pcs) |seen| {1032 for (t.seen_pcs) |seen| {
759 const hits = hit_pcs.next();1033 const hits = hit_pcs.next();
760 fresh |= hits & ~seen != 0;1034 fresh |= hits & ~seen != 0;
761 n_pcs += @popCount(hits);1035 n_pcs += @popCount(hits);
...@@ -768,7 +1042,7 @@ const Fuzzer = struct {...@@ -768,7 +1042,7 @@ const Fuzzer = struct {
768 .bytes = f.req_bytes,1042 .bytes = f.req_bytes,
769 },1043 },
770 };1044 };
771 for (f.bests.quality_buf[0..f.bests.len]) |best| {1045 for (t.bests.quality_buf[0..t.bests.len]) |best| {
772 if (exec.pc_counters[best.pc] == 0) continue;1046 if (exec.pc_counters[best.pc] == 0) continue;
773 fresh |= quality.betterLess(best.min) | quality.betterMore(best.max);1047 fresh |= quality.betterLess(best.min) | quality.betterMore(best.max);
774 }1048 }
...@@ -776,12 +1050,15 @@ const Fuzzer = struct {...@@ -776,12 +1050,15 @@ const Fuzzer = struct {
776 return fresh;1050 return fresh;
777 }1051 }
7781052
1053 /// It is the callee's responsibility to reset the corpus pos
1054 ///
779 /// Returns if `error.SkipZigTest` was indicated1055 /// Returns if `error.SkipZigTest` was indicated
780 fn runBytes(f: *Fuzzer, bytes: []const u8, mode: Input.Index) bool {1056 fn runBytes(f: *Fuzzer, bytes: []const u8, mode: Input.Index) bool {
781 assert(mode == .bytes_dry or mode == .bytes_fresh);1057 assert(mode == .bytes_dry or mode == .bytes_fresh);
7821058
783 f.bytes_input = .{ .in = bytes };1059 f.bytes_input = .{ .in = bytes };
784 f.corpus_pos = mode;1060 f.tests[f.test_i].corpus_pos = mode;
1061 defer f.tests[f.test_i].corpus_pos = undefined;
785 return f.run(0); // 0 since `f.uid_data` is unused1062 return f.run(0); // 0 since `f.uid_data` is unused
786 }1063 }
7871064
...@@ -791,89 +1068,112 @@ const Fuzzer = struct {...@@ -791,89 +1068,112 @@ const Fuzzer = struct {
791 exec.shared_seen_pcs[@sizeOf(abi.SeenPcsHeader)..].ptr,1068 exec.shared_seen_pcs[@sizeOf(abi.SeenPcsHeader)..].ptr,
792 );1069 );
7931070
1071 const t = &f.tests[f.test_i];
794 var hit_pcs = exec.pcBitsetIterator();1072 var hit_pcs = exec.pcBitsetIterator();
795 for (f.seen_pcs, shared_seen_pcs) |*seen, *shared_seen| {1073 for (t.seen_pcs, shared_seen_pcs) |*seen, *shared_seen| {
796 const new = hit_pcs.next() & ~seen.*;1074 const new = hit_pcs.next() & ~seen.*;
797 if (new != 0) {1075 if (new != 0) {
798 seen.* |= new;1076 seen.* |= new;
799 _ = @atomicRmw(usize, shared_seen, .Or, new, .monotonic);1077 _ = @atomicRmw(usize, shared_seen, .Or, new, .monotonic);
1078 t.seen_pc_count += @popCount(new);
800 }1079 }
801 }1080 }
802 }1081 }
8031082
804 fn removeBest(f: *Fuzzer, i: Input.Index, best_i: u32, modify_fs_corpus: bool) void {1083 fn removeBest(f: *Fuzzer, i: Input.Index, best_i: u32) void {
805 const ref = &f.corpus.items(.ref)[@intFromEnum(i)];1084 const t = &f.tests[f.test_i];
1085 const ref = &t.corpus.items(.ref)[@intFromEnum(i)];
806 const list_i = mem.indexOfScalar(u32, ref.best_i_buf[0..ref.best_i_len], best_i).?;1086 const list_i = mem.indexOfScalar(u32, ref.best_i_buf[0..ref.best_i_len], best_i).?;
807 ref.best_i_len -= 1;1087 ref.best_i_len -= 1;
808 ref.best_i_buf[list_i] = ref.best_i_buf[ref.best_i_len];1088 ref.best_i_buf[list_i] = ref.best_i_buf[ref.best_i_len];
8091089
810 if (ref.best_i_len == 0 and @intFromEnum(i) >= f.start_corpus_dir and modify_fs_corpus) {1090 if (ref.best_i_len == 0 and @intFromEnum(i) >= t.start_mut_corpus) {
811 // The input is no longer valuable, so remove it.1091 // The input is no longer valuable, so remove it.
812 var removed_input = f.corpus.get(@intFromEnum(i));1092 f.removeInput(i);
813 for (1093 }
814 removed_input.data.uid_slices.keys(),1094 }
815 removed_input.data.uid_slices.values(),1095
816 removed_input.seen_uid_i,1096 fn removeInput(f: *Fuzzer, i: Input.Index) void {
817 ) |uid, slice, seen_uid_i| {1097 const t = &f.tests[f.test_i];
818 switch (uid.kind) {1098 const ref = &t.corpus.items(.ref)[@intFromEnum(i)];
819 .int => {1099 assert(ref.best_i_len == 0 and @intFromEnum(i) >= t.start_mut_corpus);
820 const seen_ints = &f.seen_uids.values()[seen_uid_i].slices.ints;1100
821 const removed_ints = removed_input.data.ints[slice.base..][0..slice.len];1101 var removed_input = t.corpus.get(@intFromEnum(i));
822 _ = seen_ints.swapRemove(for (0.., seen_ints.items) |idx, ints| {1102 for (
823 if (removed_ints.ptr == ints.ptr) {1103 removed_input.data.uid_slices.keys(),
824 assert(removed_ints.len == ints.len);1104 removed_input.data.uid_slices.values(),
825 break idx;1105 removed_input.seen_uid_i,
826 }1106 ) |uid, slice, seen_uid_i| {
827 } else unreachable);1107 switch (uid.kind) {
828 },1108 .int => {
829 .bytes => {1109 const seen_ints = &t.seen_uids.values()[seen_uid_i].slices.ints;
830 const seen_bytes = &f.seen_uids.values()[seen_uid_i].slices.bytes;1110 const removed_ints = removed_input.data.ints[slice.base..][0..slice.len];
831 const removed_bytes: Input.Data.Bytes = .{1111 _ = seen_ints.swapRemove(for (0.., seen_ints.items) |idx, ints| {
832 .entries = removed_input.data.bytes.entries[slice.base..][0..slice.len],1112 if (removed_ints.ptr == ints.ptr) {
833 .table = removed_input.data.bytes.table,1113 assert(removed_ints.len == ints.len);
834 };1114 break idx;
835 _ = seen_bytes.swapRemove(for (0.., seen_bytes.items) |idx, bytes| {1115 }
836 if (removed_bytes.entries.ptr == bytes.entries.ptr) {1116 } else unreachable);
837 assert(removed_bytes.entries.len == bytes.entries.len);1117 },
838 assert(removed_bytes.table.ptr == bytes.table.ptr);1118 .bytes => {
839 assert(removed_bytes.table.len == bytes.table.len);1119 const seen_bytes = &t.seen_uids.values()[seen_uid_i].slices.bytes;
840 break idx;1120 const removed_bytes: Input.Data.Bytes = .{
841 }1121 .entries = removed_input.data.bytes.entries[slice.base..][0..slice.len],
842 } else unreachable);1122 .table = removed_input.data.bytes.table,
843 },1123 };
844 }1124 _ = seen_bytes.swapRemove(for (0.., seen_bytes.items) |idx, bytes| {
1125 if (removed_bytes.entries.ptr == bytes.entries.ptr) {
1126 assert(removed_bytes.entries.len == bytes.entries.len);
1127 assert(removed_bytes.table.ptr == bytes.table.ptr);
1128 assert(removed_bytes.table.len == bytes.table.len);
1129 break idx;
1130 }
1131 } else unreachable);
1132 },
845 }1133 }
846 removed_input.deinit();1134 }
847 f.corpus.swapRemove(@intFromEnum(i));1135 removed_input.deinit();
1136 t.corpus.swapRemove(@intFromEnum(i));
8481137
849 var removed_name_buf: [8]u8 = undefined;1138 if (@intFromEnum(i) != t.corpus.len) {
850 const removed_name = f.corpusFileName(&removed_name_buf, i);1139 // The last item was moved so its refs need updated.
1140 // `ref` can be reused since it was a swap remove.
1141 for (ref.best_i_buf[0..ref.best_i_len]) |update_pc_i| {
1142 const best = &t.bests.input_buf[update_pc_i];
1143 assert(@intFromEnum(best.min) == t.corpus.len or
1144 @intFromEnum(best.max) == t.corpus.len);
8511145
852 if (@intFromEnum(i) == f.corpus.len) {1146 if (@intFromEnum(best.min) == t.corpus.len) best.min = i;
853 f.corpus_dir.deleteFile(io, removed_name) catch |e| panic(1147 if (@intFromEnum(best.max) == t.corpus.len) best.max = i;
854 "failed to remove corpus file '{s}': {t}",
855 .{ removed_name, e },
856 );
857 return; // No item moved so no refs to update
858 }1148 }
1149 }
8591150
860 var swapped_name_buf: [8]u8 = undefined;1151 if (!f.main_instance) return;
861 const swapped_name = f.corpusFileName(&swapped_name_buf, @enumFromInt(f.corpus.len));1152
1153 var removed_cname: CorpusFileName = .fromTest(t.dirname);
1154 // Temporarily use removed_name to construct the path to the lock
1155 const readlock_name = removed_cname.readLockName();
1156 const readlock_file = exec.cache_f.createFile(io, readlock_name, .{
1157 .truncate = false,
1158 .lock = .exclusive,
1159 }) catch |e| panic("failed to open '{s}': {t}", .{ readlock_name, e });
1160 defer readlock_file.close(io);
1161
1162 const removed_name = removed_cname.inputName(@intFromEnum(i) - t.start_mut_corpus);
1163 if (@intFromEnum(i) == t.corpus.len) {
1164 exec.cache_f.deleteFile(io, removed_name) catch |e| panic(
1165 "failed to remove corpus file '{s}': {t}",
1166 .{ removed_name, e },
1167 );
1168 } else {
1169 var swapped_cname: CorpusFileName = .fromTest(t.dirname);
1170 const swapped_i: u32 = @intCast(t.corpus.len);
1171 const swapped_name = swapped_cname.inputName(swapped_i - t.start_mut_corpus);
8621172
863 f.corpus_dir.rename(swapped_name, f.corpus_dir, removed_name, io) catch |e| panic(1173 exec.cache_f.rename(swapped_name, exec.cache_f, removed_name, io) catch |e| panic(
864 "failed to rename corpus file '{s}' to '{s}': {t}",1174 "failed to rename corpus file '{s}' to '{s}': {t}",
865 .{ swapped_name, removed_name, e },1175 .{ swapped_name, removed_name, e },
866 );1176 );
867
868 // Update refrences. `ref` can be reused since it was a swap remove
869 for (ref.best_i_buf[0..ref.best_i_len]) |update_pc_i| {
870 const best = &f.bests.input_buf[update_pc_i];
871 assert(@intFromEnum(best.min) == f.corpus.len or
872 @intFromEnum(best.max) == f.corpus.len);
873
874 if (@intFromEnum(best.min) == f.corpus.len) best.min = i;
875 if (@intFromEnum(best.max) == f.corpus.len) best.max = i;
876 }
877 }1177 }
878 }1178 }
8791179
...@@ -881,51 +1181,30 @@ const Fuzzer = struct {...@@ -881,51 +1181,30 @@ const Fuzzer = struct {
881 // All inputs including the corpus are required to go through the memory1181 // All inputs including the corpus are required to go through the memory
882 // mapped input in case they cause a crash so they can be identified.1182 // mapped input in case they cause a crash so they can be identified.
883 f.mmap_input.appendSlice(bytes);1183 f.mmap_input.appendSlice(bytes);
884 f.newInput(false);1184 f.newInput();
885 f.mmap_input.clearRetainingCapacity();1185 f.mmap_input.clearRetainingCapacity();
886 }1186 }
8871187
888 fn newInput(f: *Fuzzer, modify_fs_corpus: bool) void {1188 fn newInput(f: *Fuzzer) void {
1189 const t = &f.tests[f.test_i];
1190 const new_is_mut = t.start_mut_corpus != math.maxInt(u32);
1191 assert(new_is_mut == (t.corpus.len >= t.start_mut_corpus));
889 const bytes = f.mmap_input.inputSlice();1192 const bytes = f.mmap_input.inputSlice();
890 // `error.SkipZigTest` here can be from one of these causes:1193 // `error.SkipZigTest` here can be from one of these causes:
891 // * The test has changed and a previous corpus input is being used1194 // * A previous corpus input after the test has changed
892 // * An input provided by the test results in it1195 // * An input provided by the test
893 // * The test is non-deterministic1196 // * The test is non-deterministic
894 if (f.runBytes(bytes, .bytes_fresh) and1197 if (f.runBytes(bytes, .bytes_fresh) and
895 modify_fs_corpus // The input is not from the filesystem.1198 new_is_mut // The corpus must be mutable at this point for the input to be
896 // This is required to ensure the filesystem and process corpus are the same.1199 // omitted (i.e. test corpus inputs and filesystem inputs cannot be dropped)
897 ) {1200 ) {
898 f.input_builder.reset();1201 f.input_builder.reset();
899 f.corpus_pos = @enumFromInt(0);1202 t.corpus_pos = @enumFromInt(0);
900 return;1203 return;
901 }1204 }
1205
902 f.req_values = f.input_builder.total_ints + f.input_builder.total_bytes;1206 f.req_values = f.input_builder.total_ints + f.input_builder.total_bytes;
903 f.req_bytes = @intCast(f.input_builder.bytes_table.items.len);1207 f.req_bytes = @intCast(f.input_builder.bytes_table.items.len);
904 var input = f.input_builder.build();
905
906 f.uid_data_i.ensureTotalCapacity(gpa, input.data.uid_slices.entries.len) catch @panic("OOM");
907 for (
908 input.seen_uid_i,
909 input.data.uid_slices.keys(),
910 input.data.uid_slices.values(),
911 ) |*i, uid, slice| {
912 const gop = f.seen_uids.getOrPutValue(gpa, uid, switch (uid.kind) {
913 .int => .{ .slices = .{ .ints = .empty } },
914 .bytes => .{ .slices = .{ .bytes = .empty } },
915 }) catch @panic("OOM");
916 switch (uid.kind) {
917 .int => f.seen_uids.values()[gop.index].slices.ints.append(
918 gpa,
919 input.data.ints[slice.base..][0..slice.len],
920 ) catch @panic("OOM"),
921 .bytes => f.seen_uids.values()[gop.index].slices.bytes.append(gpa, .{
922 .entries = input.data.bytes.entries[slice.base..][0..slice.len],
923 .table = input.data.bytes.table,
924 }) catch @panic("OOM"),
925 }
926 i.* = @intCast(gop.index);
927 }
928
929 const quality: Input.Best.Quality = .{1208 const quality: Input.Best.Quality = .{
930 .n_pcs = n_pcs: {1209 .n_pcs = n_pcs: {
931 @setRuntimeSafety(builtin.mode == .Debug); // Necessary for vectorization1210 @setRuntimeSafety(builtin.mode == .Debug); // Necessary for vectorization
...@@ -942,7 +1221,7 @@ const Fuzzer = struct {...@@ -942,7 +1221,7 @@ const Fuzzer = struct {
942 };1221 };
9431222
944 var best_i_list: std.ArrayList(u32) = .empty;1223 var best_i_list: std.ArrayList(u32) = .empty;
945 for (0.., f.bests.quality_buf[0..f.bests.len]) |best_i, best| {1224 for (0.., t.bests.quality_buf[0..t.bests.len]) |best_i, best| {
946 if (exec.pc_counters[best.pc] == 0) continue;1225 if (exec.pc_counters[best.pc] == 0) continue;
9471226
948 const better_min = quality.betterLess(best.min);1227 const better_min = quality.betterLess(best.min);
...@@ -953,30 +1232,30 @@ const Fuzzer = struct {...@@ -953,30 +1232,30 @@ const Fuzzer = struct {
953 }1232 }
954 best_i_list.append(gpa, @intCast(best_i)) catch @panic("OOM");1233 best_i_list.append(gpa, @intCast(best_i)) catch @panic("OOM");
9551234
956 const map = &f.bests.input_buf[best_i];1235 const map = &t.bests.input_buf[best_i];
957 if (map.min != map.max) {1236 if (map.min != map.max) {
958 if (better_min) {1237 if (better_min) {
959 f.removeBest(map.min, @intCast(best_i), modify_fs_corpus);1238 f.removeBest(map.min, @intCast(best_i));
960 }1239 }
961 if (better_max) {1240 if (better_max) {
962 f.removeBest(map.max, @intCast(best_i), modify_fs_corpus);1241 f.removeBest(map.max, @intCast(best_i));
963 }1242 }
964 } else {1243 } else {
965 if (better_min and better_max) {1244 if (better_min and better_max) {
966 f.removeBest(map.min, @intCast(best_i), modify_fs_corpus);1245 f.removeBest(map.min, @intCast(best_i));
967 }1246 }
968 }1247 }
969 }1248 }
9701249
971 // Must come after the above since some inputs may be removed1250 // Must come after the above since some inputs may be removed
972 const input_i: Input.Index = @enumFromInt(f.corpus.len);1251 const input_i: Input.Index = @enumFromInt(t.corpus.len);
973 if (input_i == Input.Index.reserved_start) {1252 if (input_i == Input.Index.reserved_start) {
974 @panic("corpus size limit exceeded");1253 @panic("corpus size limit exceeded");
975 }1254 }
9761255
977 for (best_i_list.items) |i| {1256 for (best_i_list.items) |i| {
978 const best_qual = &f.bests.quality_buf[i];1257 const best_qual = &t.bests.quality_buf[i];
979 const best_map = &f.bests.input_buf[i];1258 const best_map = &t.bests.input_buf[i];
9801259
981 if (quality.betterLess(best_qual.min)) {1260 if (quality.betterLess(best_qual.min)) {
982 best_qual.min = quality;1261 best_qual.min = quality;
...@@ -994,42 +1273,74 @@ const Fuzzer = struct {...@@ -994,42 +1273,74 @@ const Fuzzer = struct {
994 continue;1273 continue;
995 }1274 }
9961275
997 if ((f.seen_pcs[i / @bitSizeOf(usize)] >> @intCast(i % @bitSizeOf(usize))) & 1 == 0) {1276 if ((t.seen_pcs[i / @bitSizeOf(usize)] >> @intCast(i % @bitSizeOf(usize))) & 1 == 0) {
998 @branchHint(.unlikely);1277 @branchHint(.unlikely);
999 best_i_list.append(gpa, f.bests.len) catch @panic("OOM");1278 best_i_list.append(gpa, t.bests.len) catch @panic("OOM");
1000 f.bests.quality_buf[f.bests.len] = .{1279 t.bests.quality_buf[t.bests.len] = .{
1001 .pc = @intCast(i),1280 .pc = @intCast(i),
1002 .min = quality,1281 .min = quality,
1003 .max = quality,1282 .max = quality,
1004 };1283 };
1005 f.bests.input_buf[f.bests.len] = .{ .min = input_i, .max = input_i };1284 t.bests.input_buf[t.bests.len] = .{ .min = input_i, .max = input_i };
1006 f.bests.len += 1;1285 t.bests.len += 1;
1007 }1286 }
1008 }1287 }
10091288
1010 if (best_i_list.items.len == 0 and1289 // Having no best qualities could be from one of these causes:
1011 modify_fs_corpus // Found by freshness; otherwise, it does not need to be better1290 // * A previous corpus input after the test has changed
1012 ) {1291 // * An input provided by the test
1013 @branchHint(.cold); // Nondeterministic test1292 // * The test is non-deterministic
1014 std.log.warn("nondeterministic rerun", .{});1293 if (best_i_list.items.len == 0 and new_is_mut) {
1294 assert(best_i_list.capacity == 0);
1295 f.input_builder.reset();
1296 t.corpus_pos = @enumFromInt(0);
1015 return;1297 return;
1016 }1298 }
10171299
1300 var input = f.input_builder.build();
1301 f.uid_data_i.ensureTotalCapacity(gpa, input.data.uid_slices.entries.len) catch @panic("OOM");
1302 for (
1303 input.seen_uid_i,
1304 input.data.uid_slices.keys(),
1305 input.data.uid_slices.values(),
1306 ) |*i, uid, slice| {
1307 const gop = t.seen_uids.getOrPutValue(gpa, uid, switch (uid.kind) {
1308 .int => .{ .slices = .{ .ints = .empty } },
1309 .bytes => .{ .slices = .{ .bytes = .empty } },
1310 }) catch @panic("OOM");
1311 switch (uid.kind) {
1312 .int => t.seen_uids.values()[gop.index].slices.ints.append(
1313 gpa,
1314 input.data.ints[slice.base..][0..slice.len],
1315 ) catch @panic("OOM"),
1316 .bytes => t.seen_uids.values()[gop.index].slices.bytes.append(gpa, .{
1317 .entries = input.data.bytes.entries[slice.base..][0..slice.len],
1318 .table = input.data.bytes.table,
1319 }) catch @panic("OOM"),
1320 }
1321 i.* = @intCast(gop.index);
1322 }
1323
1018 input.ref.best_i_buf = best_i_list.toOwnedSlice(gpa) catch @panic("OOM");1324 input.ref.best_i_buf = best_i_list.toOwnedSlice(gpa) catch @panic("OOM");
1019 input.ref.best_i_len = @intCast(input.ref.best_i_buf.len);1325 input.ref.best_i_len = @intCast(input.ref.best_i_buf.len);
1020 f.corpus.append(gpa, input) catch @panic("OOM");1326 t.corpus.append(gpa, input) catch @panic("OOM");
1021 f.corpus_pos = input_i;1327 t.corpus_pos = input_i;
10221328
1023 // Must come after the above since `seen_pcs` is used1329 // Must come after the above since `seen_pcs` is used
1024 f.updateSeenPcs();1330 f.updateSeenPcs();
10251331
1026 if (!modify_fs_corpus) return;1332 t.batches_since_find = 0;
10271333 if (f.main_instance and new_is_mut) {
1028 // Write new input to cache1334 // Only the main instance increments the number of unique runs since it is likely
1029 var name_buf: [8]u8 = undefined;1335 // multiple instances find the same new input at the same time.
1030 const name = f.corpusFileName(&name_buf, input_i);1336 _ = @atomicRmw(usize, &exec.seenPcsHeader().unique_runs, .Add, 1, .monotonic);
1031 f.corpus_dir.writeFile(io, .{ .sub_path = name, .data = bytes }) catch |e|1337 // Write new input to the cache
1032 panic("failed to write corpus file '{s}': {t}", .{ name, e });1338 var cname: CorpusFileName = .fromTest(t.dirname);
1339 const name = cname.inputName(@intFromEnum(input_i) - t.start_mut_corpus);
1340 exec.cache_f.writeFile(io, .{ .sub_path = name, .data = bytes, .flags = .{
1341 .exclusive = true,
1342 } }) catch |e| panic("failed to write corpus file '{s}': {t}", .{ name, e });
1343 }
1033 }1344 }
10341345
1035 /// Returns if `error.SkipZigTest` was indicated1346 /// Returns if `error.SkipZigTest` was indicated
...@@ -1061,8 +1372,10 @@ const Fuzzer = struct {...@@ -1061,8 +1372,10 @@ const Fuzzer = struct {
10611372
1062 pub fn cycle(f: *Fuzzer) void {1373 pub fn cycle(f: *Fuzzer) void {
1063 assert(f.mmap_input.len == 0);1374 assert(f.mmap_input.len == 0);
1064 const corpus = f.corpus.slice();1375
1065 const corpus_i = @intFromEnum(f.corpus_pos);1376 const t = &f.tests[f.test_i];
1377 const corpus = t.corpus.slice();
1378 const corpus_i = @intFromEnum(t.corpus_pos);
10661379
1067 var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) };1380 var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) };
1068 var n_mutate = mutCount(small_entronopy.take(u16));1381 var n_mutate = mutCount(small_entronopy.take(u16));
...@@ -1118,13 +1431,181 @@ const Fuzzer = struct {...@@ -1118,13 +1431,181 @@ const Fuzzer = struct {
1118 if (!skip and f.isFresh()) {1431 if (!skip and f.isFresh()) {
1119 @branchHint(.unlikely);1432 @branchHint(.unlikely);
11201433
1121 _ = @atomicRmw(usize, &exec.seenPcsHeader().unique_runs, .Add, 1, .monotonic);1434 abi.runner_broadcast_input(f.test_i, .fromSlice(f.mmap_input.inputSlice()));
1122 f.newInput(true);1435 f.newInput();
1436 } else {
1437 assert(@intFromEnum(t.corpus_pos) < t.corpus.len);
1438 t.corpus_pos = @enumFromInt((@intFromEnum(t.corpus_pos) + 1) % t.corpus.len);
1123 }1439 }
1124 f.mmap_input.clearRetainingCapacity();1440 f.mmap_input.clearRetainingCapacity();
1441 }
1442
1443 fn takeReceived(f: *Fuzzer) void {
1444 const t = &f.tests[f.test_i];
1445 if (t.received.state.startReadIfPending()) {
1446 defer t.received.state.finishRead();
1447 const inputs = &t.received.inputs;
1448 var rem = inputs.items;
1449
1450 while (true) {
1451 const len: u32 = @bitCast(rem[0..4].*);
1452 rem = rem[4..];
1453 const bytes = rem[0..len];
1454 rem = rem[len..];
1455
1456 f.mmap_input.appendSlice(bytes);
1457 f.newInput();
1458 f.mmap_input.clearRetainingCapacity();
1459
1460 if (rem.len == 0) break;
1461 }
1462
1463 inputs.clearRetainingCapacity();
1464 }
1465 }
1466
1467 pub fn batch(f: *Fuzzer) void {
1468 const t = &f.tests[f.test_i];
1469 assert(t.limit != 0);
1470 t.batches += 1;
1471 t.batches_since_find += 1;
1472 if (f.tests.len != 1) {
1473 // Use cpu_process since some fuzz tests may spawn
1474 // other threads and give all the work to them.
1475 const start: Io.Timestamp = .now(io, .cpu_process);
1476 var completed_cycles: u32 = 0;
1477 var total_cycles: u32 = t.batch_cycles;
1478
1479 while (true) {
1480 assert(completed_cycles != total_cycles);
1481 while (completed_cycles < total_cycles) {
1482 f.takeReceived();
1483 f.cycle();
1484 completed_cycles += 1;
1485 }
1486
1487 const duration = start.untilNow(io, .cpu_process);
1488 const ns = @min(@max(1, duration.nanoseconds), math.maxInt(u64));
1489 const speed = @as(u64, t.batch_cycles) * std.time.ns_per_s / ns;
1490 // @min avoids large increases in batch_cycles due to just a few cycles running
1491 // fast. For example, if batch_cycles is only 2, and both run very fast due to
1492 // unlucky rng, this avoids a large runtime on the next batch. This also avoids
1493 // timer inprecision giving large values.
1494 t.batch_cycles = @max(1, @min(speed, t.batch_cycles *| 2));
1495
1496 if (ns < std.time.ns_per_s * 7 / 8) {
1497 // Keep running the test to get closer to a second. This will almost always
1498 // be the case for the first batch as the default batch_cycles is 1.
1499 if (t.limit == total_cycles) break;
1500
1501 const rem_ns: u64 = @as(u32, std.time.ns_per_s) - ns;
1502 const extra: u32 = @intCast(rem_ns * t.batch_cycles / std.time.ns_per_s);
1503 if (extra == 0) break; // No better approximation of a second possible
1504 total_cycles += extra;
1505 if (t.limit) |limit| total_cycles = @min(total_cycles, limit);
1506 continue;
1507 }
1508
1509 break;
1510 }
1511
1512 assert(completed_cycles == total_cycles);
1513 if (t.limit) |prev| {
1514 t.limit = prev - total_cycles;
1515 t.batch_cycles = @min(t.batch_cycles, t.limit.?);
1516 }
1517 } else {
1518 while (true) {
1519 if (t.limit) |limit| {
1520 if (limit == 0) break;
1521 t.limit = limit - 1;
1522 }
1523 f.takeReceived();
1524 f.cycle();
1525 }
1526 }
1527 }
1528
1529 pub fn select(f: *Fuzzer) ?u32 {
1530 assert(f.tests.len > 1); // More efficiently handled by the callee
1531
1532 // The algorithm for selecting tests is such that:
1533 // - 1/4 are from the number of pcs as they give an indication of test complexity.
1534 // - 3/4 are from the recency of the last find as it gives an indication of the
1535 // effectiveness of fuzzing for the test.
1536 // - Tests finding fresh inputs are run 8x other tests.
1537 // - Since new tests are considered to have just found a fresh input, this means they
1538 // are also prioritized which allows their characteristics to be learnt.
1539 // When a test has a new input pending, it is treated as if it had just found a fresh
1540 // input instead of immediately being run. This avoids a test which is finding many new
1541 // inputs from being exclusively run.
1542 const new_batches = 16;
1543
1544 var n_with_new: u32 = 0;
1545 var n_seen_pcs: u64 = 0;
1546 var n_latest_find: u64 = 0;
1547
1548 for (f.tests) |*t| {
1549 const has_pending = t.received.state.hasPending();
1550 if (has_pending) {
1551 assert(t.limit == null); // If multiprocess limited fuzzing was to be added, then
1552 // `t.received.inputs.clearRetainingCapacity()` would need to be added after
1553 // `t.received.state.startReadIfPending()` when the limit has been reached.
1554 }
1555 if (t.limit == 0) continue;
1556
1557 const latest_find = t.batches - t.batches_since_find;
1558 n_with_new += @intFromBool(t.batches_since_find < new_batches or has_pending);
1559 n_seen_pcs += @max(t.seen_pc_count, 1);
1560 n_latest_find += @max(latest_find, 1);
1561 }
1562
1563 if (n_seen_pcs == 0) {
1564 assert(n_with_new == 0);
1565 assert(n_latest_find == 0);
1566 return null; // All fuzz tests have used up their limit
1567 }
1568
1569 const rng: packed struct(u64) {
1570 idx_rng: u32,
1571 from_new: u3,
1572 from_latest_find: u2,
1573 _: u27,
1574 } = @bitCast(f.rngInt(u64));
1575
1576 if (n_with_new != 0 and rng.from_new != 0) {
1577 var n = std.Random.limitRangeBiased(u32, rng.idx_rng, n_with_new);
1578 for (0.., f.tests) |i, *t| {
1579 if (t.limit == 0) continue;
1580 if (t.batches_since_find < new_batches or t.received.state.hasPending()) {
1581 if (n == 0) return @intCast(i);
1582 n -= 1;
1583 }
1584 }
1585 unreachable;
1586 }
11251587
1126 assert(@intFromEnum(f.corpus_pos) < f.corpus.len);1588 if (rng.from_latest_find != 0) {
1127 f.corpus_pos = @enumFromInt((@intFromEnum(f.corpus_pos) + 1) % f.corpus.len);1589 const total_weight = n_latest_find;
1590 var n = f.rngLessThan(u64, total_weight);
1591 for (0.., f.tests) |i, *t| {
1592 if (t.limit == 0) continue;
1593 const latest_find = @max(t.batches - t.batches_since_find, 1);
1594 if (n < latest_find) return @intCast(i);
1595 n -= latest_find;
1596 }
1597 unreachable;
1598 } else {
1599 const total_weight = n_seen_pcs;
1600 var n = f.rngLessThan(u64, total_weight);
1601 for (0.., f.tests) |i, *t| {
1602 if (t.limit == 0) continue;
1603 const seen_pc_count = @max(t.seen_pc_count, 1);
1604 if (n < seen_pc_count) return @intCast(i);
1605 n -= seen_pc_count;
1606 }
1607 unreachable;
1608 }
1128 }1609 }
11291610
1130 fn weightsContain(int: u64, weights: []const abi.Weight) bool {1611 fn weightsContain(int: u64, weights: []const abi.Weight) bool {
...@@ -1184,8 +1665,9 @@ const Fuzzer = struct {...@@ -1184,8 +1665,9 @@ const Fuzzer = struct {
1184 mutate: Untyped,1665 mutate: Untyped,
1185 fresh: void,1666 fresh: void,
1186 } {1667 } {
1187 const corpus = f.corpus.slice();1668 const t = &f.tests[f.test_i];
1188 const corpus_i = @intFromEnum(f.corpus_pos);1669 const corpus = t.corpus.slice();
1670 const corpus_i = @intFromEnum(t.corpus_pos);
1189 const data = &corpus.items(.data)[corpus_i];1671 const data = &corpus.items(.data)[corpus_i];
1190 var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) };1672 var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) };
11911673
...@@ -1276,7 +1758,7 @@ const Fuzzer = struct {...@@ -1276,7 +1758,7 @@ const Fuzzer = struct {
1276 data_slice.len,1758 data_slice.len,
1277 } else src: {1759 } else src: {
1278 const seen_uid_i = corpus.items(.seen_uid_i)[corpus_i][uid_i];1760 const seen_uid_i = corpus.items(.seen_uid_i)[corpus_i][uid_i];
1279 const untyped_slices = f.seen_uids.values()[seen_uid_i].slices;1761 const untyped_slices = t.seen_uids.values()[seen_uid_i].slices;
1280 switch (uid.kind) {1762 switch (uid.kind) {
1281 .int => {1763 .int => {
1282 const slices = untyped_slices.ints.items;1764 const slices = untyped_slices.ints.items;
...@@ -1404,7 +1886,7 @@ const Fuzzer = struct {...@@ -1404,7 +1886,7 @@ const Fuzzer = struct {
1404 }1886 }
1405 } else {1887 } else {
1406 const seen_uid_i = corpus.items(.seen_uid_i)[corpus_i][uid_i];1888 const seen_uid_i = corpus.items(.seen_uid_i)[corpus_i][uid_i];
1407 const untyped_slices = f.seen_uids.values()[seen_uid_i].slices;1889 const untyped_slices = t.seen_uids.values()[seen_uid_i].slices;
1408 switch (uid.kind) {1890 switch (uid.kind) {
1409 .int => {1891 .int => {
1410 const slices = untyped_slices.ints.items;1892 const slices = untyped_slices.ints.items;
...@@ -1432,11 +1914,12 @@ const Fuzzer = struct {...@@ -1432,11 +1914,12 @@ const Fuzzer = struct {
1432 }1914 }
14331915
1434 pub fn nextInt(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) u64 {1916 pub fn nextInt(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) u64 {
1917 const t = &f.tests[f.test_i];
1435 f.req_values += 1;1918 f.req_values += 1;
1436 if (@intFromEnum(f.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {1919 if (@intFromEnum(t.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
1437 @branchHint(.unlikely);1920 @branchHint(.unlikely);
1438 const int = f.bytes_input.valueWeightedWithHash(u64, weights, undefined);1921 const int = f.bytes_input.valueWeightedWithHash(u64, weights, undefined);
1439 if (f.corpus_pos == .bytes_fresh) {1922 if (t.corpus_pos == .bytes_fresh) {
1440 f.input_builder.checkSmithedLen(8);1923 f.input_builder.checkSmithedLen(8);
1441 f.input_builder.addInt(uid, int);1924 f.input_builder.addInt(uid, int);
1442 }1925 }
...@@ -1455,11 +1938,12 @@ const Fuzzer = struct {...@@ -1455,11 +1938,12 @@ const Fuzzer = struct {
1455 }1938 }
14561939
1457 pub fn nextEos(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) bool {1940 pub fn nextEos(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) bool {
1941 const t = &f.tests[f.test_i];
1458 f.req_values += 1;1942 f.req_values += 1;
1459 if (@intFromEnum(f.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {1943 if (@intFromEnum(t.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
1460 @branchHint(.unlikely);1944 @branchHint(.unlikely);
1461 const eos = f.bytes_input.eosWeightedWithHash(weights, undefined);1945 const eos = f.bytes_input.eosWeightedWithHash(weights, undefined);
1462 if (f.corpus_pos == .bytes_fresh) {1946 if (t.corpus_pos == .bytes_fresh) {
1463 f.input_builder.checkSmithedLen(1);1947 f.input_builder.checkSmithedLen(1);
1464 f.input_builder.addInt(uid, @intFromBool(eos));1948 f.input_builder.addInt(uid, @intFromBool(eos));
1465 }1949 }
...@@ -1569,13 +2053,14 @@ const Fuzzer = struct {...@@ -1569,13 +2053,14 @@ const Fuzzer = struct {
1569 }2053 }
15702054
1571 pub fn nextBytes(f: *Fuzzer, uid: Uid, out: []u8, weights: []const abi.Weight) void {2055 pub fn nextBytes(f: *Fuzzer, uid: Uid, out: []u8, weights: []const abi.Weight) void {
2056 const t = &f.tests[f.test_i];
1572 f.req_values += 1;2057 f.req_values += 1;
1573 f.req_bytes +%= @truncate(out.len); // This function should panic since the 32-bit2058 f.req_bytes +%= @truncate(out.len); // This function should panic since the 32-bit
1574 // data limit is exceeded, so wrapping is fine.2059 // data limit is exceeded, so wrapping is fine.
1575 if (@intFromEnum(f.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {2060 if (@intFromEnum(t.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
1576 @branchHint(.unlikely);2061 @branchHint(.unlikely);
1577 f.bytes_input.bytesWeightedWithHash(out, weights, undefined);2062 f.bytes_input.bytesWeightedWithHash(out, weights, undefined);
1578 if (f.corpus_pos == .bytes_fresh) {2063 if (t.corpus_pos == .bytes_fresh) {
1579 f.input_builder.checkSmithedLen(out.len);2064 f.input_builder.checkSmithedLen(out.len);
1580 f.input_builder.addBytes(uid, out);2065 f.input_builder.addBytes(uid, out);
1581 }2066 }
...@@ -1660,8 +2145,9 @@ const Fuzzer = struct {...@@ -1660,8 +2145,9 @@ const Fuzzer = struct {
1660 len_weights: []const abi.Weight,2145 len_weights: []const abi.Weight,
1661 byte_weights: []const abi.Weight,2146 byte_weights: []const abi.Weight,
1662 ) u32 {2147 ) u32 {
2148 const t = &f.tests[f.test_i];
1663 f.req_values += 1;2149 f.req_values += 1;
1664 if (@intFromEnum(f.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {2150 if (@intFromEnum(t.corpus_pos) >= @intFromEnum(Input.Index.reserved_start)) {
1665 @branchHint(.unlikely);2151 @branchHint(.unlikely);
1666 const n = f.bytes_input.sliceWeightedWithHash(2152 const n = f.bytes_input.sliceWeightedWithHash(
1667 buf,2153 buf,
...@@ -1669,7 +2155,7 @@ const Fuzzer = struct {...@@ -1669,7 +2155,7 @@ const Fuzzer = struct {
1669 byte_weights,2155 byte_weights,
1670 undefined,2156 undefined,
1671 );2157 );
1672 if (f.corpus_pos == .bytes_fresh) {2158 if (t.corpus_pos == .bytes_fresh) {
1673 f.input_builder.checkSmithedLen(@as(usize, 4) + n);2159 f.input_builder.checkSmithedLen(@as(usize, 4) + n);
1674 f.input_builder.addBytes(uid, buf[0..n]);2160 f.input_builder.addBytes(uid, buf[0..n]);
1675 }2161 }
...@@ -1686,7 +2172,6 @@ const Fuzzer = struct {...@@ -1686,7 +2172,6 @@ const Fuzzer = struct {
16862172
1687export fn fuzzer_init(cache_dir_path: abi.Slice) void {2173export fn fuzzer_init(cache_dir_path: abi.Slice) void {
1688 exec = .init(cache_dir_path.toSlice());2174 exec = .init(cache_dir_path.toSlice());
1689 fuzzer = .init();
1690}2175}
16912176
1692export fn fuzzer_coverage() abi.Coverage {2177export fn fuzzer_coverage() abi.Coverage {
...@@ -1706,23 +2191,66 @@ export fn fuzzer_coverage() abi.Coverage {...@@ -1706,23 +2191,66 @@ export fn fuzzer_coverage() abi.Coverage {
1706 };2191 };
1707}2192}
17082193
1709export fn fuzzer_set_test(test_one: abi.TestOne, unit_test_name: abi.Slice) void {2194export fn fuzzer_main(
1710 current_test_name = unit_test_name.toSlice();2195 n_tests: u32,
1711 fuzzer.setTest(test_one, unit_test_name.toSlice());2196 seed: u32,
2197 limit_kind: abi.LimitKind,
2198 amount_or_instance: u64,
2199) void {
2200 fuzzer = .init(
2201 n_tests,
2202 seed ^ amount_or_instance, // seed is otherwise the same for all instances
2203 if (limit_kind == .forever) @as(u32, @intCast(amount_or_instance)) else 0,
2204 if (limit_kind == .forever) null else amount_or_instance,
2205 );
2206 defer fuzzer.deinit();
2207 abi.runner_start_input_poller();
2208 defer abi.runner_stop_input_poller();
2209
2210 if (n_tests == 1) {
2211 // no swapping between fuzz tests
2212 runTest(0);
2213 } else {
2214 while (fuzzer.select()) |i| {
2215 runTest(i);
2216 }
2217 }
2218}
2219
2220export fn fuzzer_receive_input(test_i: u32, bytes_slice: abi.Slice) bool {
2221 const recv = &fuzzer.tests[test_i].received;
2222 if (recv.state.startWrite()) return true;
2223 defer recv.state.finishWrite();
2224
2225 const bytes = bytes_slice.toSlice();
2226 const len: u32 = @intCast(bytes.len);
2227 recv.inputs.ensureUnusedCapacity(gpa, 4 + bytes.len) catch @panic("OOM");
2228 recv.inputs.appendSliceAssumeCapacity(@ptrCast(&len));
2229 recv.inputs.appendSliceAssumeCapacity(bytes);
2230
2231 return false;
2232}
2233
2234fn runTest(i: u32) void {
2235 fuzzer.test_i = i;
2236 fuzzer.mmap_input.setTest(i);
2237 current_test_name = abi.runner_test_name(i).toSlice();
2238 abi.runner_test_run(i);
2239}
2240
2241export fn fuzzer_set_test(test_one: abi.TestOne) void {
2242 fuzzer.test_one = test_one;
1712}2243}
17132244
1714export fn fuzzer_new_input(bytes: abi.Slice) void {2245export fn fuzzer_new_input(bytes: abi.Slice) void {
1715 if (bytes.len == 0) return; // An entry of length zero is always present2246 if (bytes.len == 0) return; // An entry of length zero is always present
2247 if (fuzzer.tests[fuzzer.test_i].start_mut_corpus != math.maxInt(u32)) return; // Test ran previously
1716 fuzzer.newInputExternal(bytes.toSlice());2248 fuzzer.newInputExternal(bytes.toSlice());
1717}2249}
17182250
1719export fn fuzzer_main(limit_kind: abi.LimitKind, amount: u64) void {2251export fn fuzzer_start_test() void {
1720 fuzzer.loadCorpus();2252 fuzzer.ensureCorpusLoaded();
1721 switch (limit_kind) {2253 fuzzer.batch();
1722 .forever => while (true) fuzzer.cycle(),
1723 .iterations => for (0..amount) |_| fuzzer.cycle(),
1724 }
1725 fuzzer.reset();
1726}2254}
17272255
1728export fn fuzzer_int(uid: Uid, weights: abi.Weights) u64 {2256export fn fuzzer_int(uid: Uid, weights: abi.Weights) u64 {
...@@ -1786,26 +2314,43 @@ export fn __sanitizer_cov_pcs_init(start: usize, end: usize) void {...@@ -1786,26 +2314,43 @@ export fn __sanitizer_cov_pcs_init(start: usize, end: usize) void {
1786/// Reusable and recoverable input.2314/// Reusable and recoverable input.
1787///2315///
1788/// Has a 32-bit limit on the input length. This has the nice side effect that `u32`2316/// Has a 32-bit limit on the input length. This has the nice side effect that `u32`
1789/// can be used in most placed in `fuzzer` with the last four values reserved.2317/// can be used in most placed in `fuzzer` with the last `@sizeOf(abi.MmapInputHeader)`
2318/// values reserved.
1790const MemoryMappedInput = struct {2319const MemoryMappedInput = struct {
2320 const Header = abi.MmapInputHeader;
2321
1791 len: u32,2322 len: u32,
1792 /// Directly accessing `memory` is unsafe, use either `inputSlice` or `writeSlice`.2323 /// Directly accessing `memory` is unsafe, use either `inputSlice` or `writeSlice`.
1793 ///
1794 /// `memory` starts with the length of the input as a little-endian 32-bit integer.
1795 mmap: Io.File.MemoryMap,2324 mmap: Io.File.MemoryMap,
2325 in_i: u32,
17962326
1797 /// `file` becomes owned by the returned `MemoryMappedInput`2327 /// `file` becomes owned by the returned `MemoryMappedInput`
1798 pub fn init(file: Io.File, size: usize) !MemoryMappedInput {2328 pub fn init(file: Io.File, instance_id: u32, in_i: u32) MemoryMappedInput {
1799 assert(size >= 4);2329 var size = file.length(io) catch |e|
2330 panic("failed to get length of 'in{x}': {t}", .{ in_i, e });
2331 if (size < std.heap.page_size_max) {
2332 size = std.heap.page_size_max;
2333 file.setLength(io, size) catch |e|
2334 panic("failed to resize 'in{x}': {t}", .{ in_i, e });
2335 }
2336 const map = file.createMemoryMap(io, .{ .len = size }) catch |e|
2337 panic("failed to memmap input file 'in{x}': {t}", .{ in_i, e });
2338 @as(*volatile Header, @ptrCast(map.memory)).* = .{
2339 .pc_digest = mem.nativeToLittle(u64, exec.pc_digest),
2340 .instance_id = mem.nativeToLittle(u32, instance_id),
2341 .test_i = 0,
2342 .len = 0,
2343 };
1800 return .{2344 return .{
1801 .len = 0,2345 .len = 0,
1802 .mmap = try file.createMemoryMap(io, .{ .len = size }),2346 .mmap = map,
2347 .in_i = in_i,
1803 };2348 };
1804 }2349 }
18052350
1806 pub fn deinit(l: *MemoryMappedInput) void {2351 pub fn deinit(l: *MemoryMappedInput) void {
1807 const f = l.mmap.file;2352 const f = l.mmap.file;
1808 l.mmap.write(io) catch |e| panic("failed to write memory map of 'in': {t}", .{e});2353 l.mmap.write(io) catch |e| panic("failed to write memory map of 'in{x}': {t}", .{ l.in_i, e });
1809 l.mmap.destroy(io);2354 l.mmap.destroy(io);
1810 f.close(io);2355 f.close(io);
1811 l.* = undefined;2356 l.* = undefined;
...@@ -1815,40 +2360,36 @@ const MemoryMappedInput = struct {...@@ -1815,40 +2360,36 @@ const MemoryMappedInput = struct {
1815 ///2360 ///
1816 /// Invalidates element pointers if additional memory is needed.2361 /// Invalidates element pointers if additional memory is needed.
1817 pub fn ensureUnusedCapacity(l: *MemoryMappedInput, additional_count: usize) void {2362 pub fn ensureUnusedCapacity(l: *MemoryMappedInput, additional_count: usize) void {
1818 return l.ensureTotalCapacity(4 + l.len + additional_count);2363 return l.ensureSize(@sizeOf(Header) + l.len + additional_count);
1819 }2364 }
18202365
1821 /// If the current capacity is less than `min_capacity`, this function will2366 fn ensureSize(l: *MemoryMappedInput, min_capacity: usize) void {
1822 /// modify the array so that it can hold at least `min_capacity` items.
1823 ///
1824 /// Invalidates element pointers if additional memory is needed.
1825 pub fn ensureTotalCapacity(l: *MemoryMappedInput, min_capacity: usize) void {
1826 if (l.mmap.memory.len < min_capacity) {2367 if (l.mmap.memory.len < min_capacity) {
1827 @branchHint(.unlikely);2368 @branchHint(.unlikely);
18282369
1829 const max_capacity = 1 << 32; // The size of the length header is not added2370 const max_capacity = 1 << 32; // The size of the header is not added
1830 // in order to keep the capacity page aligned and to allow those values to2371 // in order to keep the capacity page aligned and to allow those values to
1831 // reserved for other places.2372 // reserved for other places.
1832 if (min_capacity > max_capacity) @panic("too much smith data requested");2373 if (min_capacity > max_capacity) @panic("too much smith data requested");
18332374
1834 const new_capacity = @min(growCapacity(min_capacity), max_capacity);2375 const new_capacity = @min(growCapacity(min_capacity), max_capacity);
1835 l.mmap.file.setLength(io, new_capacity) catch |e|2376 l.mmap.file.setLength(io, new_capacity) catch |e|
1836 panic("failed to resize 'in': {t}", .{e});2377 panic("failed to resize 'in{x}': {t}", .{ l.in_i, e });
1837 l.mmap.setLength(io, new_capacity) catch |se| switch (se) {2378 l.mmap.setLength(io, new_capacity) catch |se| switch (se) {
1838 error.OperationUnsupported => {2379 error.OperationUnsupported => {
1839 const f = l.mmap.file;2380 const f = l.mmap.file;
1840 l.mmap.destroy(io);2381 l.mmap.destroy(io);
1841 l.mmap = f.createMemoryMap(io, .{ .len = new_capacity }) catch |e|2382 l.mmap = f.createMemoryMap(io, .{ .len = new_capacity }) catch |e|
1842 panic("failed to memory map 'in': {t}", .{e});2383 panic("failed to memory map 'in{x}': {t}", .{ l.in_i, e });
1843 },2384 },
1844 else => panic("failed to resize memory map of 'in': {t}", .{se}),2385 else => panic("failed to resize memory map of 'in{x}': {t}", .{ l.in_i, se }),
1845 };2386 };
1846 }2387 }
1847 }2388 }
18482389
1849 // Only writing has side effects, so volatile is not needed2390 // Only writing has side effects, so volatile is not needed
1850 pub fn inputSlice(l: *MemoryMappedInput) []const u8 {2391 pub fn inputSlice(l: *MemoryMappedInput) []const u8 {
1851 return l.mmap.memory[4..][0..l.len];2392 return l.mmap.memory[@sizeOf(Header)..][0..l.len];
1852 }2393 }
18532394
1854 // Writing has side effectsd, so volatile is necessary2395 // Writing has side effectsd, so volatile is necessary
...@@ -1857,7 +2398,13 @@ const MemoryMappedInput = struct {...@@ -1857,7 +2398,13 @@ const MemoryMappedInput = struct {
1857 }2398 }
18582399
1859 fn writeLen(l: *MemoryMappedInput) void {2400 fn writeLen(l: *MemoryMappedInput) void {
1860 l.writeSlice()[0..4].* = @bitCast(mem.nativeToLittle(u32, l.len));2401 l.writeSlice()[@offsetOf(Header, "len")..][0..4].* =
2402 @bitCast(mem.nativeToLittle(u32, l.len));
2403 }
2404
2405 pub fn setTest(l: *MemoryMappedInput, i: u32) void {
2406 l.writeSlice()[@offsetOf(Header, "test_i")..][0..4].* =
2407 @bitCast(mem.nativeToLittle(u32, i));
1861 }2408 }
18622409
1863 /// Invalidates all element pointers.2410 /// Invalidates all element pointers.
...@@ -1871,7 +2418,7 @@ const MemoryMappedInput = struct {...@@ -1871,7 +2418,7 @@ const MemoryMappedInput = struct {
1871 /// Invalidates item pointers if more space is required.2418 /// Invalidates item pointers if more space is required.
1872 pub fn appendSlice(l: *MemoryMappedInput, items: []const u8) void {2419 pub fn appendSlice(l: *MemoryMappedInput, items: []const u8) void {
1873 l.ensureUnusedCapacity(items.len);2420 l.ensureUnusedCapacity(items.len);
1874 @memcpy(l.writeSlice()[4 + l.len ..][0..items.len], items);2421 @memcpy(l.writeSlice()[@sizeOf(Header) + l.len ..][0..items.len], items);
1875 l.len += @as(u32, @intCast(items.len));2422 l.len += @as(u32, @intCast(items.len));
1876 l.writeLen();2423 l.writeLen();
1877 }2424 }
...@@ -1881,7 +2428,8 @@ const MemoryMappedInput = struct {...@@ -1881,7 +2428,8 @@ const MemoryMappedInput = struct {
1881 /// Invalidates item pointers if more space is required.2428 /// Invalidates item pointers if more space is required.
1882 pub fn appendLittleInt(l: *MemoryMappedInput, T: type, x: T) void {2429 pub fn appendLittleInt(l: *MemoryMappedInput, T: type, x: T) void {
1883 l.ensureUnusedCapacity(@sizeOf(T));2430 l.ensureUnusedCapacity(@sizeOf(T));
1884 l.writeSlice()[4 + l.len ..][0..@sizeOf(T)].* = @bitCast(mem.nativeToLittle(T, x));2431 l.writeSlice()[@sizeOf(Header) + l.len ..][0..@sizeOf(T)].* =
2432 @bitCast(mem.nativeToLittle(T, x));
1885 l.len += @sizeOf(T);2433 l.len += @sizeOf(T);
1886 l.writeLen();2434 l.writeLen();
1887 }2435 }
lib/std/Build.zig+3
...@@ -128,6 +128,9 @@ pub const Graph = struct {...@@ -128,6 +128,9 @@ pub const Graph = struct {
128 random_seed: u32 = 0,128 random_seed: u32 = 0,
129 dependency_cache: InitializedDepMap = .empty,129 dependency_cache: InitializedDepMap = .empty,
130 allow_so_scripts: ?bool = null,130 allow_so_scripts: ?bool = null,
131 /// Steps should use `io` to limit the number of jobs, however in the case of
132 /// a single step spawning a fixed number of processes this can be used.
133 max_jobs: ?u32 = null,
131 time_report: bool,134 time_report: bool,
132 /// Similar to the `Io.Terminal.Mode` returned by `Io.lockStderr`, but also135 /// Similar to the `Io.Terminal.Mode` returned by `Io.lockStderr`, but also
133 /// respects the '--color' flag.136 /// respects the '--color' flag.
lib/std/Build/Fuzz.zig+6-19
...@@ -128,7 +128,7 @@ pub fn init(...@@ -128,7 +128,7 @@ pub fn init(
128128
129pub fn start(fuzz: *Fuzz) void {129pub fn start(fuzz: *Fuzz) void {
130 const io = fuzz.io;130 const io = fuzz.io;
131 fuzz.prog_node = fuzz.root_prog_node.start("Fuzzing", fuzz.run_steps.len);131 fuzz.prog_node = fuzz.root_prog_node.start("Fuzzing", 0);
132132
133 if (fuzz.mode == .forever) {133 if (fuzz.mode == .forever) {
134 // For polling messages and sending updates to subscribers.134 // For polling messages and sending updates to subscribers.
...@@ -137,18 +137,8 @@ pub fn start(fuzz: *Fuzz) void {...@@ -137,18 +137,8 @@ pub fn start(fuzz: *Fuzz) void {
137 }137 }
138138
139 for (fuzz.run_steps) |run| {139 for (fuzz.run_steps) |run| {
140 if (run.fuzz_tests.items.len > 1) {140 assert(run.rebuilt_executable != null);
141 // Multiple fuzzWorkerRuns currently cause race-conditions141 fuzz.group.async(io, fuzzWorkerRun, .{ fuzz, run });
142 // since they use the same Run step. See #30969
143 fatal("--fuzz not yet implemented for multiple tests", .{});
144 }
145 }
146
147 for (fuzz.run_steps) |run| {
148 for (run.fuzz_tests.items) |unit_test_name| {
149 assert(run.rebuilt_executable != null);
150 fuzz.group.async(io, fuzzWorkerRun, .{ fuzz, run, unit_test_name });
151 }
152 }142 }
153}143}
154144
...@@ -193,16 +183,13 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, parent_prog_nod...@@ -193,16 +183,13 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, parent_prog_nod
193 run.rebuilt_executable = try rebuilt_bin_path.join(gpa, compile.out_filename);183 run.rebuilt_executable = try rebuilt_bin_path.join(gpa, compile.out_filename);
194}184}
195185
196fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run, unit_test_name: []const u8) void {186fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run) void {
197 const owner = run.step.owner;187 const owner = run.step.owner;
198 const gpa = owner.allocator;188 const gpa = owner.allocator;
199 const graph = owner.graph;189 const graph = owner.graph;
200 const io = graph.io;190 const io = graph.io;
201191
202 const prog_node = fuzz.prog_node.start(unit_test_name, 0);192 run.rerunInFuzzMode(fuzz, fuzz.prog_node) catch |err| switch (err) {
203 defer prog_node.end();
204
205 run.rerunInFuzzMode(fuzz, unit_test_name, prog_node) catch |err| switch (err) {
206 error.MakeFailed => {193 error.MakeFailed => {
207 var buf: [256]u8 = undefined;194 var buf: [256]u8 = undefined;
208 const stderr = io.lockStderr(&buf, graph.stderr_mode) catch |e| switch (e) {195 const stderr = io.lockStderr(&buf, graph.stderr_mode) catch |e| switch (e) {
...@@ -213,7 +200,7 @@ fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run, unit_test_name: []const u8) void {...@@ -213,7 +200,7 @@ fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run, unit_test_name: []const u8) void {
213 return;200 return;
214 },201 },
215 else => {202 else => {
216 log.err("step '{s}': failed to rerun '{s}' in fuzz mode: {t}", .{ run.step.name, unit_test_name, err });203 log.err("step '{s}': failed to rerun in fuzz mode: {t}", .{ run.step.name, err });
217 return;204 return;
218 },205 },
219 };206 };
lib/std/Build/Step/Run.zig+530-68
...@@ -1068,7 +1068,6 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -1068,7 +1068,6 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
1068pub fn rerunInFuzzMode(1068pub fn rerunInFuzzMode(
1069 run: *Run,1069 run: *Run,
1070 fuzz: *std.Build.Fuzz,1070 fuzz: *std.Build.Fuzz,
1071 unit_test_name: []const u8,
1072 prog_node: std.Progress.Node,1071 prog_node: std.Progress.Node,
1073) !void {1072) !void {
1074 const step = &run.step;1073 const step = &run.step;
...@@ -1139,7 +1138,6 @@ pub fn rerunInFuzzMode(...@@ -1139,7 +1138,6 @@ pub fn rerunInFuzzMode(
1139 .unit_test_timeout_ns = null, // don't time out fuzz tests for now1138 .unit_test_timeout_ns = null, // don't time out fuzz tests for now
1140 .gpa = fuzz.gpa,1139 .gpa = fuzz.gpa,
1141 }, .{1140 }, .{
1142 .unit_test_name = unit_test_name,
1143 .fuzz = fuzz,1141 .fuzz = fuzz,
1144 });1142 });
1145}1143}
...@@ -1211,7 +1209,6 @@ fn termMatches(expected: ?process.Child.Term, actual: process.Child.Term) bool {...@@ -1211,7 +1209,6 @@ fn termMatches(expected: ?process.Child.Term, actual: process.Child.Term) bool {
12111209
1212const FuzzContext = struct {1210const FuzzContext = struct {
1213 fuzz: *std.Build.Fuzz,1211 fuzz: *std.Build.Fuzz,
1214 unit_test_name: []const u8,
1215};1212};
12161213
1217fn runCommand(1214fn runCommand(
...@@ -1655,6 +1652,11 @@ fn evalZigTest(...@@ -1655,6 +1652,11 @@ fn evalZigTest(
1655 options: Step.MakeOptions,1652 options: Step.MakeOptions,
1656 fuzz_context: ?FuzzContext,1653 fuzz_context: ?FuzzContext,
1657) !void {1654) !void {
1655 if (fuzz_context != null) {
1656 try evalFuzzTest(run, spawn_options, options, fuzz_context.?);
1657 return;
1658 }
1659
1658 const step_owner = run.step.owner;1660 const step_owner = run.step.owner;
1659 const gpa = step_owner.allocator;1661 const gpa = step_owner.allocator;
1660 const arena = step_owner.allocator;1662 const arena = step_owner.allocator;
...@@ -1693,7 +1695,6 @@ fn evalZigTest(...@@ -1693,7 +1695,6 @@ fn evalZigTest(
1693 run,1695 run,
1694 &child,1696 &child,
1695 options,1697 options,
1696 fuzz_context,
1697 &multi_reader,1698 &multi_reader,
1698 &test_metadata,1699 &test_metadata,
1699 &test_results,1700 &test_results,
...@@ -1815,7 +1816,6 @@ fn waitZigTest(...@@ -1815,7 +1816,6 @@ fn waitZigTest(
1815 run: *Run,1816 run: *Run,
1816 child: *process.Child,1817 child: *process.Child,
1817 options: Step.MakeOptions,1818 options: Step.MakeOptions,
1818 fuzz_context: ?FuzzContext,
1819 multi_reader: *Io.File.MultiReader,1819 multi_reader: *Io.File.MultiReader,
1820 opt_metadata: *?TestMetadata,1820 opt_metadata: *?TestMetadata,
1821 results: *Step.TestResults,1821 results: *Step.TestResults,
...@@ -1837,29 +1837,7 @@ fn waitZigTest(...@@ -1837,29 +1837,7 @@ fn waitZigTest(
1837 var sub_prog_node: ?std.Progress.Node = null;1837 var sub_prog_node: ?std.Progress.Node = null;
1838 defer if (sub_prog_node) |n| n.end();1838 defer if (sub_prog_node) |n| n.end();
18391839
1840 if (fuzz_context) |ctx| {1840 if (opt_metadata.*) |*md| {
1841 assert(opt_metadata.* == null); // fuzz processes are never restarted
1842 switch (ctx.fuzz.mode) {
1843 .forever => {
1844 sendRunFuzzTestMessage(
1845 io,
1846 child.stdin.?,
1847 ctx.unit_test_name,
1848 .forever,
1849 0, // instance ID; will be used by multiprocess forever fuzzing in the future
1850 ) catch |err| return .{ .write_failed = err };
1851 },
1852 .limit => |limit| {
1853 sendRunFuzzTestMessage(
1854 io,
1855 child.stdin.?,
1856 ctx.unit_test_name,
1857 .iterations,
1858 limit.amount,
1859 ) catch |err| return .{ .write_failed = err };
1860 },
1861 }
1862 } else if (opt_metadata.*) |*md| {
1863 // Previous unit test process died or was killed; we're continuing where it left off1841 // Previous unit test process died or was killed; we're continuing where it left off
1864 requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };1842 requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
1865 } else {1843 } else {
...@@ -1872,14 +1850,11 @@ fn waitZigTest(...@@ -1872,14 +1850,11 @@ fn waitZigTest(
18721850
1873 var last_update: Io.Clock.Timestamp = .now(io, .awake);1851 var last_update: Io.Clock.Timestamp = .now(io, .awake);
18741852
1875 var coverage_id: ?u64 = null;
1876
1877 // This timeout is used when we're waiting on the test runner itself rather than a user-specified1853 // This timeout is used when we're waiting on the test runner itself rather than a user-specified
1878 // test. For instance, if the test runner leaves this much time between us requesting a test to1854 // test. For instance, if the test runner leaves this much time between us requesting a test to
1879 // start and it acknowledging the test starting, we terminate the child and raise an error. This1855 // start and it acknowledging the test starting, we terminate the child and raise an error. This
1880 // *should* never happen, but could in theory be caused by some very unlucky IB in a test.1856 // *should* never happen, but could in theory be caused by some very unlucky IB in a test.
1881 const response_timeout: ?Io.Clock.Duration = t: {1857 const response_timeout: Io.Clock.Duration = t: {
1882 if (fuzz_context != null) break :t null; // don't timeout fuzz tests
1883 const ns = @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s);1858 const ns = @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s);
1884 break :t .{ .clock = .awake, .raw = .fromNanoseconds(ns) };1859 break :t .{ .clock = .awake, .raw = .fromNanoseconds(ns) };
1885 };1860 };
...@@ -1947,8 +1922,6 @@ fn waitZigTest(...@@ -1947,8 +1922,6 @@ fn waitZigTest(
1947 );1922 );
1948 },1923 },
1949 .test_metadata => {1924 .test_metadata => {
1950 assert(fuzz_context == null);
1951
1952 // `metadata` would only be populated if we'd already seen a `test_metadata`, but we1925 // `metadata` would only be populated if we'd already seen a `test_metadata`, but we
1953 // only request it once (and importantly, we don't re-request it if we kill and1926 // only request it once (and importantly, we don't re-request it if we kill and
1954 // restart the test runner).1927 // restart the test runner).
...@@ -1986,7 +1959,6 @@ fn waitZigTest(...@@ -1986,7 +1959,6 @@ fn waitZigTest(
1986 last_update = .now(io, .awake);1959 last_update = .now(io, .awake);
1987 },1960 },
1988 .test_results => {1961 .test_results => {
1989 assert(fuzz_context == null);
1990 const md = &opt_metadata.*.?;1962 const md = &opt_metadata.*.?;
19911963
1992 const tr_hdr = body_r.takeStruct(std.zig.Server.Message.TestResults, .little) catch unreachable;1964 const tr_hdr = body_r.takeStruct(std.zig.Server.Message.TestResults, .little) catch unreachable;
...@@ -2033,44 +2005,523 @@ fn waitZigTest(...@@ -2033,44 +2005,523 @@ fn waitZigTest(
20332005
2034 requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };2006 requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
2035 },2007 },
2008 else => {}, // ignore other messages
2009 }
2010 }
2011}
2012
2013const FuzzTestRunner = struct {
2014 run: *Run,
2015 ctx: FuzzContext,
2016 coverage_id: ?u64,
2017
2018 instances: []Instance,
2019 /// The indexes of this are layed out such that it is effectively an array
2020 /// of `[instances.len][3]Io.Operation.Storage` of stdin, stdout, stderr.
2021 batch: Io.Batch,
2022 /// LIFO. Stream of message bodies trailed by PendingBroadcastFooter.
2023 pending_broadcasts: std.ArrayList(u8),
2024 broadcast: std.ArrayList(u8),
2025 broadcast_undelivered: u32,
2026
2027 const Instance = struct {
2028 child: process.Child,
2029 message: std.ArrayListAligned(u8, .@"4"),
2030 broadcast_written: usize,
2031 stderr: std.ArrayList(u8),
2032 stdin_vec: [1][]u8,
2033 stdout_vec: [1][]u8,
2034 stderr_vec: [1][]u8,
2035 progress_node: std.Progress.Node,
2036
2037 fn messageHeader(instance: *Instance) InHeader {
2038 assert(instance.message.items.len >= @sizeOf(InHeader));
2039 const header_ptr: *InHeader = @ptrCast(instance.message.items);
2040 var header = header_ptr.*;
2041 if (std.builtin.Endian.native != .little) {
2042 std.mem.byteSwapAllFields(InHeader, &header);
2043 }
2044 return header;
2045 }
2046 };
2047
2048 const PendingBroadcastFooter = struct {
2049 from_id: u32,
2050 body_len: u32,
2051 };
2052
2053 const InHeader = std.zig.Server.Message.Header;
2054 const OutHeader = std.zig.Client.Message.Header;
2055
2056 const stdin_i = 0;
2057 const stdout_i = 1;
2058 const stderr_i = 2;
2059
2060 fn init(
2061 run: *Run,
2062 ctx: FuzzContext,
2063 progress_node: std.Progress.Node,
2064 spawn_options: process.SpawnOptions,
2065 ) !FuzzTestRunner {
2066 const step_owner = run.step.owner;
2067 const gpa = step_owner.allocator;
2068 const io = step_owner.graph.io;
2069
2070 const n_instances = switch (ctx.fuzz.mode) {
2071 .forever => step_owner.graph.max_jobs orelse @min(
2072 std.Thread.getCpuCount() catch 1,
2073 (std.math.maxInt(u32) - 2) / 3,
2074 ),
2075 .limit => 1,
2076 };
2077 const instances = try gpa.alloc(Instance, n_instances);
2078 errdefer gpa.free(instances);
2079 const batch_storage = try gpa.alloc(Io.Operation.Storage, instances.len * 3);
2080 errdefer gpa.free(batch_storage);
2081
2082 @memset(instances, .{
2083 .child = undefined,
2084 .message = .empty,
2085 .broadcast_written = undefined,
2086 .stderr = .empty,
2087 .stdin_vec = undefined,
2088 .stdout_vec = undefined,
2089 .stderr_vec = undefined,
2090 .progress_node = undefined,
2091 });
2092 for (0.., instances) |id, *instance| {
2093 errdefer for (instances[0..id]) |*spawned| {
2094 spawned.child.kill(io);
2095 spawned.progress_node.end();
2096 };
2097 instance.child = try process.spawn(io, spawn_options);
2098 instance.progress_node = progress_node.start("starting fuzzer", 0);
2099 }
2100
2101 return .{
2102 .run = run,
2103 .ctx = ctx,
2104 .coverage_id = null,
2105
2106 .instances = instances,
2107 .batch = .init(batch_storage),
2108 .pending_broadcasts = .empty,
2109 .broadcast = .empty,
2110 .broadcast_undelivered = 0,
2111 };
2112 }
2113
2114 fn deinit(f: *FuzzTestRunner) void {
2115 const step_owner = f.run.step.owner;
2116 const gpa = step_owner.allocator;
2117 const io = step_owner.graph.io;
2118
2119 f.batch.cancel(io);
2120 gpa.free(f.batch.storage);
2121 var total_rss: usize = 0;
2122 for (f.instances) |*instance| {
2123 instance.child.kill(io);
2124 instance.message.deinit(gpa);
2125 instance.stderr.deinit(gpa);
2126 instance.progress_node.end();
2127 total_rss += instance.child.resource_usage_statistics.getMaxRss() orelse 0;
2128 }
2129 f.run.step.result_peak_rss = @max(f.run.step.result_peak_rss, total_rss);
2130 gpa.free(f.instances);
2131 }
2132
2133 fn startInstances(f: *FuzzTestRunner) !void {
2134 const step_owner = f.run.step.owner;
2135 const io = step_owner.graph.io;
2136
2137 for (0.., f.instances) |id, *instance| {
2138 const id32: u32 = @intCast(id);
2139 (switch (f.ctx.fuzz.mode) {
2140 .forever => sendRunFuzzTestMessage(
2141 io,
2142 instance.child.stdin.?,
2143 f.run.fuzz_tests.items,
2144 .forever,
2145 id32,
2146 ),
2147 .limit => |limit| sendRunFuzzTestMessage(
2148 io,
2149 instance.child.stdin.?,
2150 f.run.fuzz_tests.items,
2151 .iterations,
2152 limit.amount,
2153 ),
2154 }) catch |write_err| {
2155 // The runner unexpectedly closed stdin, which means it crashed during initialization.
2156 // Clean up everything and wait for the child to exit.
2157 instance.child.stdin.?.close(io);
2158 instance.child.stdin = null;
2159 const term = try instance.child.wait(io);
2160 return f.run.step.fail(
2161 "unable to write stdin ({t}); test process unexpectedly {f}",
2162 .{ write_err, fmtTerm(term) },
2163 );
2164 };
2165
2166 try f.addStdoutRead(id32, @sizeOf(InHeader));
2167 try f.addStderrRead(id32);
2168 }
2169 }
2170
2171 fn listen(f: *FuzzTestRunner) !void {
2172 const step_owner = f.run.step.owner;
2173 const io = step_owner.graph.io;
2174
2175 while (true) {
2176 try f.batch.awaitConcurrent(io, .none);
2177 while (f.batch.next()) |completion| {
2178 const id = completion.index / 3;
2179 const result = completion.result;
2180 switch (completion.index % 3) {
2181 0 => try f.completeStdinWrite(id, result.file_write_streaming catch |e| switch (e) {
2182 error.BrokenPipe => return f.instanceEos(id),
2183 else => |write_e| return write_e,
2184 }),
2185 1 => try f.completeStdoutRead(id, result.file_read_streaming catch |e| switch (e) {
2186 error.EndOfStream => return f.instanceEos(id),
2187 else => |read_e| return read_e,
2188 }),
2189 2 => try f.completeStderrRead(id, result.file_read_streaming catch |e| switch (e) {
2190 error.EndOfStream => return f.instanceEos(id),
2191 else => |read_e| return read_e,
2192 }),
2193 else => unreachable,
2194 }
2195 }
2196 }
2197 }
2198
2199 fn completeStdoutRead(f: *FuzzTestRunner, id: u32, n: usize) !void {
2200 const step_owner = f.run.step.owner;
2201 const gpa = step_owner.allocator;
2202 const io = step_owner.graph.io;
2203 const instance = &f.instances[id];
2204
2205 instance.message.items.len += n;
2206 const total_read = instance.message.items.len;
2207 if (total_read < @sizeOf(InHeader)) {
2208 try f.addStdoutRead(id, @sizeOf(InHeader));
2209 return;
2210 }
2211
2212 const header = instance.messageHeader();
2213 const body = instance.message.items[@sizeOf(InHeader)..];
2214 if (body.len != header.bytes_len) {
2215 try f.addStdoutRead(id, @sizeOf(InHeader) + header.bytes_len);
2216 return;
2217 }
2218
2219 switch (header.tag) {
2220 .zig_version => {
2221 if (!std.mem.eql(u8, builtin.zig_version_string, body)) return f.run.step.fail(
2222 "zig version mismatch build runner vs compiler: '{s}' vs '{s}'",
2223 .{ builtin.zig_version_string, body },
2224 );
2225 },
2036 .coverage_id => {2226 .coverage_id => {
2037 coverage_id = body_r.takeInt(u64, .little) catch unreachable;2227 var body_r: Io.Reader = .fixed(body);
2228 f.coverage_id = body_r.takeInt(u64, .little) catch unreachable;
2038 const cumulative_runs = body_r.takeInt(u64, .little) catch unreachable;2229 const cumulative_runs = body_r.takeInt(u64, .little) catch unreachable;
2039 const cumulative_unique = body_r.takeInt(u64, .little) catch unreachable;2230 const cumulative_unique = body_r.takeInt(u64, .little) catch unreachable;
2040 const cumulative_coverage = body_r.takeInt(u64, .little) catch unreachable;2231 const cumulative_coverage = body_r.takeInt(u64, .little) catch unreachable;
20412232
2042 {2233 const fuzz = f.ctx.fuzz;
2043 const fuzz = fuzz_context.?.fuzz;2234 fuzz.queue_mutex.lockUncancelable(io);
2044 fuzz.queue_mutex.lockUncancelable(io);2235 defer fuzz.queue_mutex.unlock(io);
2045 defer fuzz.queue_mutex.unlock(io);2236 try fuzz.msg_queue.append(fuzz.gpa, .{ .coverage = .{
2046 try fuzz.msg_queue.append(fuzz.gpa, .{ .coverage = .{2237 .id = f.coverage_id.?,
2047 .id = coverage_id.?,2238 .cumulative = .{
2048 .cumulative = .{2239 .runs = cumulative_runs,
2049 .runs = cumulative_runs,2240 .unique = cumulative_unique,
2050 .unique = cumulative_unique,2241 .coverage = cumulative_coverage,
2051 .coverage = cumulative_coverage,2242 },
2052 },2243 .run = f.run,
2053 .run = run,2244 } });
2054 } });2245 fuzz.queue_cond.signal(io);
2055 fuzz.queue_cond.signal(io);
2056 }
2057 },2246 },
2058 .fuzz_start_addr => {2247 .fuzz_start_addr => {
2059 const fuzz = fuzz_context.?.fuzz;2248 var body_r: Io.Reader = .fixed(body);
2249 const fuzz = f.ctx.fuzz;
2060 const addr = body_r.takeInt(u64, .little) catch unreachable;2250 const addr = body_r.takeInt(u64, .little) catch unreachable;
2061 {2251
2062 fuzz.queue_mutex.lockUncancelable(io);2252 fuzz.queue_mutex.lockUncancelable(io);
2063 defer fuzz.queue_mutex.unlock(io);2253 defer fuzz.queue_mutex.unlock(io);
2064 try fuzz.msg_queue.append(fuzz.gpa, .{ .entry_point = .{2254 try fuzz.msg_queue.append(fuzz.gpa, .{ .entry_point = .{
2065 .addr = addr,2255 .addr = addr,
2066 .coverage_id = coverage_id.?,2256 .coverage_id = f.coverage_id.?,
2067 } });2257 } });
2068 fuzz.queue_cond.signal(io);2258 fuzz.queue_cond.signal(io);
2259 },
2260 .fuzz_test_change => {
2261 const test_i = std.mem.readInt(u32, body[0..4], .little);
2262 instance.progress_node.setName(f.run.fuzz_tests.items[test_i]);
2263 },
2264 .broadcast_fuzz_input => {
2265 if (f.instances.len == 1) {
2266 // No other processes to broadcast to.
2267 } else if (f.broadcast_undelivered == 0) {
2268 try f.instanceBroadcast(id, body);
2269 } else {
2270 const footer: PendingBroadcastFooter = .{
2271 .from_id = id,
2272 .body_len = @intCast(body.len),
2273 };
2274 // There is another broadcast in progress so add this one to the queue.
2275 const size = @sizeOf(PendingBroadcastFooter) + body.len;
2276 try f.pending_broadcasts.ensureUnusedCapacity(gpa, size);
2277 f.pending_broadcasts.appendSliceAssumeCapacity(body);
2278 f.pending_broadcasts.appendSliceAssumeCapacity(@ptrCast(&footer));
2069 }2279 }
2070 },2280 },
2071 else => {}, // ignore other messages2281 else => {}, // ignore other messages
2072 }2282 }
2283
2284 instance.message.clearRetainingCapacity();
2285 try f.addStdoutRead(id, @sizeOf(InHeader));
2286 }
2287
2288 fn completeStderrRead(f: *FuzzTestRunner, id: u32, n: usize) !void {
2289 const instance = &f.instances[id];
2290 instance.stderr.items.len += n;
2291 try f.addStderrRead(id);
2292 }
2293
2294 fn completeStdinWrite(f: *FuzzTestRunner, id: u32, n: usize) !void {
2295 const instance = &f.instances[id];
2296
2297 instance.broadcast_written += n;
2298 if (instance.broadcast_written == f.broadcast.items.len) {
2299 f.broadcast_undelivered -= 1;
2300 if (f.broadcast_undelivered == 0) {
2301 try f.broadcastComplete();
2302 }
2303 } else {
2304 f.addStdinWrite(id);
2305 }
2073 }2306 }
2307
2308 fn addStdoutRead(f: *FuzzTestRunner, id: u32, end: usize) !void {
2309 const step_owner = f.run.step.owner;
2310 const gpa = step_owner.allocator;
2311 const instance = &f.instances[id];
2312
2313 try instance.message.ensureTotalCapacity(gpa, end);
2314 const start = instance.message.items.len;
2315 instance.stdout_vec = .{instance.message.allocatedSlice()[start..end]};
2316 f.batch.addAt(id * 3 + stdout_i, .{ .file_read_streaming = .{
2317 .file = instance.child.stdout.?,
2318 .data = &instance.stdout_vec,
2319 } });
2320 }
2321
2322 fn addStderrRead(f: *FuzzTestRunner, id: u32) !void {
2323 const step_owner = f.run.step.owner;
2324 const gpa = step_owner.allocator;
2325 const instance = &f.instances[id];
2326
2327 try instance.stderr.ensureUnusedCapacity(gpa, 1);
2328 instance.stderr_vec = .{instance.stderr.unusedCapacitySlice()};
2329 f.batch.addAt(id * 3 + stderr_i, .{ .file_read_streaming = .{
2330 .file = instance.child.stderr.?,
2331 .data = &instance.stderr_vec,
2332 } });
2333 }
2334
2335 fn addStdinWrite(f: *FuzzTestRunner, id: u32) void {
2336 const instance = &f.instances[id];
2337
2338 assert(f.broadcast.items.len != instance.broadcast_written);
2339 instance.stdin_vec = .{f.broadcast.items[instance.broadcast_written..]};
2340 f.batch.addAt(id * 3 + stdin_i, .{ .file_write_streaming = .{
2341 .file = instance.child.stdin.?,
2342 .data = &instance.stdin_vec,
2343 } });
2344 }
2345
2346 fn instanceEos(f: *FuzzTestRunner, id: u32) !void {
2347 const step_owner = f.run.step.owner;
2348 const io = step_owner.graph.io;
2349 const instance = &f.instances[id];
2350
2351 instance.child.stdin.?.close(io);
2352 instance.child.stdin = null;
2353 const term = try instance.child.wait(io);
2354 if (!termMatches(.{ .exited = 0 }, term)) {
2355 f.run.step.result_stderr = try f.mergedStderr();
2356 try f.saveCrash(id, term);
2357 return f.run.step.fail("test process unexpectedly {f}", .{fmtTerm(term)});
2358 }
2359 }
2360
2361 fn saveCrash(f: *FuzzTestRunner, id: u32, term: process.Child.Term) !void {
2362 const step = &f.run.step;
2363 const b = step.owner;
2364 const io = b.graph.io;
2365
2366 if (f.coverage_id == null) return;
2367
2368 // Search for the input file corresponding to the instance
2369 const InputHeader = Build.abi.fuzz.MmapInputHeader;
2370 var in_r_buf: [@sizeOf(InputHeader)]u8 = undefined;
2371 var in_r: Io.File.Reader = undefined;
2372 var in_f: Io.File = undefined;
2373 var in_name_buf: [12]u8 = undefined;
2374 var in_name: []const u8 = undefined;
2375 var i: u32 = 0;
2376 const header: InputHeader = while (true) {
2377 const name_prefix = "f" ++ Io.Dir.path.sep_str ++ "in";
2378 in_name = std.fmt.bufPrint(&in_name_buf, name_prefix ++ "{x}", .{i}) catch unreachable;
2379 in_f = b.cache_root.handle.openFile(io, in_name, .{
2380 .lock = .exclusive,
2381 .lock_nonblocking = true,
2382 }) catch |e| switch (e) {
2383 error.FileNotFound => return,
2384 error.WouldBlock => continue, // Can not be from
2385 // the crashed instance since it is still locked.
2386 else => return step.fail("failed to open file '{f}{s}': {t}", .{
2387 b.cache_root, in_name, e,
2388 }),
2389 };
2390
2391 in_r = in_f.readerStreaming(io, &in_r_buf);
2392 const header = in_r.interface.takeStruct(InputHeader, .little) catch |e| {
2393 in_f.close(io);
2394 switch (e) {
2395 error.ReadFailed => return step.fail("failed to read file '{f}{s}': {t}", .{
2396 b.cache_root, in_name, in_r.err.?,
2397 }),
2398 error.EndOfStream => continue,
2399 }
2400 };
2401
2402 if (header.pc_digest == f.coverage_id.? and
2403 header.instance_id == id and
2404 header.test_i < f.run.fuzz_tests.items.len)
2405 {
2406 break header;
2407 }
2408
2409 in_f.close(io);
2410 if (i == std.math.maxInt(u32)) return;
2411 i += 1;
2412 };
2413 defer in_f.close(io);
2414
2415 // Save it to a seperate file
2416 const crash_name = "f" ++ Io.Dir.path.sep_str ++ "crash";
2417 const out = b.cache_root.handle.createFile(io, crash_name, .{
2418 .lock = .exclusive, // Multiple run steps could have found a crash at the same time
2419 }) catch |e| return step.fail("failed to create file '{f}{s}': {t}", .{
2420 b.cache_root, crash_name, e,
2421 });
2422 defer out.close(io);
2423
2424 var out_w_buf: [512]u8 = undefined;
2425 var out_w = out.writerStreaming(io, &out_w_buf);
2426 _ = out_w.interface.sendFileAll(&in_r, .limited(header.len)) catch |e| switch (e) {
2427 error.ReadFailed => return step.fail("failed to read file '{f}{s}': {t}", .{
2428 b.cache_root, in_name, in_r.err.?,
2429 }),
2430 error.WriteFailed => return step.fail("failed to write file '{f}{s}': {t}", .{
2431 b.cache_root, crash_name, out_w.err.?,
2432 }),
2433 };
2434
2435 return f.run.step.fail("test '{s}' {f}; input saved to '{f}{s}'", .{
2436 f.run.fuzz_tests.items[header.test_i],
2437 fmtTerm(term),
2438 b.cache_root,
2439 crash_name,
2440 });
2441 }
2442
2443 fn instanceBroadcast(f: *FuzzTestRunner, from_id: u32, bytes: []const u8) !void {
2444 assert(f.instances.len > 1);
2445 assert(f.broadcast_undelivered == 0); // no other broadcast is progress
2446 assert(f.broadcast.items.len == 0);
2447 assert(from_id < f.instances.len);
2448
2449 const step_owner = f.run.step.owner;
2450 const gpa = step_owner.allocator;
2451
2452 var out_header: OutHeader = .{
2453 .tag = .new_fuzz_input,
2454 .bytes_len = @intCast(bytes.len),
2455 };
2456 if (std.builtin.Endian.native != .little) {
2457 std.mem.byteSwapAllFields(OutHeader, &out_header);
2458 }
2459 try f.broadcast.ensureTotalCapacity(gpa, @sizeOf(OutHeader) + bytes.len);
2460 f.broadcast.appendSliceAssumeCapacity(@ptrCast(&out_header));
2461 f.broadcast.appendSliceAssumeCapacity(bytes);
2462
2463 f.broadcast_undelivered = @intCast(f.instances.len - 1);
2464 for (0.., f.instances) |to_id, *instance| {
2465 if (to_id == from_id) continue;
2466 instance.broadcast_written = 0;
2467 f.addStdinWrite(@intCast(to_id));
2468 }
2469 }
2470
2471 fn broadcastComplete(f: *FuzzTestRunner) !void {
2472 assert(f.instances.len > 1);
2473 assert(f.broadcast_undelivered == 0);
2474 f.broadcast.clearRetainingCapacity();
2475
2476 const pending = &f.pending_broadcasts;
2477 if (pending.items.len != 0) {
2478 // Another broadcast is pending; copy it over to `broadcast`
2479
2480 const footer_len = @sizeOf(PendingBroadcastFooter);
2481 const footer_bytes = pending.items[pending.items.len - footer_len ..];
2482 const footer: *align(1) PendingBroadcastFooter = @ptrCast(footer_bytes);
2483 pending.items.len -= footer_len;
2484
2485 const body = pending.items[pending.items.len - footer.body_len ..];
2486 try f.instanceBroadcast(footer.from_id, body);
2487 pending.items.len -= body.len;
2488 }
2489 }
2490
2491 fn mergedStderr(f: *FuzzTestRunner) std.mem.Allocator.Error![]const u8 {
2492 const step_owner = f.run.step.owner;
2493 const arena = step_owner.allocator;
2494
2495 // Collect any remaining stderr
2496 while (f.batch.next()) |completion| {
2497 if (completion.index % 3 != 2) continue;
2498 const len = completion.result.file_read_streaming catch continue;
2499 f.instances[completion.index / 3].stderr.items.len += len;
2500 }
2501
2502 var stderr_len: usize = 0;
2503 for (f.instances) |*instance| stderr_len += instance.stderr.items.len;
2504 const stderr = try arena.alloc(u8, stderr_len);
2505
2506 stderr_len = 0;
2507 for (f.instances) |*instance| {
2508 @memcpy(stderr[stderr_len..][0..instance.stderr.items.len], instance.stderr.items);
2509 stderr_len += instance.stderr.items.len;
2510 }
2511 return stderr;
2512 }
2513};
2514
2515fn evalFuzzTest(
2516 run: *Run,
2517 spawn_options: process.SpawnOptions,
2518 options: Step.MakeOptions,
2519 fuzz_context: FuzzContext,
2520) !void {
2521 var f: FuzzTestRunner = try .init(run, fuzz_context, options.progress_node, spawn_options);
2522 defer f.deinit();
2523 try f.startInstances();
2524 try f.listen();
2074}2525}
20752526
2076const TestMetadata = struct {2527const TestMetadata = struct {
...@@ -2149,30 +2600,41 @@ fn sendRunTestMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag, in...@@ -2149,30 +2600,41 @@ fn sendRunTestMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag, in
2149fn sendRunFuzzTestMessage(2600fn sendRunFuzzTestMessage(
2150 io: Io,2601 io: Io,
2151 file: Io.File,2602 file: Io.File,
2152 test_name: []const u8,2603 test_names: []const []const u8,
2153 kind: std.Build.abi.fuzz.LimitKind,2604 kind: std.Build.abi.fuzz.LimitKind,
2154 amount_or_instance: u64,2605 amount_or_instance: u64,
2155) !void {2606) !void {
2156 const header: std.zig.Client.Message.Header = .{2607 const header: std.zig.Client.Message.Header = .{
2157 .tag = .start_fuzzing,2608 .tag = .start_fuzzing,
2158 .bytes_len = 4 + 1 + 8,2609 .bytes_len = 1 + 8 + 4 + count: {
2610 var c: u32 = @intCast(test_names.len * 4);
2611 for (test_names) |name| {
2612 c += @intCast(name.len);
2613 }
2614 break :count c;
2615 },
2159 };2616 };
2160 var w = file.writerStreaming(io, &.{});2617 var w = file.writerStreaming(io, &.{});
2161 w.interface.writeStruct(header, .little) catch |err| switch (err) {2618 w.interface.writeStruct(header, .little) catch |err| switch (err) {
2162 error.WriteFailed => return w.err.?,2619 error.WriteFailed => return w.err.?,
2163 };2620 };
2164 w.interface.writeInt(u32, @intCast(test_name.len), .little) catch |err| switch (err) {
2165 error.WriteFailed => return w.err.?,
2166 };
2167 w.interface.writeAll(test_name) catch |err| switch (err) {
2168 error.WriteFailed => return w.err.?,
2169 };
2170 w.interface.writeByte(@intFromEnum(kind)) catch |err| switch (err) {2621 w.interface.writeByte(@intFromEnum(kind)) catch |err| switch (err) {
2171 error.WriteFailed => return w.err.?,2622 error.WriteFailed => return w.err.?,
2172 };2623 };
2173 w.interface.writeInt(u64, amount_or_instance, .little) catch |err| switch (err) {2624 w.interface.writeInt(u64, amount_or_instance, .little) catch |err| switch (err) {
2174 error.WriteFailed => return w.err.?,2625 error.WriteFailed => return w.err.?,
2175 };2626 };
2627 w.interface.writeInt(u32, @intCast(test_names.len), .little) catch |err| switch (err) {
2628 error.WriteFailed => return w.err.?,
2629 };
2630 for (test_names) |test_name| {
2631 w.interface.writeInt(u32, @intCast(test_name.len), .little) catch |err| switch (err) {
2632 error.WriteFailed => return w.err.?,
2633 };
2634 w.interface.writeAll(test_name) catch |err| switch (err) {
2635 error.WriteFailed => return w.err.?,
2636 };
2637 }
2176}2638}
21772639
2178fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResult {2640fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResult {
lib/std/Build/abi.zig+39-7
...@@ -162,15 +162,39 @@ pub const fuzz = struct {...@@ -162,15 +162,39 @@ pub const fuzz = struct {
162 pub extern fn fuzzer_init(cache_dir_path: Slice) void;162 pub extern fn fuzzer_init(cache_dir_path: Slice) void;
163 /// `fuzzer_init` must be called first.163 /// `fuzzer_init` must be called first.
164 pub extern fn fuzzer_coverage() Coverage;164 pub extern fn fuzzer_coverage() Coverage;
165 pub extern fn fuzzer_unslide_address(addr: usize) usize;
166
167 /// Performs all the fuzzing work and selects tests to run
168 ///
165 /// `fuzzer_init` must be called first.169 /// `fuzzer_init` must be called first.
166 pub extern fn fuzzer_set_test(test_one: TestOne, unit_test_name: Slice) void;170 pub extern fn fuzzer_main(
167 /// `fuzzer_set_test` must be called first.171 n_tests: u32,
168 /// The callee owns the memory of bytes and must not free it until `fuzzer_main` returns172 seed: u32,
173 limit_kind: LimitKind,
174 amount_or_instance: u64,
175 ) void;
176 pub extern fn runner_test_run(i: u32) void;
177 pub extern fn runner_test_name(i: u32) Slice;
178 // Since the runner owns the `std.zig.Server` instance, it also controls the
179 // concurrent Io instance so reads can be canceled. As such, the fuzzer has
180 // to call into the runner for any zig server / concurrent operation.
181 pub extern fn runner_start_input_poller() void;
182 pub extern fn runner_stop_input_poller() void;
183 /// Returns if cancelation has been indicated.
184 pub extern fn runner_futex_wait(*const u32, expected: u32) bool;
185 pub extern fn runner_futex_wake(*const u32, waiters: u32) void;
186 pub extern fn runner_broadcast_input(test_i: u32, bytes: Slice) void;
187 /// `fuzzer_main` must be called first.
188 ///
189 /// Called concurrently with `fuzzer_main`. Returns if cancelation has been indicated.
190 pub extern fn fuzzer_receive_input(test_i: u32, bytes: Slice) bool;
191
192 /// Must be called from inside a test function
193 pub extern fn fuzzer_set_test(test_one: TestOne) void;
194 /// Must be called from inside a test function where `fuzzer_set_test` has been called first.
169 pub extern fn fuzzer_new_input(bytes: Slice) void;195 pub extern fn fuzzer_new_input(bytes: Slice) void;
170 /// `fuzzer_set_test` must be called first.196 /// Must be called from inside a test function where `fuzzer_set_test` has been called first.
171 /// Resets the fuzzer's state to that of `fuzzer_init`.197 pub extern fn fuzzer_start_test() void;
172 pub extern fn fuzzer_main(limit_kind: LimitKind, amount: u64) void;
173 pub extern fn fuzzer_unslide_address(addr: usize) usize;
174198
175 pub extern fn fuzzer_int(uid: Uid, weights: Weights) u64;199 pub extern fn fuzzer_int(uid: Uid, weights: Weights) u64;
176 pub extern fn fuzzer_eos(uid: Uid, weights: Weights) bool;200 pub extern fn fuzzer_eos(uid: Uid, weights: Weights) bool;
...@@ -337,6 +361,14 @@ pub const fuzz = struct {...@@ -337,6 +361,14 @@ pub const fuzz = struct {
337 }361 }
338 };362 };
339363
364 /// Fields are little-endian
365 pub const MmapInputHeader = extern struct {
366 pc_digest: u64 align(4), // aligned so header does not have padding
367 instance_id: u32,
368 test_i: u32,
369 len: u32,
370 };
371
340 /// WebSocket server->client.372 /// WebSocket server->client.
341 ///373 ///
342 /// Sent once, when fuzzing starts, to indicate the available coverage data.374 /// Sent once, when fuzzing starts, to indicate the available coverage data.
lib/std/compress/flate/Compress.zig+3-3
...@@ -1488,7 +1488,7 @@ const PackedContainer = packed struct(u2) {...@@ -1488,7 +1488,7 @@ const PackedContainer = packed struct(u2) {
14881488
1489test Compress {1489test Compress {
1490 const fbufs = try testingFreqBufs();1490 const fbufs = try testingFreqBufs();
1491 defer if (!builtin.fuzz) std.testing.allocator.destroy(fbufs);1491 defer std.testing.allocator.destroy(fbufs);
1492 try std.testing.fuzz(fbufs, testFuzzedCompressInput, .{});1492 try std.testing.fuzz(fbufs, testFuzzedCompressInput, .{});
1493}1493}
14941494
...@@ -1818,7 +1818,7 @@ pub const Raw = struct {...@@ -1818,7 +1818,7 @@ pub const Raw = struct {
18181818
1819test Raw {1819test Raw {
1820 const data_buf = try std.testing.allocator.create([4 * 65536]u8);1820 const data_buf = try std.testing.allocator.create([4 * 65536]u8);
1821 defer if (!builtin.fuzz) std.testing.allocator.destroy(data_buf);1821 defer std.testing.allocator.destroy(data_buf);
1822 var prng: std.Random.DefaultPrng = .init(std.testing.random_seed);1822 var prng: std.Random.DefaultPrng = .init(std.testing.random_seed);
1823 prng.random().bytes(data_buf);1823 prng.random().bytes(data_buf);
1824 try std.testing.fuzz(data_buf, testFuzzedRawInput, .{});1824 try std.testing.fuzz(data_buf, testFuzzedRawInput, .{});
...@@ -2491,7 +2491,7 @@ pub const Huffman = struct {...@@ -2491,7 +2491,7 @@ pub const Huffman = struct {
24912491
2492test Huffman {2492test Huffman {
2493 const fbufs = try testingFreqBufs();2493 const fbufs = try testingFreqBufs();
2494 defer if (!builtin.fuzz) std.testing.allocator.destroy(fbufs);2494 defer std.testing.allocator.destroy(fbufs);
2495 try std.testing.fuzz(fbufs, testFuzzedHuffmanInput, .{});2495 try std.testing.fuzz(fbufs, testFuzzedHuffmanInput, .{});
2496}2496}
24972497
lib/std/zig/Client.zig+8-3
...@@ -33,13 +33,18 @@ pub const Message = struct {...@@ -33,13 +33,18 @@ pub const Message = struct {
33 /// Ask the test runner to run a particular test.33 /// Ask the test runner to run a particular test.
34 /// The message body is a u32 test index.34 /// The message body is a u32 test index.
35 run_test,35 run_test,
36 /// Ask the test runner to start fuzzing a particular test forever or for a given amount of time/iterations.36 /// Ask the test runner to start fuzzing a set of test forever or each for a given amount of
37 /// iterations. After this is sent, the only allowed message is `new_fuzz_input`.
38 ///
37 /// The message body is:39 /// The message body is:
38 /// - a u32 test name len.
39 /// - a test name with the above length
40 /// - a u8 test limit kind (std.Build.api.fuzz.LimitKind)40 /// - a u8 test limit kind (std.Build.api.fuzz.LimitKind)
41 /// - a u64 value whose meaning depends on FuzzLimitKind (either a limit amount or an instance id)41 /// - a u64 value whose meaning depends on FuzzLimitKind (either a limit amount or an instance id)
42 /// - a u32 number of tests followed by n elements of
43 /// - a u32 test name len.
44 /// - a test name with the above length
42 start_fuzzing,45 start_fuzzing,
46 /// The message body has the same format as in Server.
47 new_fuzz_input,
4348
44 _,49 _,
45 };50 };
lib/std/zig/Server.zig+26
...@@ -60,6 +60,13 @@ pub const Message = struct {...@@ -60,6 +60,13 @@ pub const Message = struct {
60 /// address of the fuzz unit test. This is used to provide a starting60 /// address of the fuzz unit test. This is used to provide a starting
61 /// point to view coverage.61 /// point to view coverage.
62 fuzz_start_addr,62 fuzz_start_addr,
63 /// Body is:
64 /// - u32le test index.
65 fuzz_test_change,
66 /// Body is:
67 /// - u32le test index
68 /// - input in remaining bytes
69 broadcast_fuzz_input,
63 /// Body is a TimeReport.70 /// Body is a TimeReport.
64 time_report,71 time_report,
6572
...@@ -176,6 +183,15 @@ pub fn serveMessageHeader(s: *const Server, header: OutMessage.Header) !void {...@@ -176,6 +183,15 @@ pub fn serveMessageHeader(s: *const Server, header: OutMessage.Header) !void {
176 try s.out.writeStruct(header, .little);183 try s.out.writeStruct(header, .little);
177}184}
178185
186pub fn serveU32Message(s: *const Server, tag: OutMessage.Tag, int: u32) !void {
187 try serveMessageHeader(s, .{
188 .tag = tag,
189 .bytes_len = @sizeOf(u32),
190 });
191 try s.out.writeInt(u32, int, .little);
192 try s.out.flush();
193}
194
179pub fn serveU64Message(s: *const Server, tag: OutMessage.Tag, int: u64) !void {195pub fn serveU64Message(s: *const Server, tag: OutMessage.Tag, int: u64) !void {
180 assert(tag != .coverage_id);196 assert(tag != .coverage_id);
181 try serveMessageHeader(s, .{197 try serveMessageHeader(s, .{
...@@ -198,6 +214,16 @@ pub fn serveCoverageIdMessage(s: *const Server, id: u64, runs: u64, unique: u64,...@@ -198,6 +214,16 @@ pub fn serveCoverageIdMessage(s: *const Server, id: u64, runs: u64, unique: u64,
198 try s.out.flush();214 try s.out.flush();
199}215}
200216
217pub fn serveBroadcastFuzzInputMessage(s: *const Server, test_i: u32, bytes: []const u8) !void {
218 try s.serveMessageHeader(.{
219 .tag = .broadcast_fuzz_input,
220 .bytes_len = @sizeOf(u32) + @as(u32, @intCast(bytes.len)),
221 });
222 try s.out.writeInt(u32, test_i, .little);
223 try s.out.writeAll(bytes);
224 try s.out.flush();
225}
226
201pub fn serveEmitDigest(227pub fn serveEmitDigest(
202 s: *Server,228 s: *Server,
203 digest: *const [Cache.bin_digest_len]u8,229 digest: *const [Cache.bin_digest_len]u8,
test/standalone/libfuzzer/main.zig+33-3
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1const std = @import("std");1const std = @import("std");
2const assert = std.debug.assert;
2const abi = std.Build.abi.fuzz;3const abi = std.Build.abi.fuzz;
3const native_endian = @import("builtin").cpu.arch.endian();4const native_endian = @import("builtin").cpu.arch.endian();
45
...@@ -6,6 +7,37 @@ fn testOne() callconv(.c) bool {...@@ -6,6 +7,37 @@ fn testOne() callconv(.c) bool {
6 return false;7 return false;
7}8}
89
10export fn runner_test_run(i: u32) void {
11 assert(i == 0);
12 abi.fuzzer_set_test(testOne);
13 abi.fuzzer_new_input(.fromSlice(""));
14 abi.fuzzer_new_input(.fromSlice("hello"));
15 abi.fuzzer_start_test();
16}
17
18export fn runner_test_name(i: u32) abi.Slice {
19 assert(i == 0);
20 return .fromSlice("test");
21}
22
23export fn runner_start_input_poller() void {}
24export fn runner_stop_input_poller() void {}
25
26export fn runner_futex_wait(ptr: *const u32, expected: u32) bool {
27 assert(ptr.* == expected); // single-threaded
28 return false;
29}
30
31export fn runner_futex_wake(ptr: *const u32, waiters: u32) void {
32 _ = ptr;
33 _ = waiters;
34}
35
36export fn runner_broadcast_input(test_i: u32, bytes: abi.Slice) void {
37 _ = test_i;
38 _ = bytes;
39}
40
9pub fn main(init: std.process.Init) !void {41pub fn main(init: std.process.Init) !void {
10 const gpa = init.gpa;42 const gpa = init.gpa;
11 const io = init.io;43 const io = init.io;
...@@ -19,9 +51,7 @@ pub fn main(init: std.process.Init) !void {...@@ -19,9 +51,7 @@ pub fn main(init: std.process.Init) !void {
19 defer cache_dir.close(io);51 defer cache_dir.close(io);
2052
21 abi.fuzzer_init(.fromSlice(cache_dir_path));53 abi.fuzzer_init(.fromSlice(cache_dir_path));
22 abi.fuzzer_set_test(testOne, .fromSlice("test"));54 abi.fuzzer_main(1, 0, .iterations, 100);
23 abi.fuzzer_new_input(.fromSlice(""));
24 abi.fuzzer_new_input(.fromSlice("hello"));
2555
26 const pc_digest = abi.fuzzer_coverage().id;56 const pc_digest = abi.fuzzer_coverage().id;
27 const coverage_file_path = "v/" ++ std.fmt.hex(pc_digest);57 const coverage_file_path = "v/" ++ std.fmt.hex(pc_digest);