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 {
424424 fatal("unable to parse jobs count '{s}': {t}", .{ text, err });
425425 if (n < 1) fatal("number of jobs must be at least 1", .{});
426426 threaded.setAsyncLimit(.limited(n));
427 graph.max_jobs = n;
427428 } else if (mem.eql(u8, arg, "--")) {
428429 builder.args = argsRest(args, arg_idx);
429430 break;
lib/compiler/test_runner.zig+191-59
......@@ -6,6 +6,7 @@ const Io = std.Io;
66const fatal = std.process.fatal;
77const testing = std.testing;
88const assert = std.debug.assert;
9const panic = std.debug.panic;
910const fuzz_abi = std.Build.abi.fuzz;
1011
1112pub const std_options: std.Options = .{
......@@ -17,6 +18,8 @@ var fba: std.heap.FixedBufferAllocator = .init(&fba_buffer);
1718var fba_buffer: [8192]u8 = undefined;
1819var stdin_buffer: [4096]u8 = undefined;
1920var stdout_buffer: [4096]u8 = undefined;
21var stdin_reader: Io.File.Reader = undefined;
22var stdout_writer: Io.File.Writer = undefined;
2023const runner_threaded_io: Io = Io.Threaded.global_single_threaded.io();
2124
2225/// 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 {
3841 }
3942
4043 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});
4245 }
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
4649 var listen = false;
4750 var opt_cache_dir: ?[]const u8 = null;
......@@ -55,7 +58,7 @@ pub fn main(init: std.process.Init.Minimal) void {
5558 } else if (std.mem.startsWith(u8, arg, "--cache-dir")) {
5659 opt_cache_dir = arg["--cache-dir=".len..];
5760 } else {
58 std.debug.panic("unrecognized command line argument: {s}", .{arg});
61 panic("unrecognized command line argument: {s}", .{arg});
5962 }
6063 }
6164
......@@ -65,7 +68,7 @@ pub fn main(init: std.process.Init.Minimal) void {
6568 }
6669
6770 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});
6972 } else {
7073 return mainTerminal(init);
7174 }
......@@ -73,24 +76,14 @@ pub fn main(init: std.process.Init.Minimal) void {
7376
7477fn mainServer(init: std.process.Init.Minimal) !void {
7578 @disableInstrumentation();
76 var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io, &stdin_buffer);
77 var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io, &stdout_buffer);
79 stdin_reader = .initStreaming(.stdin(), runner_threaded_io, &stdin_buffer);
80 stdout_writer = .initStreaming(.stdout(), runner_threaded_io, &stdout_buffer);
7881 var server = try std.zig.Server.init(.{
7982 .in = &stdin_reader.interface,
8083 .out = &stdout_writer.interface,
8184 .zig_version = builtin.zig_version_string,
8285 });
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
9487 while (true) {
9588 const hdr = try server.receiveMessage();
9689 switch (hdr.tag) {
......@@ -180,48 +173,75 @@ fn mainServer(init: std.process.Init.Minimal) !void {
180173 // since they are not present.
181174 if (!builtin.fuzz) unreachable;
182175
183 const index: u32 = @intCast(index: {
184 testing.allocator_instance = .{};
185 defer if (testing.allocator_instance.deinit() == .leak) {
186 @panic("internal test runner memory leak");
187 };
188
189 const name_len = try server.receiveBody_u32();
190 const name = try server.in.readAlloc(testing.allocator, @intCast(name_len));
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 }
176 var gpa_instance: std.heap.DebugAllocator(.{}) = .init;
177 defer if (gpa_instance.deinit() == .leak) {
178 @panic("internal test runner memory leak");
179 };
180 const gpa = gpa_instance.allocator();
181 var io_instance: Io.Threaded = .init(gpa, .{
182 .argv0 = .init(init.args),
183 .environ = init.environ,
199184 });
185 defer io_instance.deinit();
186 const io = io_instance.io();
187
200188 const mode: fuzz_abi.LimitKind = @enumFromInt(try server.receiveBody_u8());
201189 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];
204 const entry_addr = @intFromPtr(test_fn.func);
202 const n_tests: u32 = try server.receiveBody_u32();
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));
207 defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);
208 is_fuzz_test = false;
209 fuzz_test_index = index;
210 fuzz_mode = mode;
211 fuzz_amount_or_instance = amount_or_instance;
213 {
214 var large_name_buf: std.ArrayList(u8) = .empty;
215 defer large_name_buf.deinit(gpa);
216 for (test_indexes) |*i| {
217 const name_len = try server.receiveBody_u32();
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) {
214 error.SkipZigTest => return,
215 else => {
216 if (@errorReturnTrace()) |trace| {
217 std.debug.dumpStackTrace(trace);
235 if (main_instance) {
236 const relocated_entry_addr = @intFromPtr(builtin.test_functions[i.*].func);
237 const entry_addr = fuzz_abi.fuzzer_unslide_address(relocated_entry_addr);
238 try server.serveU64Message(.fuzz_start_addr, entry_addr);
218239 }
219 std.debug.print("failed with error.{t}\n", .{err});
220 std.process.exit(1);
221 },
222 };
223 if (!is_fuzz_test) @panic("missed call to std.testing.fuzz");
224 if (log_err_count != 0) @panic("error logs detected");
240 }
241 }
242
243 fuzz_abi.fuzzer_main(n_tests, testing.random_seed, mode, amount_or_instance);
244
225245 assert(mode != .forever);
226246 std.process.exit(0);
227247 },
......@@ -382,16 +402,126 @@ pub fn mainSimple() anyerror!void {
382402 passed += 1;
383403 }
384404 if (enable_print) {
385 var stdout_writer = stdout.writer(runner_threaded_io, &.{});
386 stdout_writer.interface.print("{} passed, {} skipped, {} failed\n", .{ passed, skipped, failed }) catch {};
405 var unbuffered_stdout_writer = stdout.writer(runner_threaded_io, &.{});
406 unbuffered_stdout_writer.interface.print(
407 "{} passed, {} skipped, {} failed\n",
408 .{ passed, skipped, failed },
409 ) catch {};
387410 }
388411 if (failed != 0) std.process.exit(1);
389412}
390413
391414var is_fuzz_test: bool = undefined;
392var fuzz_test_index: u32 = undefined;
393var fuzz_mode: fuzz_abi.LimitKind = undefined;
394var fuzz_amount_or_instance: u64 = undefined;
415var fuzz_runner: if (builtin.fuzz) struct {
416 indexes: []u32,
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
396526pub fn fuzz(
397527 context: anytype,
......@@ -448,16 +578,18 @@ pub fn fuzz(
448578 return false;
449579 }
450580 };
581
451582 if (builtin.fuzz) {
583 // Preserve the calling test's allocator state
452584 const prev_allocator_state = testing.allocator_instance;
453585 testing.allocator_instance = .{};
454586 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);
458590 for (options.corpus) |elem|
459591 fuzz_abi.fuzzer_new_input(.fromSlice(elem));
460 fuzz_abi.fuzzer_main(fuzz_mode, fuzz_amount_or_instance);
592 fuzz_abi.fuzzer_start_test();
461593 return;
462594 }
463595
lib/fuzzer.zig+855-307
......@@ -13,7 +13,7 @@ pub const std_options = std.Options{
1313 .logFn = logOverride,
1414};
1515
16const io = std.Io.Threaded.global_single_threaded.io();
16const io = Io.Threaded.global_single_threaded.io();
1717
1818fn logOverride(
1919 comptime level: std.log.Level,
......@@ -77,23 +77,27 @@ const Executable = struct {
7777 panic("failed to create directory 'v': {t}", .{e});
7878 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, .{
8189 .read = true,
82 // If we create the file, we want to block other processes while we populate it
83 .lock = .exclusive,
84 .exclusive = true,
85 })) |f|
86 .{ f, true }
87 else |e| switch (e) {
88 error.PathAlreadyExists => .{ v.openFile(io, &file_name, .{
89 .mode = .read_write,
90 .lock = .shared,
91 }) catch |e2| panic(
92 "failed to open existing coverage file '{s}': {t}",
93 .{ &file_name, e2 },
94 ), false },
95 else => panic("failed to create coverage file '{s}': {t}", .{ &file_name, e }),
96 };
90 .truncate = false,
91 }) catch |e| panic("failed to open coverage file '{s}': {t}", .{ &file_name, e });
92
93 const maybe_populate = coverage_file.tryLock(io, .exclusive) catch |e| panic(
94 "failed to acquire exclusive lock coverage file '{s}': {t}",
95 .{ &file_name, e },
96 );
97 if (!maybe_populate) {
98 coverage_file.lock(io, .shared) catch |e|
99 panic("failed to acquire share lock coverage file '{s}': {t}", .{ &file_name, e });
100 }
97101
98102 comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_bits_usize);
99103 comptime assert(abi.SeenPcsHeader.trailing[1] == .pc_addr);
......@@ -102,16 +106,21 @@ const Executable = struct {
102106 pc_bitset_usizes * @sizeOf(usize) +
103107 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) {
106113 coverage_file.setLength(io, coverage_file_len) catch |e|
107114 panic("failed to resize new coverage file '{s}': {t}", .{ &file_name, e });
108 } else {
109 const size = coverage_file.length(io) catch |e|
110 panic("failed to stat coverage file '{s}': {t}", .{ &file_name, e });
111 if (size != coverage_file_len) panic(
115 populate = true;
116 } else if (size != coverage_file_len) {
117 panic(
112118 "incompatible existing coverage file '{s}' (differing lengths: {} != {})",
113119 .{ &file_name, size, coverage_file_len },
114120 );
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 });
115124 }
116125
117126 var io_map = coverage_file.createMemoryMap(io, .{ .len = coverage_file_len }) catch |e|
......@@ -228,6 +237,13 @@ const Executable = struct {
228237 return self;
229238 }
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
231247 pub fn pcBitsetIterator(self: Executable) PcBitsetIterator {
232248 return .{ .pc_counters = self.pc_counters };
233249 }
......@@ -263,32 +279,16 @@ const Executable = struct {
263279};
264280
265281const Fuzzer = struct {
282 tests: []Test,
283 test_i: u32,
284 test_one: abi.TestOne,
285
266286 // The default PRNG is not used here since going through `Random` can be very expensive
267287 // since LLVM often fails to devirtualize and inline `fill`. Additionally, optimization
268288 // is simpler since integers are not serialized then deserialized in the random stream.
269289 //
270290 // This acounts for a 30% performance improvement with LLVM 21.
271291 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
292292 bytes_input: std.testing.Smith,
293293 input_builder: Input.Builder,
294294 /// Number of data calls the current run has made.
......@@ -319,13 +319,140 @@ const Fuzzer = struct {
319319 },
320320
321321 /// 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.
323324 mmap_input: MemoryMappedInput,
324 /// Filesystem directory containing found inputs for future runs
325 corpus_dir: Io.Dir,
326 /// The values in `corpus` past this point directly correspond to what is found
327 /// in `corpus_dir`.
328 start_corpus_dir: u32,
325 /// The instance is responsible for updating the filesystem corpus.
326 ///
327 /// Since different fuzzer instances can be out of sync due to finding inputs before recieving
328 /// others and nondeterministic tests, the filesystem is only based off the first instance.
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
330457 const SeqCopy = union {
331458 order_i: u32,
......@@ -480,7 +607,9 @@ const Fuzzer = struct {
480607 .total_ints = 0,
481608 .total_bytes = 0,
482609 .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,
484613 };
485614
486615 pub fn addInt(b: *Builder, uid: Uid, int: u64) void {
......@@ -591,7 +720,7 @@ const Fuzzer = struct {
591720 b.total_ints = 0;
592721 b.total_bytes = 0;
593722 b.weighted_len = 0;
594 b.smithed_len = 4;
723 b.smithed_len = Builder.init.smithed_len;
595724 return input;
596725 }
597726
......@@ -604,31 +733,128 @@ const Fuzzer = struct {
604733 }
605734 }
606735 b.uid_slices.clearRetainingCapacity();
736 b.bytes_table.clearRetainingCapacity();
607737 b.total_ints = 0;
608738 b.total_bytes = 0;
609739 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;
611749 }
612750 };
613751 };
614752
615 pub fn init() Fuzzer {
616 if (exec.pc_counters.len > math.maxInt(u32)) @panic("too many pcs");
617 const f: Fuzzer = .{
618 .xoshiro = .init(0),
619 .test_one = undefined,
753 pub fn init(n_tests: u32, seed: u64, instance_id: u32, limit: ?u64) Fuzzer {
754 const pcs = exec.pc_counters.len;
755 if (pcs > math.maxInt(u32)) @panic("too many pcs");
756
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"),
622 .bests = .{
623 .len = 0,
624 .quality_buf = gpa.alloc(Input.Best, exec.pc_counters.len) catch @panic("OOM"),
625 .input_buf = gpa.alloc(Input.Best.Map, exec.pc_counters.len) catch @panic("OOM"),
626 },
627 .seen_uids = .empty,
788 const tests = gpa.alloc(Test, n_tests) catch @panic("OOM");
789 const seen_pcs_len = bitsetUsizes(pcs);
790 var seen_pcs_bufs = gpa.alloc(usize, seen_pcs_len * n_tests) catch @panic("OOM");
791 var best_quality_bufs = gpa.alloc(Input.Best, pcs * n_tests) catch @panic("OOM");
792 var best_input_bufs = gpa.alloc(Input.Best.Map, pcs * n_tests) catch @panic("OOM");
793 @memset(seen_pcs_bufs, 0);
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,
630 .corpus_pos = undefined,
852 return .{
853 .tests = tests,
854 .test_i = undefined,
855 .test_one = undefined,
631856
857 .xoshiro = .init(seed),
632858 .bytes_input = undefined,
633859 .input_builder = .init,
634860 .req_values = undefined,
......@@ -636,97 +862,144 @@ const Fuzzer = struct {
636862 .uid_data_i = .empty,
637863 .mut_data = undefined,
638864
639 .mmap_input = undefined,
640 .corpus_dir = undefined,
641 .start_corpus_dir = undefined,
865 .mmap_input = mmap_input,
866 .main_instance = instance_id == 0,
642867 };
643 @memset(f.seen_pcs, 0);
644 return f;
645868 }
646869
647 /// May only be called after `f.setTest` has been called
648 pub fn reset(f: *Fuzzer) void {
649 f.test_one = undefined;
650
651 @memset(f.seen_pcs, 0);
652 f.bests.len = 0;
653 @memset(f.bests.quality_buf, undefined);
654 @memset(f.bests.input_buf, undefined);
655 for (f.seen_uids.keys(), f.seen_uids.values()) |uid, *u| {
656 switch (uid.kind) {
657 .int => u.slices.ints.deinit(gpa),
658 .bytes => u.slices.bytes.deinit(gpa),
870 pub fn deinit(f: *Fuzzer) void {
871 const pcs = exec.pc_counters.len;
872 const n_tests = f.tests.len;
873 gpa.free(f.tests[0].seen_pcs.ptr[0 .. bitsetUsizes(pcs) * n_tests]);
874 gpa.free(f.tests[0].bests.quality_buf.ptr[0 .. pcs * n_tests]);
875 gpa.free(f.tests[0].bests.input_buf.ptr[0 .. pcs * n_tests]);
876 for (f.tests) |*t| {
877 const seen_uids = t.seen_uids.entries.slice();
878 for (seen_uids.items(.key), seen_uids.items(.value)) |uid, *data| {
879 switch (uid.kind) {
880 .int => data.slices.ints.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();
659890 }
891 if (f.main_instance) {
892 t.lock_file.close(io);
893 }
894 t.received.inputs.deinit(gpa);
660895 }
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();
664 f.corpus_pos = undefined;
902 pub fn ensureCorpusLoaded(f: *Fuzzer) void {
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();
669 f.corpus_dir.close(io);
670 f.start_corpus_dir = undefined;
671 }
912 read_corpus: {
913 var cname: CorpusFileName = .fromTest(t.dirname);
672914
673 pub fn setTest(f: *Fuzzer, test_one: abi.TestOne, unit_test_name: []const u8) void {
674 f.test_one = test_one;
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,
915 const readlock_name = cname.readLockName();
916 const readlock_file = exec.cache_f.createFile(io, readlock_name, .{
680917 .truncate = false,
681 // In case any other fuzz tests are running under the same test name,
682 // the input file is exclusively locked to ensures only one proceeds.
683 .lock = .exclusive,
684 .lock_nonblocking = true,
918 .lock = .shared,
685919 }) catch |e| switch (e) {
686 error.WouldBlock => @panic("input file 'in' is in use by another fuzzing process"),
687 else => panic("failed to create input file 'in': {t}", .{e}),
920 // FileNotFound means the corpus directory does not exist, which means it is empty
921 error.FileNotFound => break :read_corpus,
922 else => panic("failed to open '{s}': {t}", .{ readlock_name, e }),
688923 };
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});
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});
954 i += 1; // Cannot overflow due to corpus 32-bit size limit
694955 }
956 }
695957
696 break :map MemoryMappedInput.init(input, size) catch |e|
697 panic("failed to memmap input file 'in': {t}", .{e});
698 };
958 if (f.main_instance) {
959 t.start_mut_corpus = start_mut;
699960
700 // Perform a dry-run of the stored input in case it might reproduce a crash.
701 const len = mem.readInt(u32, f.mmap_input.mmap.memory[0..4], .little);
702 if (len < f.mmap_input.mmap.memory[4..].len) {
703 f.mmap_input.len = len;
704 _ = f.runBytes(f.mmap_input.inputSlice(), .bytes_dry);
705 f.mmap_input.clearRetainingCapacity();
961 // Cull old inputs
962 const ref = t.corpus.items(.ref);
963 var i: usize = t.start_mut_corpus;
964 while (i < t.corpus.len) {
965 if (ref[i].best_i_len == 0) {
966 f.removeInput(@enumFromInt(i));
967 } else {
968 i += 1;
969 }
970 }
706971 }
972
973 t.corpus_pos = @enumFromInt(0);
707974 }
708975
709 pub fn loadCorpus(f: *Fuzzer) void {
710 f.corpus_pos = @enumFromInt(f.corpus.len);
711 f.corpus.append(gpa, .none) catch @panic("OOM"); // Also ensures the corpus is not empty
712 f.start_corpus_dir = @intCast(f.corpus.len);
713 while (true) {
714 var name_buf: [8]u8 = undefined;
715 const name = f.corpusFileName(&name_buf, @enumFromInt(f.corpus.len));
716 const bytes = f.corpus_dir.readFileAlloc(io, name, gpa, .unlimited) catch |e| switch (e) {
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);
976 const CorpusFileName = struct {
977 buf: [Test.dirname_len + 9]u8,
978
979 pub fn fromTest(dirname: [Test.dirname_len]u8) CorpusFileName {
980 var n: CorpusFileName = undefined;
981 n.buf[0..dirname.len].* = dirname;
982 n.buf[dirname.len] = Io.Dir.path.sep;
983 return n;
722984 }
723 f.corpus_pos = @enumFromInt(0);
724 }
725985
726 fn corpusFileName(f: *Fuzzer, buf: *[8]u8, i: Input.Index) []u8 {
727 const dir_i = @intFromEnum(i) - f.start_corpus_dir;
728 return std.fmt.bufPrint(buf, "{x}", .{dir_i}) catch unreachable;
729 }
986 pub fn readLockName(n: *CorpusFileName) []u8 {
987 const basename = "readlock";
988 n.buf[Test.dirname_len + 1 ..][0..basename.len].* = basename.*;
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
7311004 fn rngInt(f: *Fuzzer, T: type) T {
7321005 comptime assert(@bitSizeOf(T) <= 64);
......@@ -749,13 +1022,14 @@ const Fuzzer = struct {
7491022 };
7501023
7511024 fn isFresh(f: *Fuzzer) bool {
1025 const t = &f.tests[f.test_i];
7521026 // Store as a bool instead of returning immediately to aid optimizations
7531027 // by reducing branching since a fresh input is the unlikely case.
7541028 var fresh: bool = false;
7551029
7561030 var n_pcs: u32 = 0;
7571031 var hit_pcs = exec.pcBitsetIterator();
758 for (f.seen_pcs) |seen| {
1032 for (t.seen_pcs) |seen| {
7591033 const hits = hit_pcs.next();
7601034 fresh |= hits & ~seen != 0;
7611035 n_pcs += @popCount(hits);
......@@ -768,7 +1042,7 @@ const Fuzzer = struct {
7681042 .bytes = f.req_bytes,
7691043 },
7701044 };
771 for (f.bests.quality_buf[0..f.bests.len]) |best| {
1045 for (t.bests.quality_buf[0..t.bests.len]) |best| {
7721046 if (exec.pc_counters[best.pc] == 0) continue;
7731047 fresh |= quality.betterLess(best.min) | quality.betterMore(best.max);
7741048 }
......@@ -776,12 +1050,15 @@ const Fuzzer = struct {
7761050 return fresh;
7771051 }
7781052
1053 /// It is the callee's responsibility to reset the corpus pos
1054 ///
7791055 /// Returns if `error.SkipZigTest` was indicated
7801056 fn runBytes(f: *Fuzzer, bytes: []const u8, mode: Input.Index) bool {
7811057 assert(mode == .bytes_dry or mode == .bytes_fresh);
7821058
7831059 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;
7851062 return f.run(0); // 0 since `f.uid_data` is unused
7861063 }
7871064
......@@ -791,89 +1068,112 @@ const Fuzzer = struct {
7911068 exec.shared_seen_pcs[@sizeOf(abi.SeenPcsHeader)..].ptr,
7921069 );
7931070
1071 const t = &f.tests[f.test_i];
7941072 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| {
7961074 const new = hit_pcs.next() & ~seen.*;
7971075 if (new != 0) {
7981076 seen.* |= new;
7991077 _ = @atomicRmw(usize, shared_seen, .Or, new, .monotonic);
1078 t.seen_pc_count += @popCount(new);
8001079 }
8011080 }
8021081 }
8031082
804 fn removeBest(f: *Fuzzer, i: Input.Index, best_i: u32, modify_fs_corpus: bool) void {
805 const ref = &f.corpus.items(.ref)[@intFromEnum(i)];
1083 fn removeBest(f: *Fuzzer, i: Input.Index, best_i: u32) void {
1084 const t = &f.tests[f.test_i];
1085 const ref = &t.corpus.items(.ref)[@intFromEnum(i)];
8061086 const list_i = mem.indexOfScalar(u32, ref.best_i_buf[0..ref.best_i_len], best_i).?;
8071087 ref.best_i_len -= 1;
8081088 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) {
8111091 // The input is no longer valuable, so remove it.
812 var removed_input = f.corpus.get(@intFromEnum(i));
813 for (
814 removed_input.data.uid_slices.keys(),
815 removed_input.data.uid_slices.values(),
816 removed_input.seen_uid_i,
817 ) |uid, slice, seen_uid_i| {
818 switch (uid.kind) {
819 .int => {
820 const seen_ints = &f.seen_uids.values()[seen_uid_i].slices.ints;
821 const removed_ints = removed_input.data.ints[slice.base..][0..slice.len];
822 _ = seen_ints.swapRemove(for (0.., seen_ints.items) |idx, ints| {
823 if (removed_ints.ptr == ints.ptr) {
824 assert(removed_ints.len == ints.len);
825 break idx;
826 }
827 } else unreachable);
828 },
829 .bytes => {
830 const seen_bytes = &f.seen_uids.values()[seen_uid_i].slices.bytes;
831 const removed_bytes: Input.Data.Bytes = .{
832 .entries = removed_input.data.bytes.entries[slice.base..][0..slice.len],
833 .table = removed_input.data.bytes.table,
834 };
835 _ = seen_bytes.swapRemove(for (0.., seen_bytes.items) |idx, bytes| {
836 if (removed_bytes.entries.ptr == bytes.entries.ptr) {
837 assert(removed_bytes.entries.len == bytes.entries.len);
838 assert(removed_bytes.table.ptr == bytes.table.ptr);
839 assert(removed_bytes.table.len == bytes.table.len);
840 break idx;
841 }
842 } else unreachable);
843 },
844 }
1092 f.removeInput(i);
1093 }
1094 }
1095
1096 fn removeInput(f: *Fuzzer, i: Input.Index) void {
1097 const t = &f.tests[f.test_i];
1098 const ref = &t.corpus.items(.ref)[@intFromEnum(i)];
1099 assert(ref.best_i_len == 0 and @intFromEnum(i) >= t.start_mut_corpus);
1100
1101 var removed_input = t.corpus.get(@intFromEnum(i));
1102 for (
1103 removed_input.data.uid_slices.keys(),
1104 removed_input.data.uid_slices.values(),
1105 removed_input.seen_uid_i,
1106 ) |uid, slice, seen_uid_i| {
1107 switch (uid.kind) {
1108 .int => {
1109 const seen_ints = &t.seen_uids.values()[seen_uid_i].slices.ints;
1110 const removed_ints = removed_input.data.ints[slice.base..][0..slice.len];
1111 _ = seen_ints.swapRemove(for (0.., seen_ints.items) |idx, ints| {
1112 if (removed_ints.ptr == ints.ptr) {
1113 assert(removed_ints.len == ints.len);
1114 break idx;
1115 }
1116 } else unreachable);
1117 },
1118 .bytes => {
1119 const seen_bytes = &t.seen_uids.values()[seen_uid_i].slices.bytes;
1120 const removed_bytes: Input.Data.Bytes = .{
1121 .entries = removed_input.data.bytes.entries[slice.base..][0..slice.len],
1122 .table = removed_input.data.bytes.table,
1123 };
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 },
8451133 }
846 removed_input.deinit();
847 f.corpus.swapRemove(@intFromEnum(i));
1134 }
1135 removed_input.deinit();
1136 t.corpus.swapRemove(@intFromEnum(i));
8481137
849 var removed_name_buf: [8]u8 = undefined;
850 const removed_name = f.corpusFileName(&removed_name_buf, i);
1138 if (@intFromEnum(i) != t.corpus.len) {
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) {
853 f.corpus_dir.deleteFile(io, removed_name) catch |e| panic(
854 "failed to remove corpus file '{s}': {t}",
855 .{ removed_name, e },
856 );
857 return; // No item moved so no refs to update
1146 if (@intFromEnum(best.min) == t.corpus.len) best.min = i;
1147 if (@intFromEnum(best.max) == t.corpus.len) best.max = i;
8581148 }
1149 }
8591150
860 var swapped_name_buf: [8]u8 = undefined;
861 const swapped_name = f.corpusFileName(&swapped_name_buf, @enumFromInt(f.corpus.len));
1151 if (!f.main_instance) return;
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(
8641174 "failed to rename corpus file '{s}' to '{s}': {t}",
8651175 .{ swapped_name, removed_name, e },
8661176 );
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 }
8771177 }
8781178 }
8791179
......@@ -881,51 +1181,30 @@ const Fuzzer = struct {
8811181 // All inputs including the corpus are required to go through the memory
8821182 // mapped input in case they cause a crash so they can be identified.
8831183 f.mmap_input.appendSlice(bytes);
884 f.newInput(false);
1184 f.newInput();
8851185 f.mmap_input.clearRetainingCapacity();
8861186 }
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));
8891192 const bytes = f.mmap_input.inputSlice();
8901193 // `error.SkipZigTest` here can be from one of these causes:
891 // * The test has changed and a previous corpus input is being used
892 // * An input provided by the test results in it
1194 // * A previous corpus input after the test has changed
1195 // * An input provided by the test
8931196 // * The test is non-deterministic
8941197 if (f.runBytes(bytes, .bytes_fresh) and
895 modify_fs_corpus // The input is not from the filesystem.
896 // This is required to ensure the filesystem and process corpus are the same.
1198 new_is_mut // The corpus must be mutable at this point for the input to be
1199 // omitted (i.e. test corpus inputs and filesystem inputs cannot be dropped)
8971200 ) {
8981201 f.input_builder.reset();
899 f.corpus_pos = @enumFromInt(0);
1202 t.corpus_pos = @enumFromInt(0);
9001203 return;
9011204 }
1205
9021206 f.req_values = f.input_builder.total_ints + f.input_builder.total_bytes;
9031207 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
9291208 const quality: Input.Best.Quality = .{
9301209 .n_pcs = n_pcs: {
9311210 @setRuntimeSafety(builtin.mode == .Debug); // Necessary for vectorization
......@@ -942,7 +1221,7 @@ const Fuzzer = struct {
9421221 };
9431222
9441223 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| {
9461225 if (exec.pc_counters[best.pc] == 0) continue;
9471226
9481227 const better_min = quality.betterLess(best.min);
......@@ -953,30 +1232,30 @@ const Fuzzer = struct {
9531232 }
9541233 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];
9571236 if (map.min != map.max) {
9581237 if (better_min) {
959 f.removeBest(map.min, @intCast(best_i), modify_fs_corpus);
1238 f.removeBest(map.min, @intCast(best_i));
9601239 }
9611240 if (better_max) {
962 f.removeBest(map.max, @intCast(best_i), modify_fs_corpus);
1241 f.removeBest(map.max, @intCast(best_i));
9631242 }
9641243 } else {
9651244 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));
9671246 }
9681247 }
9691248 }
9701249
9711250 // 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);
9731252 if (input_i == Input.Index.reserved_start) {
9741253 @panic("corpus size limit exceeded");
9751254 }
9761255
9771256 for (best_i_list.items) |i| {
978 const best_qual = &f.bests.quality_buf[i];
979 const best_map = &f.bests.input_buf[i];
1257 const best_qual = &t.bests.quality_buf[i];
1258 const best_map = &t.bests.input_buf[i];
9801259
9811260 if (quality.betterLess(best_qual.min)) {
9821261 best_qual.min = quality;
......@@ -994,42 +1273,74 @@ const Fuzzer = struct {
9941273 continue;
9951274 }
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) {
9981277 @branchHint(.unlikely);
999 best_i_list.append(gpa, f.bests.len) catch @panic("OOM");
1000 f.bests.quality_buf[f.bests.len] = .{
1278 best_i_list.append(gpa, t.bests.len) catch @panic("OOM");
1279 t.bests.quality_buf[t.bests.len] = .{
10011280 .pc = @intCast(i),
10021281 .min = quality,
10031282 .max = quality,
10041283 };
1005 f.bests.input_buf[f.bests.len] = .{ .min = input_i, .max = input_i };
1006 f.bests.len += 1;
1284 t.bests.input_buf[t.bests.len] = .{ .min = input_i, .max = input_i };
1285 t.bests.len += 1;
10071286 }
10081287 }
10091288
1010 if (best_i_list.items.len == 0 and
1011 modify_fs_corpus // Found by freshness; otherwise, it does not need to be better
1012 ) {
1013 @branchHint(.cold); // Nondeterministic test
1014 std.log.warn("nondeterministic rerun", .{});
1289 // Having no best qualities could be from one of these causes:
1290 // * A previous corpus input after the test has changed
1291 // * An input provided by the test
1292 // * The test is non-deterministic
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);
10151297 return;
10161298 }
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
10181324 input.ref.best_i_buf = best_i_list.toOwnedSlice(gpa) catch @panic("OOM");
10191325 input.ref.best_i_len = @intCast(input.ref.best_i_buf.len);
1020 f.corpus.append(gpa, input) catch @panic("OOM");
1021 f.corpus_pos = input_i;
1326 t.corpus.append(gpa, input) catch @panic("OOM");
1327 t.corpus_pos = input_i;
10221328
10231329 // Must come after the above since `seen_pcs` is used
10241330 f.updateSeenPcs();
10251331
1026 if (!modify_fs_corpus) return;
1027
1028 // Write new input to cache
1029 var name_buf: [8]u8 = undefined;
1030 const name = f.corpusFileName(&name_buf, input_i);
1031 f.corpus_dir.writeFile(io, .{ .sub_path = name, .data = bytes }) catch |e|
1032 panic("failed to write corpus file '{s}': {t}", .{ name, e });
1332 t.batches_since_find = 0;
1333 if (f.main_instance and new_is_mut) {
1334 // Only the main instance increments the number of unique runs since it is likely
1335 // multiple instances find the same new input at the same time.
1336 _ = @atomicRmw(usize, &exec.seenPcsHeader().unique_runs, .Add, 1, .monotonic);
1337 // Write new input to the cache
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 }
10331344 }
10341345
10351346 /// Returns if `error.SkipZigTest` was indicated
......@@ -1061,8 +1372,10 @@ const Fuzzer = struct {
10611372
10621373 pub fn cycle(f: *Fuzzer) void {
10631374 assert(f.mmap_input.len == 0);
1064 const corpus = f.corpus.slice();
1065 const corpus_i = @intFromEnum(f.corpus_pos);
1375
1376 const t = &f.tests[f.test_i];
1377 const corpus = t.corpus.slice();
1378 const corpus_i = @intFromEnum(t.corpus_pos);
10661379
10671380 var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) };
10681381 var n_mutate = mutCount(small_entronopy.take(u16));
......@@ -1118,13 +1431,181 @@ const Fuzzer = struct {
11181431 if (!skip and f.isFresh()) {
11191432 @branchHint(.unlikely);
11201433
1121 _ = @atomicRmw(usize, &exec.seenPcsHeader().unique_runs, .Add, 1, .monotonic);
1122 f.newInput(true);
1434 abi.runner_broadcast_input(f.test_i, .fromSlice(f.mmap_input.inputSlice()));
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);
11231439 }
11241440 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);
1127 f.corpus_pos = @enumFromInt((@intFromEnum(f.corpus_pos) + 1) % f.corpus.len);
1588 if (rng.from_latest_find != 0) {
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 }
11281609 }
11291610
11301611 fn weightsContain(int: u64, weights: []const abi.Weight) bool {
......@@ -1184,8 +1665,9 @@ const Fuzzer = struct {
11841665 mutate: Untyped,
11851666 fresh: void,
11861667 } {
1187 const corpus = f.corpus.slice();
1188 const corpus_i = @intFromEnum(f.corpus_pos);
1668 const t = &f.tests[f.test_i];
1669 const corpus = t.corpus.slice();
1670 const corpus_i = @intFromEnum(t.corpus_pos);
11891671 const data = &corpus.items(.data)[corpus_i];
11901672 var small_entronopy: SmallEntronopy = .{ .bits = f.rngInt(u64) };
11911673
......@@ -1276,7 +1758,7 @@ const Fuzzer = struct {
12761758 data_slice.len,
12771759 } else src: {
12781760 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;
12801762 switch (uid.kind) {
12811763 .int => {
12821764 const slices = untyped_slices.ints.items;
......@@ -1404,7 +1886,7 @@ const Fuzzer = struct {
14041886 }
14051887 } else {
14061888 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;
14081890 switch (uid.kind) {
14091891 .int => {
14101892 const slices = untyped_slices.ints.items;
......@@ -1432,11 +1914,12 @@ const Fuzzer = struct {
14321914 }
14331915
14341916 pub fn nextInt(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) u64 {
1917 const t = &f.tests[f.test_i];
14351918 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)) {
14371920 @branchHint(.unlikely);
14381921 const int = f.bytes_input.valueWeightedWithHash(u64, weights, undefined);
1439 if (f.corpus_pos == .bytes_fresh) {
1922 if (t.corpus_pos == .bytes_fresh) {
14401923 f.input_builder.checkSmithedLen(8);
14411924 f.input_builder.addInt(uid, int);
14421925 }
......@@ -1455,11 +1938,12 @@ const Fuzzer = struct {
14551938 }
14561939
14571940 pub fn nextEos(f: *Fuzzer, uid: Uid, weights: []const abi.Weight) bool {
1941 const t = &f.tests[f.test_i];
14581942 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)) {
14601944 @branchHint(.unlikely);
14611945 const eos = f.bytes_input.eosWeightedWithHash(weights, undefined);
1462 if (f.corpus_pos == .bytes_fresh) {
1946 if (t.corpus_pos == .bytes_fresh) {
14631947 f.input_builder.checkSmithedLen(1);
14641948 f.input_builder.addInt(uid, @intFromBool(eos));
14651949 }
......@@ -1569,13 +2053,14 @@ const Fuzzer = struct {
15692053 }
15702054
15712055 pub fn nextBytes(f: *Fuzzer, uid: Uid, out: []u8, weights: []const abi.Weight) void {
2056 const t = &f.tests[f.test_i];
15722057 f.req_values += 1;
15732058 f.req_bytes +%= @truncate(out.len); // This function should panic since the 32-bit
15742059 // 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)) {
15762061 @branchHint(.unlikely);
15772062 f.bytes_input.bytesWeightedWithHash(out, weights, undefined);
1578 if (f.corpus_pos == .bytes_fresh) {
2063 if (t.corpus_pos == .bytes_fresh) {
15792064 f.input_builder.checkSmithedLen(out.len);
15802065 f.input_builder.addBytes(uid, out);
15812066 }
......@@ -1660,8 +2145,9 @@ const Fuzzer = struct {
16602145 len_weights: []const abi.Weight,
16612146 byte_weights: []const abi.Weight,
16622147 ) u32 {
2148 const t = &f.tests[f.test_i];
16632149 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)) {
16652151 @branchHint(.unlikely);
16662152 const n = f.bytes_input.sliceWeightedWithHash(
16672153 buf,
......@@ -1669,7 +2155,7 @@ const Fuzzer = struct {
16692155 byte_weights,
16702156 undefined,
16712157 );
1672 if (f.corpus_pos == .bytes_fresh) {
2158 if (t.corpus_pos == .bytes_fresh) {
16732159 f.input_builder.checkSmithedLen(@as(usize, 4) + n);
16742160 f.input_builder.addBytes(uid, buf[0..n]);
16752161 }
......@@ -1686,7 +2172,6 @@ const Fuzzer = struct {
16862172
16872173export fn fuzzer_init(cache_dir_path: abi.Slice) void {
16882174 exec = .init(cache_dir_path.toSlice());
1689 fuzzer = .init();
16902175}
16912176
16922177export fn fuzzer_coverage() abi.Coverage {
......@@ -1706,23 +2191,66 @@ export fn fuzzer_coverage() abi.Coverage {
17062191 };
17072192}
17082193
1709export fn fuzzer_set_test(test_one: abi.TestOne, unit_test_name: abi.Slice) void {
1710 current_test_name = unit_test_name.toSlice();
1711 fuzzer.setTest(test_one, unit_test_name.toSlice());
2194export fn fuzzer_main(
2195 n_tests: u32,
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;
17122243}
17132244
17142245export fn fuzzer_new_input(bytes: abi.Slice) void {
17152246 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
17162248 fuzzer.newInputExternal(bytes.toSlice());
17172249}
17182250
1719export fn fuzzer_main(limit_kind: abi.LimitKind, amount: u64) void {
1720 fuzzer.loadCorpus();
1721 switch (limit_kind) {
1722 .forever => while (true) fuzzer.cycle(),
1723 .iterations => for (0..amount) |_| fuzzer.cycle(),
1724 }
1725 fuzzer.reset();
2251export fn fuzzer_start_test() void {
2252 fuzzer.ensureCorpusLoaded();
2253 fuzzer.batch();
17262254}
17272255
17282256export fn fuzzer_int(uid: Uid, weights: abi.Weights) u64 {
......@@ -1786,26 +2314,43 @@ export fn __sanitizer_cov_pcs_init(start: usize, end: usize) void {
17862314/// Reusable and recoverable input.
17872315///
17882316/// 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.
17902319const MemoryMappedInput = struct {
2320 const Header = abi.MmapInputHeader;
2321
17912322 len: u32,
17922323 /// 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.
17952324 mmap: Io.File.MemoryMap,
2325 in_i: u32,
17962326
17972327 /// `file` becomes owned by the returned `MemoryMappedInput`
1798 pub fn init(file: Io.File, size: usize) !MemoryMappedInput {
1799 assert(size >= 4);
2328 pub fn init(file: Io.File, instance_id: u32, in_i: u32) MemoryMappedInput {
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 };
18002344 return .{
18012345 .len = 0,
1802 .mmap = try file.createMemoryMap(io, .{ .len = size }),
2346 .mmap = map,
2347 .in_i = in_i,
18032348 };
18042349 }
18052350
18062351 pub fn deinit(l: *MemoryMappedInput) void {
18072352 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 });
18092354 l.mmap.destroy(io);
18102355 f.close(io);
18112356 l.* = undefined;
......@@ -1815,40 +2360,36 @@ const MemoryMappedInput = struct {
18152360 ///
18162361 /// Invalidates element pointers if additional memory is needed.
18172362 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);
18192364 }
18202365
1821 /// If the current capacity is less than `min_capacity`, this function will
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 {
2366 fn ensureSize(l: *MemoryMappedInput, min_capacity: usize) void {
18262367 if (l.mmap.memory.len < min_capacity) {
18272368 @branchHint(.unlikely);
18282369
1829 const max_capacity = 1 << 32; // The size of the length header is not added
2370 const max_capacity = 1 << 32; // The size of the header is not added
18302371 // in order to keep the capacity page aligned and to allow those values to
18312372 // reserved for other places.
18322373 if (min_capacity > max_capacity) @panic("too much smith data requested");
18332374
18342375 const new_capacity = @min(growCapacity(min_capacity), max_capacity);
18352376 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 });
18372378 l.mmap.setLength(io, new_capacity) catch |se| switch (se) {
18382379 error.OperationUnsupported => {
18392380 const f = l.mmap.file;
18402381 l.mmap.destroy(io);
18412382 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 });
18432384 },
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 }),
18452386 };
18462387 }
18472388 }
18482389
18492390 // Only writing has side effects, so volatile is not needed
18502391 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];
18522393 }
18532394
18542395 // Writing has side effectsd, so volatile is necessary
......@@ -1857,7 +2398,13 @@ const MemoryMappedInput = struct {
18572398 }
18582399
18592400 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));
18612408 }
18622409
18632410 /// Invalidates all element pointers.
......@@ -1871,7 +2418,7 @@ const MemoryMappedInput = struct {
18712418 /// Invalidates item pointers if more space is required.
18722419 pub fn appendSlice(l: *MemoryMappedInput, items: []const u8) void {
18732420 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);
18752422 l.len += @as(u32, @intCast(items.len));
18762423 l.writeLen();
18772424 }
......@@ -1881,7 +2428,8 @@ const MemoryMappedInput = struct {
18812428 /// Invalidates item pointers if more space is required.
18822429 pub fn appendLittleInt(l: *MemoryMappedInput, T: type, x: T) void {
18832430 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));
18852433 l.len += @sizeOf(T);
18862434 l.writeLen();
18872435 }
lib/std/Build.zig+3
......@@ -128,6 +128,9 @@ pub const Graph = struct {
128128 random_seed: u32 = 0,
129129 dependency_cache: InitializedDepMap = .empty,
130130 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,
131134 time_report: bool,
132135 /// Similar to the `Io.Terminal.Mode` returned by `Io.lockStderr`, but also
133136 /// respects the '--color' flag.
lib/std/Build/Fuzz.zig+6-19
......@@ -128,7 +128,7 @@ pub fn init(
128128
129129pub fn start(fuzz: *Fuzz) void {
130130 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
133133 if (fuzz.mode == .forever) {
134134 // For polling messages and sending updates to subscribers.
......@@ -137,18 +137,8 @@ pub fn start(fuzz: *Fuzz) void {
137137 }
138138
139139 for (fuzz.run_steps) |run| {
140 if (run.fuzz_tests.items.len > 1) {
141 // Multiple fuzzWorkerRuns currently cause race-conditions
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 }
140 assert(run.rebuilt_executable != null);
141 fuzz.group.async(io, fuzzWorkerRun, .{ fuzz, run });
152142 }
153143}
154144
......@@ -193,16 +183,13 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, parent_prog_nod
193183 run.rebuilt_executable = try rebuilt_bin_path.join(gpa, compile.out_filename);
194184}
195185
196fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run, unit_test_name: []const u8) void {
186fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run) void {
197187 const owner = run.step.owner;
198188 const gpa = owner.allocator;
199189 const graph = owner.graph;
200190 const io = graph.io;
201191
202 const prog_node = fuzz.prog_node.start(unit_test_name, 0);
203 defer prog_node.end();
204
205 run.rerunInFuzzMode(fuzz, unit_test_name, prog_node) catch |err| switch (err) {
192 run.rerunInFuzzMode(fuzz, fuzz.prog_node) catch |err| switch (err) {
206193 error.MakeFailed => {
207194 var buf: [256]u8 = undefined;
208195 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 {
213200 return;
214201 },
215202 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 });
217204 return;
218205 },
219206 };
lib/std/Build/Step/Run.zig+530-68
......@@ -1068,7 +1068,6 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
10681068pub fn rerunInFuzzMode(
10691069 run: *Run,
10701070 fuzz: *std.Build.Fuzz,
1071 unit_test_name: []const u8,
10721071 prog_node: std.Progress.Node,
10731072) !void {
10741073 const step = &run.step;
......@@ -1139,7 +1138,6 @@ pub fn rerunInFuzzMode(
11391138 .unit_test_timeout_ns = null, // don't time out fuzz tests for now
11401139 .gpa = fuzz.gpa,
11411140 }, .{
1142 .unit_test_name = unit_test_name,
11431141 .fuzz = fuzz,
11441142 });
11451143}
......@@ -1211,7 +1209,6 @@ fn termMatches(expected: ?process.Child.Term, actual: process.Child.Term) bool {
12111209
12121210const FuzzContext = struct {
12131211 fuzz: *std.Build.Fuzz,
1214 unit_test_name: []const u8,
12151212};
12161213
12171214fn runCommand(
......@@ -1655,6 +1652,11 @@ fn evalZigTest(
16551652 options: Step.MakeOptions,
16561653 fuzz_context: ?FuzzContext,
16571654) !void {
1655 if (fuzz_context != null) {
1656 try evalFuzzTest(run, spawn_options, options, fuzz_context.?);
1657 return;
1658 }
1659
16581660 const step_owner = run.step.owner;
16591661 const gpa = step_owner.allocator;
16601662 const arena = step_owner.allocator;
......@@ -1693,7 +1695,6 @@ fn evalZigTest(
16931695 run,
16941696 &child,
16951697 options,
1696 fuzz_context,
16971698 &multi_reader,
16981699 &test_metadata,
16991700 &test_results,
......@@ -1815,7 +1816,6 @@ fn waitZigTest(
18151816 run: *Run,
18161817 child: *process.Child,
18171818 options: Step.MakeOptions,
1818 fuzz_context: ?FuzzContext,
18191819 multi_reader: *Io.File.MultiReader,
18201820 opt_metadata: *?TestMetadata,
18211821 results: *Step.TestResults,
......@@ -1837,29 +1837,7 @@ fn waitZigTest(
18371837 var sub_prog_node: ?std.Progress.Node = null;
18381838 defer if (sub_prog_node) |n| n.end();
18391839
1840 if (fuzz_context) |ctx| {
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| {
1840 if (opt_metadata.*) |*md| {
18631841 // Previous unit test process died or was killed; we're continuing where it left off
18641842 requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
18651843 } else {
......@@ -1872,14 +1850,11 @@ fn waitZigTest(
18721850
18731851 var last_update: Io.Clock.Timestamp = .now(io, .awake);
18741852
1875 var coverage_id: ?u64 = null;
1876
18771853 // This timeout is used when we're waiting on the test runner itself rather than a user-specified
18781854 // test. For instance, if the test runner leaves this much time between us requesting a test to
18791855 // start and it acknowledging the test starting, we terminate the child and raise an error. This
18801856 // *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: {
1882 if (fuzz_context != null) break :t null; // don't timeout fuzz tests
1857 const response_timeout: Io.Clock.Duration = t: {
18831858 const ns = @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s);
18841859 break :t .{ .clock = .awake, .raw = .fromNanoseconds(ns) };
18851860 };
......@@ -1947,8 +1922,6 @@ fn waitZigTest(
19471922 );
19481923 },
19491924 .test_metadata => {
1950 assert(fuzz_context == null);
1951
19521925 // `metadata` would only be populated if we'd already seen a `test_metadata`, but we
19531926 // only request it once (and importantly, we don't re-request it if we kill and
19541927 // restart the test runner).
......@@ -1986,7 +1959,6 @@ fn waitZigTest(
19861959 last_update = .now(io, .awake);
19871960 },
19881961 .test_results => {
1989 assert(fuzz_context == null);
19901962 const md = &opt_metadata.*.?;
19911963
19921964 const tr_hdr = body_r.takeStruct(std.zig.Server.Message.TestResults, .little) catch unreachable;
......@@ -2033,44 +2005,523 @@ fn waitZigTest(
20332005
20342006 requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
20352007 },
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 },
20362226 .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;
20382229 const cumulative_runs = body_r.takeInt(u64, .little) catch unreachable;
20392230 const cumulative_unique = body_r.takeInt(u64, .little) catch unreachable;
20402231 const cumulative_coverage = body_r.takeInt(u64, .little) catch unreachable;
20412232
2042 {
2043 const fuzz = fuzz_context.?.fuzz;
2044 fuzz.queue_mutex.lockUncancelable(io);
2045 defer fuzz.queue_mutex.unlock(io);
2046 try fuzz.msg_queue.append(fuzz.gpa, .{ .coverage = .{
2047 .id = coverage_id.?,
2048 .cumulative = .{
2049 .runs = cumulative_runs,
2050 .unique = cumulative_unique,
2051 .coverage = cumulative_coverage,
2052 },
2053 .run = run,
2054 } });
2055 fuzz.queue_cond.signal(io);
2056 }
2233 const fuzz = f.ctx.fuzz;
2234 fuzz.queue_mutex.lockUncancelable(io);
2235 defer fuzz.queue_mutex.unlock(io);
2236 try fuzz.msg_queue.append(fuzz.gpa, .{ .coverage = .{
2237 .id = f.coverage_id.?,
2238 .cumulative = .{
2239 .runs = cumulative_runs,
2240 .unique = cumulative_unique,
2241 .coverage = cumulative_coverage,
2242 },
2243 .run = f.run,
2244 } });
2245 fuzz.queue_cond.signal(io);
20572246 },
20582247 .fuzz_start_addr => {
2059 const fuzz = fuzz_context.?.fuzz;
2248 var body_r: Io.Reader = .fixed(body);
2249 const fuzz = f.ctx.fuzz;
20602250 const addr = body_r.takeInt(u64, .little) catch unreachable;
2061 {
2062 fuzz.queue_mutex.lockUncancelable(io);
2063 defer fuzz.queue_mutex.unlock(io);
2064 try fuzz.msg_queue.append(fuzz.gpa, .{ .entry_point = .{
2065 .addr = addr,
2066 .coverage_id = coverage_id.?,
2067 } });
2068 fuzz.queue_cond.signal(io);
2251
2252 fuzz.queue_mutex.lockUncancelable(io);
2253 defer fuzz.queue_mutex.unlock(io);
2254 try fuzz.msg_queue.append(fuzz.gpa, .{ .entry_point = .{
2255 .addr = addr,
2256 .coverage_id = f.coverage_id.?,
2257 } });
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));
20692279 }
20702280 },
20712281 else => {}, // ignore other messages
20722282 }
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 }
20732306 }
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();
20742525}
20752526
20762527const TestMetadata = struct {
......@@ -2149,30 +2600,41 @@ fn sendRunTestMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag, in
21492600fn sendRunFuzzTestMessage(
21502601 io: Io,
21512602 file: Io.File,
2152 test_name: []const u8,
2603 test_names: []const []const u8,
21532604 kind: std.Build.abi.fuzz.LimitKind,
21542605 amount_or_instance: u64,
21552606) !void {
21562607 const header: std.zig.Client.Message.Header = .{
21572608 .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 },
21592616 };
21602617 var w = file.writerStreaming(io, &.{});
21612618 w.interface.writeStruct(header, .little) catch |err| switch (err) {
21622619 error.WriteFailed => return w.err.?,
21632620 };
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 };
21702621 w.interface.writeByte(@intFromEnum(kind)) catch |err| switch (err) {
21712622 error.WriteFailed => return w.err.?,
21722623 };
21732624 w.interface.writeInt(u64, amount_or_instance, .little) catch |err| switch (err) {
21742625 error.WriteFailed => return w.err.?,
21752626 };
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 }
21762638}
21772639
21782640fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResult {
lib/std/Build/abi.zig+39-7
......@@ -162,15 +162,39 @@ pub const fuzz = struct {
162162 pub extern fn fuzzer_init(cache_dir_path: Slice) void;
163163 /// `fuzzer_init` must be called first.
164164 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 ///
165169 /// `fuzzer_init` must be called first.
166 pub extern fn fuzzer_set_test(test_one: TestOne, unit_test_name: Slice) void;
167 /// `fuzzer_set_test` must be called first.
168 /// The callee owns the memory of bytes and must not free it until `fuzzer_main` returns
170 pub extern fn fuzzer_main(
171 n_tests: u32,
172 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.
169195 pub extern fn fuzzer_new_input(bytes: Slice) void;
170 /// `fuzzer_set_test` must be called first.
171 /// Resets the fuzzer's state to that of `fuzzer_init`.
172 pub extern fn fuzzer_main(limit_kind: LimitKind, amount: u64) void;
173 pub extern fn fuzzer_unslide_address(addr: usize) usize;
196 /// Must be called from inside a test function where `fuzzer_set_test` has been called first.
197 pub extern fn fuzzer_start_test() void;
174198
175199 pub extern fn fuzzer_int(uid: Uid, weights: Weights) u64;
176200 pub extern fn fuzzer_eos(uid: Uid, weights: Weights) bool;
......@@ -337,6 +361,14 @@ pub const fuzz = struct {
337361 }
338362 };
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
340372 /// WebSocket server->client.
341373 ///
342374 /// 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) {
14881488
14891489test Compress {
14901490 const fbufs = try testingFreqBufs();
1491 defer if (!builtin.fuzz) std.testing.allocator.destroy(fbufs);
1491 defer std.testing.allocator.destroy(fbufs);
14921492 try std.testing.fuzz(fbufs, testFuzzedCompressInput, .{});
14931493}
14941494
......@@ -1818,7 +1818,7 @@ pub const Raw = struct {
18181818
18191819test Raw {
18201820 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);
18221822 var prng: std.Random.DefaultPrng = .init(std.testing.random_seed);
18231823 prng.random().bytes(data_buf);
18241824 try std.testing.fuzz(data_buf, testFuzzedRawInput, .{});
......@@ -2491,7 +2491,7 @@ pub const Huffman = struct {
24912491
24922492test Huffman {
24932493 const fbufs = try testingFreqBufs();
2494 defer if (!builtin.fuzz) std.testing.allocator.destroy(fbufs);
2494 defer std.testing.allocator.destroy(fbufs);
24952495 try std.testing.fuzz(fbufs, testFuzzedHuffmanInput, .{});
24962496}
24972497
lib/std/zig/Client.zig+8-3
......@@ -33,13 +33,18 @@ pub const Message = struct {
3333 /// Ask the test runner to run a particular test.
3434 /// The message body is a u32 test index.
3535 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 ///
3739 /// The message body is:
38 /// - a u32 test name len.
39 /// - a test name with the above length
4040 /// - a u8 test limit kind (std.Build.api.fuzz.LimitKind)
4141 /// - 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
4245 start_fuzzing,
46 /// The message body has the same format as in Server.
47 new_fuzz_input,
4348
4449 _,
4550 };
lib/std/zig/Server.zig+26
......@@ -60,6 +60,13 @@ pub const Message = struct {
6060 /// address of the fuzz unit test. This is used to provide a starting
6161 /// point to view coverage.
6262 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,
6370 /// Body is a TimeReport.
6471 time_report,
6572
......@@ -176,6 +183,15 @@ pub fn serveMessageHeader(s: *const Server, header: OutMessage.Header) !void {
176183 try s.out.writeStruct(header, .little);
177184}
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
179195pub fn serveU64Message(s: *const Server, tag: OutMessage.Tag, int: u64) !void {
180196 assert(tag != .coverage_id);
181197 try serveMessageHeader(s, .{
......@@ -198,6 +214,16 @@ pub fn serveCoverageIdMessage(s: *const Server, id: u64, runs: u64, unique: u64,
198214 try s.out.flush();
199215}
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
201227pub fn serveEmitDigest(
202228 s: *Server,
203229 digest: *const [Cache.bin_digest_len]u8,
test/standalone/libfuzzer/main.zig+33-3
......@@ -1,4 +1,5 @@
11const std = @import("std");
2const assert = std.debug.assert;
23const abi = std.Build.abi.fuzz;
34const native_endian = @import("builtin").cpu.arch.endian();
45
......@@ -6,6 +7,37 @@ fn testOne() callconv(.c) bool {
67 return false;
78}
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
941pub fn main(init: std.process.Init) !void {
1042 const gpa = init.gpa;
1143 const io = init.io;
......@@ -19,9 +51,7 @@ pub fn main(init: std.process.Init) !void {
1951 defer cache_dir.close(io);
2052
2153 abi.fuzzer_init(.fromSlice(cache_dir_path));
22 abi.fuzzer_set_test(testOne, .fromSlice("test"));
23 abi.fuzzer_new_input(.fromSlice(""));
24 abi.fuzzer_new_input(.fromSlice("hello"));
54 abi.fuzzer_main(1, 0, .iterations, 100);
2555
2656 const pc_digest = abi.fuzzer_coverage().id;
2757 const coverage_file_path = "v/" ++ std.fmt.hex(pc_digest);