authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-12 20:25:02+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-12 20:25:02+01:00
logd5bfa657c48e9d023bb789fbf8dacbcbd42f528d
treeb5934c60fcecd60f3710594c94d0f26cbcf20ab4
parenteff332fd042a4ef556409c7650c6b74da0e30235
parent9d1e47d220a50fc5db4b622bd86e2fe51673c163

Merge pull request 'fix several fuzzing bugs' (#31470) from gooncreeper/zig:fuzzing-fixes into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31470 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

8 files changed, 103 insertions(+), 50 deletions(-)

lib/build-web/fuzz.zig+1-1
...@@ -255,7 +255,7 @@ fn unpackSourcesInner(tar_bytes: []u8) !void {...@@ -255,7 +255,7 @@ fn unpackSourcesInner(tar_bytes: []u8) !void {
255}255}
256256
257fn updateStats() error{OutOfMemory}!void {257fn updateStats() error{OutOfMemory}!void {
258 @setFloatMode(.optimized);258 // No @setFloatMode(.optimized) since some stats may be at zero and lead to divisions by zero
259259
260 if (recent_coverage_update.items.len == 0) return;260 if (recent_coverage_update.items.len == 0) return;
261261
lib/compiler/test_runner.zig+20-3
...@@ -180,7 +180,23 @@ fn mainServer(init: std.process.Init.Minimal) !void {...@@ -180,7 +180,23 @@ fn mainServer(init: std.process.Init.Minimal) !void {
180 // since they are not present.180 // since they are not present.
181 if (!builtin.fuzz) unreachable;181 if (!builtin.fuzz) unreachable;
182182
183 const index = try server.receiveBody_u32();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 }
199 });
184 const mode: fuzz_abi.LimitKind = @enumFromInt(try server.receiveBody_u8());200 const mode: fuzz_abi.LimitKind = @enumFromInt(try server.receiveBody_u8());
185 const amount_or_instance = try server.receiveBody_u64();201 const amount_or_instance = try server.receiveBody_u64();
186202
...@@ -406,13 +422,13 @@ pub fn fuzz(...@@ -406,13 +422,13 @@ pub fn fuzz(
406 const global = struct {422 const global = struct {
407 var ctx: @TypeOf(context) = undefined;423 var ctx: @TypeOf(context) = undefined;
408424
409 fn test_one() callconv(.c) void {425 fn test_one() callconv(.c) bool {
410 @disableInstrumentation();426 @disableInstrumentation();
411 testing.allocator_instance = .{};427 testing.allocator_instance = .{};
412 defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);428 defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);
413 log_err_count = 0;429 log_err_count = 0;
414 testOne(ctx, @constCast(&testing.Smith{ .in = null })) catch |err| switch (err) {430 testOne(ctx, @constCast(&testing.Smith{ .in = null })) catch |err| switch (err) {
415 error.SkipZigTest => return,431 error.SkipZigTest => return true,
416 else => {432 else => {
417 const stderr = std.debug.lockStderr(&.{}).terminal();433 const stderr = std.debug.lockStderr(&.{}).terminal();
418 p: {434 p: {
...@@ -429,6 +445,7 @@ pub fn fuzz(...@@ -429,6 +445,7 @@ pub fn fuzz(
429 stderr.writer.print("error logs detected\n", .{}) catch {};445 stderr.writer.print("error logs detected\n", .{}) catch {};
430 std.process.exit(1);446 std.process.exit(1);
431 }447 }
448 return false;
432 }449 }
433 };450 };
434 if (builtin.fuzz) {451 if (builtin.fuzz) {
lib/fuzzer.zig+44-18
...@@ -686,7 +686,7 @@ const Fuzzer = struct {...@@ -686,7 +686,7 @@ const Fuzzer = struct {
686 const len = mem.readInt(u32, f.mmap_input.mmap.memory[0..4], .little);686 const len = mem.readInt(u32, f.mmap_input.mmap.memory[0..4], .little);
687 if (len < f.mmap_input.mmap.memory[4..].len) {687 if (len < f.mmap_input.mmap.memory[4..].len) {
688 f.mmap_input.len = len;688 f.mmap_input.len = len;
689 f.runBytes(f.mmap_input.inputSlice(), .bytes_dry);689 _ = f.runBytes(f.mmap_input.inputSlice(), .bytes_dry);
690 f.mmap_input.clearRetainingCapacity();690 f.mmap_input.clearRetainingCapacity();
691 }691 }
692 }692 }
...@@ -703,7 +703,7 @@ const Fuzzer = struct {...@@ -703,7 +703,7 @@ const Fuzzer = struct {
703 else => panic("failed to read corpus file '{s}': {t}", .{ name, e }),703 else => panic("failed to read corpus file '{s}': {t}", .{ name, e }),
704 };704 };
705 defer gpa.free(bytes);705 defer gpa.free(bytes);
706 f.newInput(bytes, false);706 f.newInputExternal(bytes);
707 }707 }
708 f.corpus_pos = @enumFromInt(0);708 f.corpus_pos = @enumFromInt(0);
709 }709 }
...@@ -761,12 +761,13 @@ const Fuzzer = struct {...@@ -761,12 +761,13 @@ const Fuzzer = struct {
761 return fresh;761 return fresh;
762 }762 }
763763
764 fn runBytes(f: *Fuzzer, bytes: []const u8, mode: Input.Index) void {764 /// Returns if `error.SkipZigTest` was indicated
765 fn runBytes(f: *Fuzzer, bytes: []const u8, mode: Input.Index) bool {
765 assert(mode == .bytes_dry or mode == .bytes_fresh);766 assert(mode == .bytes_dry or mode == .bytes_fresh);
766767
767 f.bytes_input = .{ .in = bytes };768 f.bytes_input = .{ .in = bytes };
768 f.corpus_pos = mode;769 f.corpus_pos = mode;
769 f.run(0); // 0 since `f.uid_data` is unused770 return f.run(0); // 0 since `f.uid_data` is unused
770 }771 }
771772
772 fn updateSeenPcs(f: *Fuzzer) void {773 fn updateSeenPcs(f: *Fuzzer) void {
...@@ -861,8 +862,21 @@ const Fuzzer = struct {...@@ -861,8 +862,21 @@ const Fuzzer = struct {
861 }862 }
862 }863 }
863864
864 pub fn newInput(f: *Fuzzer, bytes: []const u8, modify_fs_corpus: bool) void {865 pub fn newInputExternal(f: *Fuzzer, bytes: []const u8) void {
865 f.runBytes(bytes, .bytes_fresh);866 // All inputs including the corpus are required to go through the memory
867 // mapped input in case they cause a crash so they can be identified.
868 f.mmap_input.appendSlice(bytes);
869 f.newInput(false);
870 f.mmap_input.clearRetainingCapacity();
871 }
872
873 fn newInput(f: *Fuzzer, modify_fs_corpus: bool) void {
874 const bytes = f.mmap_input.inputSlice();
875 // `error.SkipZigTest` here can be from one of these causes:
876 // * The test has changed and a previous corpus input is being used
877 // * An input provided by the test results in it
878 // * The test is non-deterministic
879 if (f.runBytes(bytes, .bytes_fresh)) return;
866 f.req_values = f.input_builder.total_ints + f.input_builder.total_bytes;880 f.req_values = f.input_builder.total_ints + f.input_builder.total_bytes;
867 f.req_bytes = @intCast(f.input_builder.bytes_table.items.len);881 f.req_bytes = @intCast(f.input_builder.bytes_table.items.len);
868 var input = f.input_builder.build();882 var input = f.input_builder.build();
...@@ -996,15 +1010,17 @@ const Fuzzer = struct {...@@ -996,15 +1010,17 @@ const Fuzzer = struct {
996 panic("failed to write corpus file '{s}': {t}", .{ name, e });1010 panic("failed to write corpus file '{s}': {t}", .{ name, e });
997 }1011 }
9981012
999 fn run(f: *Fuzzer, input_uids: usize) void {1013 /// Returns if `error.SkipZigTest` was indicated
1014 fn run(f: *Fuzzer, input_uids: usize) bool {
1000 @memset(exec.pc_counters, 0);1015 @memset(exec.pc_counters, 0);
1001 f.uid_data_i.items.len = input_uids;1016 f.uid_data_i.items.len = input_uids;
1002 @memset(f.uid_data_i.items, 0);1017 @memset(f.uid_data_i.items, 0);
1003 f.req_values = 0;1018 f.req_values = 0;
1004 f.req_bytes = 0;1019 f.req_bytes = 0;
10051020
1006 f.test_one();1021 const skip = f.test_one();
1007 _ = @atomicRmw(usize, &exec.seenPcsHeader().n_runs, .Add, 1, .monotonic);1022 _ = @atomicRmw(usize, &exec.seenPcsHeader().n_runs, .Add, 1, .monotonic);
1023 return skip;
1008 }1024 }
10091025
1010 /// Returns a number of mutations to perform from 1-41026 /// Returns a number of mutations to perform from 1-4
...@@ -1076,12 +1092,12 @@ const Fuzzer = struct {...@@ -1076,12 +1092,12 @@ const Fuzzer = struct {
1076 i.* = data.order[order_i];1092 i.* = data.order[order_i];
1077 };1093 };
10781094
1079 f.run(data.uid_slices.entries.len);1095 const skip = f.run(data.uid_slices.entries.len);
1080 if (f.isFresh()) {1096 if (!skip and f.isFresh()) {
1081 @branchHint(.unlikely);1097 @branchHint(.unlikely);
10821098
1083 _ = @atomicRmw(usize, &exec.seenPcsHeader().unique_runs, .Add, 1, .monotonic);1099 _ = @atomicRmw(usize, &exec.seenPcsHeader().unique_runs, .Add, 1, .monotonic);
1084 f.newInput(f.mmap_input.inputSlice(), true);1100 f.newInput(true);
1085 }1101 }
1086 f.mmap_input.clearRetainingCapacity();1102 f.mmap_input.clearRetainingCapacity();
10871103
...@@ -1320,13 +1336,23 @@ const Fuzzer = struct {...@@ -1320,13 +1336,23 @@ const Fuzzer = struct {
13201336
1321 if (opts.copy != 0) {1337 if (opts.copy != 0) {
1322 if (opts.fresh == 0 or slice_i == data_slice.len) return .fresh;1338 if (opts.fresh == 0 or slice_i == data_slice.len) return .fresh;
1323 return .{ .mutate = switch (uid.kind) {1339 switch (uid.kind) {
1324 .int => .{ .int = data.ints[data_i] },1340 .int => {
1325 .bytes => .{ .bytes = b: {1341 const int = data.ints[data_i];
1342 if (weightsContain(int, weights)) {
1343 @branchHint(.likely);
1344 return .{ .mutate = .{ .int = int } };
1345 }
1346 },
1347 .bytes => {
1326 const entry = data.bytes.entries[data_i];1348 const entry = data.bytes.entries[data_i];
1327 break :b data.bytes.table[entry.off..][0..entry.len];1349 const bytes = data.bytes.table[entry.off..][0..entry.len];
1328 } },1350 if (weightsContainBytes(bytes, weights)) {
1329 } };1351 @branchHint(.likely);
1352 return .{ .mutate = .{ .bytes = bytes } };
1353 }
1354 },
1355 }
1330 }1356 }
13311357
1332 if (!opts.splice) {1358 if (!opts.splice) {
...@@ -1665,7 +1691,7 @@ export fn fuzzer_set_test(test_one: abi.TestOne, unit_test_name: abi.Slice) void...@@ -1665,7 +1691,7 @@ export fn fuzzer_set_test(test_one: abi.TestOne, unit_test_name: abi.Slice) void
16651691
1666export fn fuzzer_new_input(bytes: abi.Slice) void {1692export fn fuzzer_new_input(bytes: abi.Slice) void {
1667 if (bytes.len == 0) return; // An entry of length zero is always present1693 if (bytes.len == 0) return; // An entry of length zero is always present
1668 fuzzer.newInput(bytes.toSlice(), false);1694 fuzzer.newInputExternal(bytes.toSlice());
1669}1695}
16701696
1671export fn fuzzer_main(limit_kind: abi.LimitKind, amount: u64) void {1697export fn fuzzer_main(limit_kind: abi.LimitKind, amount: u64) void {
lib/std/Build/Fuzz.zig+7-8
...@@ -145,9 +145,9 @@ pub fn start(fuzz: *Fuzz) void {...@@ -145,9 +145,9 @@ pub fn start(fuzz: *Fuzz) void {
145 }145 }
146146
147 for (fuzz.run_steps) |run| {147 for (fuzz.run_steps) |run| {
148 for (run.fuzz_tests.items) |unit_test_index| {148 for (run.fuzz_tests.items) |unit_test_name| {
149 assert(run.rebuilt_executable != null);149 assert(run.rebuilt_executable != null);
150 fuzz.group.async(io, fuzzWorkerRun, .{ fuzz, run, unit_test_index });150 fuzz.group.async(io, fuzzWorkerRun, .{ fuzz, run, unit_test_name });
151 }151 }
152 }152 }
153}153}
...@@ -193,17 +193,16 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, parent_prog_nod...@@ -193,17 +193,16 @@ fn rebuildTestsWorkerRunFallible(run: *Step.Run, gpa: Allocator, parent_prog_nod
193 run.rebuilt_executable = try rebuilt_bin_path.join(gpa, compile.out_filename);193 run.rebuilt_executable = try rebuilt_bin_path.join(gpa, compile.out_filename);
194}194}
195195
196fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run, unit_test_index: u32) void {196fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run, unit_test_name: []const u8) void {
197 const owner = run.step.owner;197 const owner = run.step.owner;
198 const gpa = owner.allocator;198 const gpa = owner.allocator;
199 const graph = owner.graph;199 const graph = owner.graph;
200 const io = graph.io;200 const io = graph.io;
201 const test_name = run.cached_test_metadata.?.testName(unit_test_index);
202201
203 const prog_node = fuzz.prog_node.start(test_name, 0);202 const prog_node = fuzz.prog_node.start(unit_test_name, 0);
204 defer prog_node.end();203 defer prog_node.end();
205204
206 run.rerunInFuzzMode(fuzz, unit_test_index, prog_node) catch |err| switch (err) {205 run.rerunInFuzzMode(fuzz, unit_test_name, prog_node) catch |err| switch (err) {
207 error.MakeFailed => {206 error.MakeFailed => {
208 var buf: [256]u8 = undefined;207 var buf: [256]u8 = undefined;
209 const stderr = io.lockStderr(&buf, graph.stderr_mode) catch |e| switch (e) {208 const stderr = io.lockStderr(&buf, graph.stderr_mode) catch |e| switch (e) {
...@@ -214,7 +213,7 @@ fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run, unit_test_index: u32) void {...@@ -214,7 +213,7 @@ fn fuzzWorkerRun(fuzz: *Fuzz, run: *Step.Run, unit_test_index: u32) void {
214 return;213 return;
215 },214 },
216 else => {215 else => {
217 log.err("step '{s}': failed to rerun '{s}' in fuzz mode: {t}", .{ run.step.name, test_name, err });216 log.err("step '{s}': failed to rerun '{s}' in fuzz mode: {t}", .{ run.step.name, unit_test_name, err });
218 return;217 return;
219 },218 },
220 };219 };
...@@ -588,7 +587,7 @@ pub fn waitAndPrintReport(fuzz: *Fuzz) Io.Cancelable!void {...@@ -588,7 +587,7 @@ pub fn waitAndPrintReport(fuzz: *Fuzz) Io.Cancelable!void {
588 \\587 \\
589 , .{588 , .{
590 cov.run.step.name,589 cov.run.step.name,
591 cov.run.cached_test_metadata.?.testName(cov.run.fuzz_tests.items[0]),590 cov.run.fuzz_tests.items[0],
592 cov.id,591 cov.id,
593 cov.cumulative.runs,592 cov.cumulative.runs,
594 header.n_runs,593 header.n_runs,
lib/std/Build/Step/Run.zig+18-14
...@@ -88,9 +88,10 @@ dep_output_file: ?*Output,...@@ -88,9 +88,10 @@ dep_output_file: ?*Output,
8888
89has_side_effects: bool,89has_side_effects: bool,
9090
91/// If this is a Zig unit test binary, this tracks the indexes of the unit91/// If this is a Zig unit test binary, this tracks the names of the unit
92/// tests that are also fuzz tests.92/// tests that are also fuzz tests. Indexes cannot be used as they may
93fuzz_tests: std.ArrayList(u32),93/// change between reruns.
94fuzz_tests: std.ArrayList([]const u8),
94cached_test_metadata: ?CachedTestMetadata = null,95cached_test_metadata: ?CachedTestMetadata = null,
9596
96/// Populated during the fuzz phase if this run step corresponds to a unit test97/// Populated during the fuzz phase if this run step corresponds to a unit test
...@@ -1067,7 +1068,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -1067,7 +1068,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
1067pub fn rerunInFuzzMode(1068pub fn rerunInFuzzMode(
1068 run: *Run,1069 run: *Run,
1069 fuzz: *std.Build.Fuzz,1070 fuzz: *std.Build.Fuzz,
1070 unit_test_index: u32,1071 unit_test_name: []const u8,
1071 prog_node: std.Progress.Node,1072 prog_node: std.Progress.Node,
1072) !void {1073) !void {
1073 const step = &run.step;1074 const step = &run.step;
...@@ -1138,7 +1139,7 @@ pub fn rerunInFuzzMode(...@@ -1138,7 +1139,7 @@ pub fn rerunInFuzzMode(
1138 .unit_test_timeout_ns = null, // don't time out fuzz tests for now1139 .unit_test_timeout_ns = null, // don't time out fuzz tests for now
1139 .gpa = fuzz.gpa,1140 .gpa = fuzz.gpa,
1140 }, .{1141 }, .{
1141 .unit_test_index = unit_test_index,1142 .unit_test_name = unit_test_name,
1142 .fuzz = fuzz,1143 .fuzz = fuzz,
1143 });1144 });
1144}1145}
...@@ -1210,7 +1211,7 @@ fn termMatches(expected: ?process.Child.Term, actual: process.Child.Term) bool {...@@ -1210,7 +1211,7 @@ fn termMatches(expected: ?process.Child.Term, actual: process.Child.Term) bool {
12101211
1211const FuzzContext = struct {1212const FuzzContext = struct {
1212 fuzz: *std.Build.Fuzz,1213 fuzz: *std.Build.Fuzz,
1213 unit_test_index: u32,1214 unit_test_name: []const u8,
1214};1215};
12151216
1216fn runCommand(1217fn runCommand(
...@@ -1843,7 +1844,7 @@ fn waitZigTest(...@@ -1843,7 +1844,7 @@ fn waitZigTest(
1843 sendRunFuzzTestMessage(1844 sendRunFuzzTestMessage(
1844 io,1845 io,
1845 child.stdin.?,1846 child.stdin.?,
1846 ctx.unit_test_index,1847 ctx.unit_test_name,
1847 .forever,1848 .forever,
1848 0, // instance ID; will be used by multiprocess forever fuzzing in the future1849 0, // instance ID; will be used by multiprocess forever fuzzing in the future
1849 ) catch |err| return .{ .write_failed = err };1850 ) catch |err| return .{ .write_failed = err };
...@@ -1852,7 +1853,7 @@ fn waitZigTest(...@@ -1852,7 +1853,7 @@ fn waitZigTest(
1852 sendRunFuzzTestMessage(1853 sendRunFuzzTestMessage(
1853 io,1854 io,
1854 child.stdin.?,1855 child.stdin.?,
1855 ctx.unit_test_index,1856 ctx.unit_test_name,
1856 .iterations,1857 .iterations,
1857 limit.amount,1858 limit.amount,
1858 ) catch |err| return .{ .write_failed = err };1859 ) catch |err| return .{ .write_failed = err };
...@@ -2001,10 +2002,10 @@ fn waitZigTest(...@@ -2001,10 +2002,10 @@ fn waitZigTest(
2001 results.leak_count +|= leak_count;2002 results.leak_count +|= leak_count;
2002 results.log_err_count +|= log_err_count;2003 results.log_err_count +|= log_err_count;
20032004
2004 if (tr_hdr.flags.fuzz) try run.fuzz_tests.append(gpa, tr_hdr.index);2005 if (tr_hdr.flags.fuzz) try run.fuzz_tests.append(gpa, md.testName(tr_hdr.index));
20052006
2006 if (tr_hdr.flags.status == .fail) {2007 if (tr_hdr.flags.status == .fail) {
2007 const name = std.mem.sliceTo(md.testName(tr_hdr.index), 0);2008 const name = md.testName(tr_hdr.index);
2008 const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");2009 const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");
2009 stderr.tossBuffered();2010 stderr.tossBuffered();
2010 if (stderr_bytes.len == 0) {2011 if (stderr_bytes.len == 0) {
...@@ -2013,12 +2014,12 @@ fn waitZigTest(...@@ -2013,12 +2014,12 @@ fn waitZigTest(
2013 try run.step.addError("'{s}' failed:\n{s}", .{ name, stderr_bytes });2014 try run.step.addError("'{s}' failed:\n{s}", .{ name, stderr_bytes });
2014 }2015 }
2015 } else if (leak_count > 0) {2016 } else if (leak_count > 0) {
2016 const name = std.mem.sliceTo(md.testName(tr_hdr.index), 0);2017 const name = md.testName(tr_hdr.index);
2017 const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");2018 const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");
2018 stderr.tossBuffered();2019 stderr.tossBuffered();
2019 try run.step.addError("'{s}' leaked {d} allocations:\n{s}", .{ name, leak_count, stderr_bytes });2020 try run.step.addError("'{s}' leaked {d} allocations:\n{s}", .{ name, leak_count, stderr_bytes });
2020 } else if (log_err_count > 0) {2021 } else if (log_err_count > 0) {
2021 const name = std.mem.sliceTo(md.testName(tr_hdr.index), 0);2022 const name = md.testName(tr_hdr.index);
2022 const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");2023 const stderr_bytes = std.mem.trim(u8, stderr.buffered(), "\n");
2023 stderr.tossBuffered();2024 stderr.tossBuffered();
2024 try run.step.addError("'{s}' logged {d} errors:\n{s}", .{ name, log_err_count, stderr_bytes });2025 try run.step.addError("'{s}' logged {d} errors:\n{s}", .{ name, log_err_count, stderr_bytes });
...@@ -2148,7 +2149,7 @@ fn sendRunTestMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag, in...@@ -2148,7 +2149,7 @@ fn sendRunTestMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag, in
2148fn sendRunFuzzTestMessage(2149fn sendRunFuzzTestMessage(
2149 io: Io,2150 io: Io,
2150 file: Io.File,2151 file: Io.File,
2151 index: u32,2152 test_name: []const u8,
2152 kind: std.Build.abi.fuzz.LimitKind,2153 kind: std.Build.abi.fuzz.LimitKind,
2153 amount_or_instance: u64,2154 amount_or_instance: u64,
2154) !void {2155) !void {
...@@ -2160,7 +2161,10 @@ fn sendRunFuzzTestMessage(...@@ -2160,7 +2161,10 @@ fn sendRunFuzzTestMessage(
2160 w.interface.writeStruct(header, .little) catch |err| switch (err) {2161 w.interface.writeStruct(header, .little) catch |err| switch (err) {
2161 error.WriteFailed => return w.err.?,2162 error.WriteFailed => return w.err.?,
2162 };2163 };
2163 w.interface.writeInt(u32, index, .little) catch |err| switch (err) {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) {
2164 error.WriteFailed => return w.err.?,2168 error.WriteFailed => return w.err.?,
2165 };2169 };
2166 w.interface.writeByte(@intFromEnum(kind)) catch |err| switch (err) {2170 w.interface.writeByte(@intFromEnum(kind)) catch |err| switch (err) {
lib/std/Build/abi.zig+8-4
...@@ -139,7 +139,8 @@ pub const Rebuild = extern struct {...@@ -139,7 +139,8 @@ pub const Rebuild = extern struct {
139139
140/// ABI bits specifically relating to the fuzzer interface.140/// ABI bits specifically relating to the fuzzer interface.
141pub const fuzz = struct {141pub const fuzz = struct {
142 pub const TestOne = *const fn () callconv(.c) void;142 /// Returns if `error.SkipZigTest` was indicated
143 pub const TestOne = *const fn () callconv(.c) bool;
143144
144 /// A unique value to identify the related requests across runs145 /// A unique value to identify the related requests across runs
145 pub const Uid = packed struct(u32) {146 pub const Uid = packed struct(u32) {
...@@ -249,9 +250,12 @@ pub const fuzz = struct {...@@ -249,9 +250,12 @@ pub const fuzz = struct {
249 }250 }
250 // Reject types that don't have a fixed bitsize (esp. usize)251 // Reject types that don't have a fixed bitsize (esp. usize)
251 // since they are not gauraunteed to fit in a u64 across targets.252 // since they are not gauraunteed to fit in a u64 across targets.
252 if (std.mem.indexOfScalar(type, &.{253 //
253 usize, c_char, c_ushort, c_uint, c_ulong, c_ulonglong,254 // std.mem.indexOfScalar is not used to avoid backward branches
254 }, T) != null) {255 // and preserve the eval branch quota.
256 if (T == usize or T == c_char or T == c_ushort or
257 T == c_uint or T == c_ulong or T == c_ulonglong)
258 {
255 @compileError("type does not have a fixed bitsize: " ++ @typeName(T));259 @compileError("type does not have a fixed bitsize: " ++ @typeName(T));
256 }260 }
257 }261 }
lib/std/zig/Client.zig+2-1
...@@ -35,7 +35,8 @@ pub const Message = struct {...@@ -35,7 +35,8 @@ pub const Message = struct {
35 run_test,35 run_test,
36 /// Ask the test runner to start fuzzing a particular test forever or for a given amount of time/iterations.36 /// Ask the test runner to start fuzzing a particular test forever or for a given amount of time/iterations.
37 /// The message body is:37 /// The message body is:
38 /// - a u32 test index.38 /// - a u32 test name len.
39 /// - a test name with the above length
39 /// - a u8 test limit kind (std.Build.api.fuzz.LimitKind)40 /// - a u8 test limit kind (std.Build.api.fuzz.LimitKind)
40 /// - a u64 value whose meaning depends on FuzzLimitKind (either a limit amount or an instance id)41 /// - a u64 value whose meaning depends on FuzzLimitKind (either a limit amount or an instance id)
41 start_fuzzing,42 start_fuzzing,
test/standalone/libfuzzer/main.zig+3-1
...@@ -2,7 +2,9 @@ const std = @import("std");...@@ -2,7 +2,9 @@ const std = @import("std");
2const abi = std.Build.abi.fuzz;2const abi = std.Build.abi.fuzz;
3const native_endian = @import("builtin").cpu.arch.endian();3const native_endian = @import("builtin").cpu.arch.endian();
44
5fn testOne() callconv(.c) void {}5fn testOne() callconv(.c) bool {
6 return false;
7}
68
7pub fn main(init: std.process.Init) !void {9pub fn main(init: std.process.Init) !void {
8 const gpa = init.gpa;10 const gpa = init.gpa;