authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-25 18:52:39-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-07-25 18:52:39-07:00
logafddfe25d80ee4db930ba746f25264286af6d325
tree13c7fa62cbe65047da0cd109784ef417e92bd6ae
parent1c35e73b614398529782f8c027366c6d8d51ac4b
parent688c2df6464bd10a2dcfdf49e89c313e01da9991
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #20773 from ziglang/fuzz

integrate fuzz testing into the build system

15 files changed, 663 insertions(+), 76 deletions(-)

lib/compiler/build_runner.zig+33-12
...@@ -9,8 +9,10 @@ const ArrayList = std.ArrayList;...@@ -9,8 +9,10 @@ const ArrayList = std.ArrayList;
9const File = std.fs.File;9const File = std.fs.File;
10const Step = std.Build.Step;10const Step = std.Build.Step;
11const Watch = std.Build.Watch;11const Watch = std.Build.Watch;
12const Fuzz = std.Build.Fuzz;
12const Allocator = std.mem.Allocator;13const Allocator = std.mem.Allocator;
13const fatal = std.zig.fatal;14const fatal = std.process.fatal;
15const runner = @This();
1416
15pub const root = @import("@build");17pub const root = @import("@build");
16pub const dependencies = @import("@dependencies");18pub const dependencies = @import("@dependencies");
...@@ -102,6 +104,7 @@ pub fn main() !void {...@@ -102,6 +104,7 @@ pub fn main() !void {
102 var steps_menu = false;104 var steps_menu = false;
103 var output_tmp_nonce: ?[16]u8 = null;105 var output_tmp_nonce: ?[16]u8 = null;
104 var watch = false;106 var watch = false;
107 var fuzz = false;
105 var debounce_interval_ms: u16 = 50;108 var debounce_interval_ms: u16 = 50;
106109
107 while (nextArg(args, &arg_idx)) |arg| {110 while (nextArg(args, &arg_idx)) |arg| {
...@@ -205,6 +208,8 @@ pub fn main() !void {...@@ -205,6 +208,8 @@ pub fn main() !void {
205 try debug_log_scopes.append(next_arg);208 try debug_log_scopes.append(next_arg);
206 } else if (mem.eql(u8, arg, "--debug-pkg-config")) {209 } else if (mem.eql(u8, arg, "--debug-pkg-config")) {
207 builder.debug_pkg_config = true;210 builder.debug_pkg_config = true;
211 } else if (mem.eql(u8, arg, "--debug-rt")) {
212 graph.debug_compiler_runtime_libs = true;
208 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {213 } else if (mem.eql(u8, arg, "--debug-compile-errors")) {
209 builder.debug_compile_errors = true;214 builder.debug_compile_errors = true;
210 } else if (mem.eql(u8, arg, "--system")) {215 } else if (mem.eql(u8, arg, "--system")) {
...@@ -234,6 +239,8 @@ pub fn main() !void {...@@ -234,6 +239,8 @@ pub fn main() !void {
234 prominent_compile_errors = true;239 prominent_compile_errors = true;
235 } else if (mem.eql(u8, arg, "--watch")) {240 } else if (mem.eql(u8, arg, "--watch")) {
236 watch = true;241 watch = true;
242 } else if (mem.eql(u8, arg, "--fuzz")) {
243 fuzz = true;
237 } else if (mem.eql(u8, arg, "-fincremental")) {244 } else if (mem.eql(u8, arg, "-fincremental")) {
238 graph.incremental = true;245 graph.incremental = true;
239 } else if (mem.eql(u8, arg, "-fno-incremental")) {246 } else if (mem.eql(u8, arg, "-fno-incremental")) {
...@@ -353,6 +360,7 @@ pub fn main() !void {...@@ -353,6 +360,7 @@ pub fn main() !void {
353 .max_rss_mutex = .{},360 .max_rss_mutex = .{},
354 .skip_oom_steps = skip_oom_steps,361 .skip_oom_steps = skip_oom_steps,
355 .watch = watch,362 .watch = watch,
363 .fuzz = fuzz,
356 .memory_blocked_steps = std.ArrayList(*Step).init(arena),364 .memory_blocked_steps = std.ArrayList(*Step).init(arena),
357 .step_stack = .{},365 .step_stack = .{},
358 .prominent_compile_errors = prominent_compile_errors,366 .prominent_compile_errors = prominent_compile_errors,
...@@ -394,6 +402,10 @@ pub fn main() !void {...@@ -394,6 +402,10 @@ pub fn main() !void {
394 },402 },
395 else => return err,403 else => return err,
396 };404 };
405 if (fuzz) {
406 Fuzz.start(&run.thread_pool, run.step_stack.keys(), run.ttyconf, main_progress_node);
407 }
408
397 if (!watch) return cleanExit();409 if (!watch) return cleanExit();
398410
399 switch (builtin.os.tag) {411 switch (builtin.os.tag) {
...@@ -457,6 +469,7 @@ const Run = struct {...@@ -457,6 +469,7 @@ const Run = struct {
457 max_rss_mutex: std.Thread.Mutex,469 max_rss_mutex: std.Thread.Mutex,
458 skip_oom_steps: bool,470 skip_oom_steps: bool,
459 watch: bool,471 watch: bool,
472 fuzz: bool,
460 memory_blocked_steps: std.ArrayList(*Step),473 memory_blocked_steps: std.ArrayList(*Step),
461 step_stack: std.AutoArrayHashMapUnmanaged(*Step, void),474 step_stack: std.AutoArrayHashMapUnmanaged(*Step, void),
462 prominent_compile_errors: bool,475 prominent_compile_errors: bool,
...@@ -466,6 +479,11 @@ const Run = struct {...@@ -466,6 +479,11 @@ const Run = struct {
466 summary: Summary,479 summary: Summary,
467 ttyconf: std.io.tty.Config,480 ttyconf: std.io.tty.Config,
468 stderr: File,481 stderr: File,
482
483 fn cleanExit(run: Run) void {
484 if (run.watch or run.fuzz) return;
485 return runner.cleanExit();
486 }
469};487};
470488
471fn prepare(489fn prepare(
...@@ -614,8 +632,7 @@ fn runStepNames(...@@ -614,8 +632,7 @@ fn runStepNames(
614 else => false,632 else => false,
615 };633 };
616 if (failure_count == 0 and failures_only) {634 if (failure_count == 0 and failures_only) {
617 if (!run.watch) cleanExit();635 return run.cleanExit();
618 return;
619 }636 }
620637
621 const ttyconf = run.ttyconf;638 const ttyconf = run.ttyconf;
...@@ -672,8 +689,7 @@ fn runStepNames(...@@ -672,8 +689,7 @@ fn runStepNames(
672 }689 }
673690
674 if (failure_count == 0) {691 if (failure_count == 0) {
675 if (!run.watch) cleanExit();692 return run.cleanExit();
676 return;
677 }693 }
678694
679 // Finally, render compile errors at the bottom of the terminal.695 // Finally, render compile errors at the bottom of the terminal.
...@@ -1058,7 +1074,8 @@ fn workerMakeOneStep(...@@ -1058,7 +1074,8 @@ fn workerMakeOneStep(
1058 std.debug.lockStdErr();1074 std.debug.lockStdErr();
1059 defer std.debug.unlockStdErr();1075 defer std.debug.unlockStdErr();
10601076
1061 printErrorMessages(b, s, run) catch {};1077 const gpa = b.allocator;
1078 printErrorMessages(gpa, s, run.ttyconf, run.stderr, run.prominent_compile_errors) catch {};
1062 }1079 }
10631080
1064 handle_result: {1081 handle_result: {
...@@ -1111,11 +1128,13 @@ fn workerMakeOneStep(...@@ -1111,11 +1128,13 @@ fn workerMakeOneStep(
1111 }1128 }
1112}1129}
11131130
1114fn printErrorMessages(b: *std.Build, failing_step: *Step, run: *const Run) !void {1131pub fn printErrorMessages(
1115 const gpa = b.allocator;1132 gpa: Allocator,
1116 const stderr = run.stderr;1133 failing_step: *Step,
1117 const ttyconf = run.ttyconf;1134 ttyconf: std.io.tty.Config,
11181135 stderr: File,
1136 prominent_compile_errors: bool,
1137) !void {
1119 // Provide context for where these error messages are coming from by1138 // Provide context for where these error messages are coming from by
1120 // printing the corresponding Step subtree.1139 // printing the corresponding Step subtree.
11211140
...@@ -1152,7 +1171,7 @@ fn printErrorMessages(b: *std.Build, failing_step: *Step, run: *const Run) !void...@@ -1152,7 +1171,7 @@ fn printErrorMessages(b: *std.Build, failing_step: *Step, run: *const Run) !void
1152 }1171 }
1153 }1172 }
11541173
1155 if (!run.prominent_compile_errors and failing_step.result_error_bundle.errorMessageCount() > 0)1174 if (!prominent_compile_errors and failing_step.result_error_bundle.errorMessageCount() > 0)
1156 try failing_step.result_error_bundle.renderToWriter(renderOptions(ttyconf), stderr.writer());1175 try failing_step.result_error_bundle.renderToWriter(renderOptions(ttyconf), stderr.writer());
11571176
1158 for (failing_step.result_error_msgs.items) |msg| {1177 for (failing_step.result_error_msgs.items) |msg| {
...@@ -1226,6 +1245,7 @@ fn usage(b: *std.Build, out_stream: anytype) !void {...@@ -1226,6 +1245,7 @@ fn usage(b: *std.Build, out_stream: anytype) !void {
1226 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss1245 \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
1227 \\ --fetch Exit after fetching dependency tree1246 \\ --fetch Exit after fetching dependency tree
1228 \\ --watch Continuously rebuild when source files are modified1247 \\ --watch Continuously rebuild when source files are modified
1248 \\ --fuzz Continuously search for unit test failures
1229 \\ --debounce <ms> Delay before rebuilding after changed file detected1249 \\ --debounce <ms> Delay before rebuilding after changed file detected
1230 \\ -fincremental Enable incremental compilation1250 \\ -fincremental Enable incremental compilation
1231 \\ -fno-incremental Disable incremental compilation1251 \\ -fno-incremental Disable incremental compilation
...@@ -1294,6 +1314,7 @@ fn usage(b: *std.Build, out_stream: anytype) !void {...@@ -1294,6 +1314,7 @@ fn usage(b: *std.Build, out_stream: anytype) !void {
1294 \\ --seed [integer] For shuffling dependency traversal order (default: random)1314 \\ --seed [integer] For shuffling dependency traversal order (default: random)
1295 \\ --debug-log [scope] Enable debugging the compiler1315 \\ --debug-log [scope] Enable debugging the compiler
1296 \\ --debug-pkg-config Fail if unknown pkg-config flags encountered1316 \\ --debug-pkg-config Fail if unknown pkg-config flags encountered
1317 \\ --debug-rt Debug compiler runtime libraries
1297 \\ --verbose-link Enable compiler debug output for linking1318 \\ --verbose-link Enable compiler debug output for linking
1298 \\ --verbose-air Enable compiler debug output for Zig AIR1319 \\ --verbose-air Enable compiler debug output for Zig AIR
1299 \\ --verbose-llvm-ir[=file] Enable compiler debug output for LLVM IR1320 \\ --verbose-llvm-ir[=file] Enable compiler debug output for LLVM IR
lib/compiler/test_runner.zig+89-22
...@@ -1,18 +1,26 @@...@@ -1,18 +1,26 @@
1//! Default test runner for unit tests.1//! Default test runner for unit tests.
2const builtin = @import("builtin");
2const std = @import("std");3const std = @import("std");
3const io = std.io;4const io = std.io;
4const builtin = @import("builtin");5const testing = std.testing;
56
6pub const std_options = .{7pub const std_options = .{
7 .logFn = log,8 .logFn = log,
8};9};
910
10var log_err_count: usize = 0;11var log_err_count: usize = 0;
11var cmdline_buffer: [4096]u8 = undefined;12var fba_buffer: [8192]u8 = undefined;
12var fba = std.heap.FixedBufferAllocator.init(&cmdline_buffer);13var fba = std.heap.FixedBufferAllocator.init(&fba_buffer);
14
15const crippled = switch (builtin.zig_backend) {
16 .stage2_riscv64 => true,
17 else => false,
18};
1319
14pub fn main() void {20pub fn main() void {
15 if (builtin.zig_backend == .stage2_riscv64) {21 @disableInstrumentation();
22
23 if (crippled) {
16 return mainSimple() catch @panic("test failure\n");24 return mainSimple() catch @panic("test failure\n");
17 }25 }
1826
...@@ -25,13 +33,15 @@ pub fn main() void {...@@ -25,13 +33,15 @@ pub fn main() void {
25 if (std.mem.eql(u8, arg, "--listen=-")) {33 if (std.mem.eql(u8, arg, "--listen=-")) {
26 listen = true;34 listen = true;
27 } else if (std.mem.startsWith(u8, arg, "--seed=")) {35 } else if (std.mem.startsWith(u8, arg, "--seed=")) {
28 std.testing.random_seed = std.fmt.parseUnsigned(u32, arg["--seed=".len..], 0) catch36 testing.random_seed = std.fmt.parseUnsigned(u32, arg["--seed=".len..], 0) catch
29 @panic("unable to parse --seed command line argument");37 @panic("unable to parse --seed command line argument");
30 } else {38 } else {
31 @panic("unrecognized command line argument");39 @panic("unrecognized command line argument");
32 }40 }
33 }41 }
3442
43 fba.reset();
44
35 if (listen) {45 if (listen) {
36 return mainServer() catch @panic("internal test runner failure");46 return mainServer() catch @panic("internal test runner failure");
37 } else {47 } else {
...@@ -40,6 +50,7 @@ pub fn main() void {...@@ -40,6 +50,7 @@ pub fn main() void {
40}50}
4151
42fn mainServer() !void {52fn mainServer() !void {
53 @disableInstrumentation();
43 var server = try std.zig.Server.init(.{54 var server = try std.zig.Server.init(.{
44 .gpa = fba.allocator(),55 .gpa = fba.allocator(),
45 .in = std.io.getStdIn(),56 .in = std.io.getStdIn(),
...@@ -55,24 +66,24 @@ fn mainServer() !void {...@@ -55,24 +66,24 @@ fn mainServer() !void {
55 return std.process.exit(0);66 return std.process.exit(0);
56 },67 },
57 .query_test_metadata => {68 .query_test_metadata => {
58 std.testing.allocator_instance = .{};69 testing.allocator_instance = .{};
59 defer if (std.testing.allocator_instance.deinit() == .leak) {70 defer if (testing.allocator_instance.deinit() == .leak) {
60 @panic("internal test runner memory leak");71 @panic("internal test runner memory leak");
61 };72 };
6273
63 var string_bytes: std.ArrayListUnmanaged(u8) = .{};74 var string_bytes: std.ArrayListUnmanaged(u8) = .{};
64 defer string_bytes.deinit(std.testing.allocator);75 defer string_bytes.deinit(testing.allocator);
65 try string_bytes.append(std.testing.allocator, 0); // Reserve 0 for null.76 try string_bytes.append(testing.allocator, 0); // Reserve 0 for null.
6677
67 const test_fns = builtin.test_functions;78 const test_fns = builtin.test_functions;
68 const names = try std.testing.allocator.alloc(u32, test_fns.len);79 const names = try testing.allocator.alloc(u32, test_fns.len);
69 defer std.testing.allocator.free(names);80 defer testing.allocator.free(names);
70 const expected_panic_msgs = try std.testing.allocator.alloc(u32, test_fns.len);81 const expected_panic_msgs = try testing.allocator.alloc(u32, test_fns.len);
71 defer std.testing.allocator.free(expected_panic_msgs);82 defer testing.allocator.free(expected_panic_msgs);
7283
73 for (test_fns, names, expected_panic_msgs) |test_fn, *name, *expected_panic_msg| {84 for (test_fns, names, expected_panic_msgs) |test_fn, *name, *expected_panic_msg| {
74 name.* = @as(u32, @intCast(string_bytes.items.len));85 name.* = @as(u32, @intCast(string_bytes.items.len));
75 try string_bytes.ensureUnusedCapacity(std.testing.allocator, test_fn.name.len + 1);86 try string_bytes.ensureUnusedCapacity(testing.allocator, test_fn.name.len + 1);
76 string_bytes.appendSliceAssumeCapacity(test_fn.name);87 string_bytes.appendSliceAssumeCapacity(test_fn.name);
77 string_bytes.appendAssumeCapacity(0);88 string_bytes.appendAssumeCapacity(0);
78 expected_panic_msg.* = 0;89 expected_panic_msg.* = 0;
...@@ -86,13 +97,13 @@ fn mainServer() !void {...@@ -86,13 +97,13 @@ fn mainServer() !void {
86 },97 },
8798
88 .run_test => {99 .run_test => {
89 std.testing.allocator_instance = .{};100 testing.allocator_instance = .{};
90 log_err_count = 0;101 log_err_count = 0;
91 const index = try server.receiveBody_u32();102 const index = try server.receiveBody_u32();
92 const test_fn = builtin.test_functions[index];103 const test_fn = builtin.test_functions[index];
93 var fail = false;104 var fail = false;
94 var skip = false;105 var skip = false;
95 var leak = false;106 is_fuzz_test = false;
96 test_fn.func() catch |err| switch (err) {107 test_fn.func() catch |err| switch (err) {
97 error.SkipZigTest => skip = true,108 error.SkipZigTest => skip = true,
98 else => {109 else => {
...@@ -102,13 +113,14 @@ fn mainServer() !void {...@@ -102,13 +113,14 @@ fn mainServer() !void {
102 }113 }
103 },114 },
104 };115 };
105 leak = std.testing.allocator_instance.deinit() == .leak;116 const leak = testing.allocator_instance.deinit() == .leak;
106 try server.serveTestResults(.{117 try server.serveTestResults(.{
107 .index = index,118 .index = index,
108 .flags = .{119 .flags = .{
109 .fail = fail,120 .fail = fail,
110 .skip = skip,121 .skip = skip,
111 .leak = leak,122 .leak = leak,
123 .fuzz = is_fuzz_test,
112 .log_err_count = std.math.lossyCast(124 .log_err_count = std.math.lossyCast(
113 @TypeOf(@as(std.zig.Server.Message.TestResults.Flags, undefined).log_err_count),125 @TypeOf(@as(std.zig.Server.Message.TestResults.Flags, undefined).log_err_count),
114 log_err_count,126 log_err_count,
...@@ -116,9 +128,31 @@ fn mainServer() !void {...@@ -116,9 +128,31 @@ fn mainServer() !void {
116 },128 },
117 });129 });
118 },130 },
131 .start_fuzzing => {
132 const index = try server.receiveBody_u32();
133 const test_fn = builtin.test_functions[index];
134 while (true) {
135 testing.allocator_instance = .{};
136 defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);
137 log_err_count = 0;
138 is_fuzz_test = false;
139 test_fn.func() catch |err| switch (err) {
140 error.SkipZigTest => continue,
141 else => {
142 if (@errorReturnTrace()) |trace| {
143 std.debug.dumpStackTrace(trace.*);
144 }
145 std.debug.print("failed with error.{s}\n", .{@errorName(err)});
146 std.process.exit(1);
147 },
148 };
149 if (!is_fuzz_test) @panic("missed call to std.testing.fuzzInput");
150 if (log_err_count != 0) @panic("error logs detected");
151 }
152 },
119153
120 else => {154 else => {
121 std.debug.print("unsupported message: {x}", .{@intFromEnum(hdr.tag)});155 std.debug.print("unsupported message: {x}\n", .{@intFromEnum(hdr.tag)});
122 std.process.exit(1);156 std.process.exit(1);
123 },157 },
124 }158 }
...@@ -126,10 +160,12 @@ fn mainServer() !void {...@@ -126,10 +160,12 @@ fn mainServer() !void {
126}160}
127161
128fn mainTerminal() void {162fn mainTerminal() void {
163 @disableInstrumentation();
129 const test_fn_list = builtin.test_functions;164 const test_fn_list = builtin.test_functions;
130 var ok_count: usize = 0;165 var ok_count: usize = 0;
131 var skip_count: usize = 0;166 var skip_count: usize = 0;
132 var fail_count: usize = 0;167 var fail_count: usize = 0;
168 var fuzz_count: usize = 0;
133 const root_node = std.Progress.start(.{169 const root_node = std.Progress.start(.{
134 .root_name = "Test",170 .root_name = "Test",
135 .estimated_total_items = test_fn_list.len,171 .estimated_total_items = test_fn_list.len,
...@@ -143,18 +179,19 @@ fn mainTerminal() void {...@@ -143,18 +179,19 @@ fn mainTerminal() void {
143179
144 var leaks: usize = 0;180 var leaks: usize = 0;
145 for (test_fn_list, 0..) |test_fn, i| {181 for (test_fn_list, 0..) |test_fn, i| {
146 std.testing.allocator_instance = .{};182 testing.allocator_instance = .{};
147 defer {183 defer {
148 if (std.testing.allocator_instance.deinit() == .leak) {184 if (testing.allocator_instance.deinit() == .leak) {
149 leaks += 1;185 leaks += 1;
150 }186 }
151 }187 }
152 std.testing.log_level = .warn;188 testing.log_level = .warn;
153189
154 const test_node = root_node.start(test_fn.name, 0);190 const test_node = root_node.start(test_fn.name, 0);
155 if (!have_tty) {191 if (!have_tty) {
156 std.debug.print("{d}/{d} {s}...", .{ i + 1, test_fn_list.len, test_fn.name });192 std.debug.print("{d}/{d} {s}...", .{ i + 1, test_fn_list.len, test_fn.name });
157 }193 }
194 is_fuzz_test = false;
158 if (test_fn.func()) |_| {195 if (test_fn.func()) |_| {
159 ok_count += 1;196 ok_count += 1;
160 test_node.end();197 test_node.end();
...@@ -184,6 +221,7 @@ fn mainTerminal() void {...@@ -184,6 +221,7 @@ fn mainTerminal() void {
184 test_node.end();221 test_node.end();
185 },222 },
186 }223 }
224 fuzz_count += @intFromBool(is_fuzz_test);
187 }225 }
188 root_node.end();226 root_node.end();
189 if (ok_count == test_fn_list.len) {227 if (ok_count == test_fn_list.len) {
...@@ -197,6 +235,9 @@ fn mainTerminal() void {...@@ -197,6 +235,9 @@ fn mainTerminal() void {
197 if (leaks != 0) {235 if (leaks != 0) {
198 std.debug.print("{d} tests leaked memory.\n", .{leaks});236 std.debug.print("{d} tests leaked memory.\n", .{leaks});
199 }237 }
238 if (fuzz_count != 0) {
239 std.debug.print("{d} fuzz tests found.\n", .{fuzz_count});
240 }
200 if (leaks != 0 or log_err_count != 0 or fail_count != 0) {241 if (leaks != 0 or log_err_count != 0 or fail_count != 0) {
201 std.process.exit(1);242 std.process.exit(1);
202 }243 }
...@@ -208,10 +249,11 @@ pub fn log(...@@ -208,10 +249,11 @@ pub fn log(
208 comptime format: []const u8,249 comptime format: []const u8,
209 args: anytype,250 args: anytype,
210) void {251) void {
252 @disableInstrumentation();
211 if (@intFromEnum(message_level) <= @intFromEnum(std.log.Level.err)) {253 if (@intFromEnum(message_level) <= @intFromEnum(std.log.Level.err)) {
212 log_err_count +|= 1;254 log_err_count +|= 1;
213 }255 }
214 if (@intFromEnum(message_level) <= @intFromEnum(std.testing.log_level)) {256 if (@intFromEnum(message_level) <= @intFromEnum(testing.log_level)) {
215 std.debug.print(257 std.debug.print(
216 "[" ++ @tagName(scope) ++ "] (" ++ @tagName(message_level) ++ "): " ++ format ++ "\n",258 "[" ++ @tagName(scope) ++ "] (" ++ @tagName(message_level) ++ "): " ++ format ++ "\n",
217 args,259 args,
...@@ -222,6 +264,7 @@ pub fn log(...@@ -222,6 +264,7 @@ pub fn log(
222/// Simpler main(), exercising fewer language features, so that264/// Simpler main(), exercising fewer language features, so that
223/// work-in-progress backends can handle it.265/// work-in-progress backends can handle it.
224pub fn mainSimple() anyerror!void {266pub fn mainSimple() anyerror!void {
267 @disableInstrumentation();
225 // is the backend capable of printing to stderr?268 // is the backend capable of printing to stderr?
226 const enable_print = switch (builtin.zig_backend) {269 const enable_print = switch (builtin.zig_backend) {
227 else => false,270 else => false,
...@@ -266,3 +309,27 @@ pub fn mainSimple() anyerror!void {...@@ -266,3 +309,27 @@ pub fn mainSimple() anyerror!void {
266 }309 }
267 if (failed != 0) std.process.exit(1);310 if (failed != 0) std.process.exit(1);
268}311}
312
313const FuzzerSlice = extern struct {
314 ptr: [*]const u8,
315 len: usize,
316
317 inline fn toSlice(s: FuzzerSlice) []const u8 {
318 return s.ptr[0..s.len];
319 }
320};
321
322var is_fuzz_test: bool = undefined;
323
324extern fn fuzzer_next() FuzzerSlice;
325
326pub fn fuzzInput(options: testing.FuzzInputOptions) []const u8 {
327 @disableInstrumentation();
328 if (crippled) return "";
329 is_fuzz_test = true;
330 if (builtin.fuzz) return fuzzer_next().toSlice();
331 if (options.corpus.len == 0) return "";
332 var prng = std.Random.DefaultPrng.init(testing.random_seed);
333 const random = prng.random();
334 return options.corpus[random.uintLessThan(usize, options.corpus.len)];
335}
lib/fuzzer.zig+263-8
...@@ -1,13 +1,43 @@...@@ -1,13 +1,43 @@
1const builtin = @import("builtin");
1const std = @import("std");2const std = @import("std");
3const Allocator = std.mem.Allocator;
4const assert = std.debug.assert;
5
6pub const std_options = .{
7 .logFn = logOverride,
8};
9
10var log_file: ?std.fs.File = null;
11
12fn logOverride(
13 comptime level: std.log.Level,
14 comptime scope: @TypeOf(.EnumLiteral),
15 comptime format: []const u8,
16 args: anytype,
17) void {
18 if (builtin.mode != .Debug) return;
19 const f = if (log_file) |f| f else f: {
20 const f = std.fs.cwd().createFile("libfuzzer.log", .{}) catch @panic("failed to open fuzzer log file");
21 log_file = f;
22 break :f f;
23 };
24 const prefix1 = comptime level.asText();
25 const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
26 f.writer().print(prefix1 ++ prefix2 ++ format ++ "\n", args) catch @panic("failed to write to fuzzer log");
27}
228
3export threadlocal var __sancov_lowest_stack: usize = 0;29export threadlocal var __sancov_lowest_stack: usize = 0;
430
5export fn __sanitizer_cov_8bit_counters_init(start: [*]u8, stop: [*]u8) void {31export fn __sanitizer_cov_8bit_counters_init(start: [*]u8, stop: [*]u8) void {
6 std.debug.print("__sanitizer_cov_8bit_counters_init start={*}, stop={*}\n", .{ start, stop });32 std.log.debug("__sanitizer_cov_8bit_counters_init start={*}, stop={*}", .{ start, stop });
7}33}
834
9export fn __sanitizer_cov_pcs_init(pcs_beg: [*]const usize, pcs_end: [*]const usize) void {35export fn __sanitizer_cov_pcs_init(pc_start: [*]const usize, pc_end: [*]const usize) void {
10 std.debug.print("__sanitizer_cov_pcs_init pcs_beg={*}, pcs_end={*}\n", .{ pcs_beg, pcs_end });36 std.log.debug("__sanitizer_cov_pcs_init pc_start={*}, pc_end={*}", .{ pc_start, pc_end });
37 fuzzer.pc_range = .{
38 .start = @intFromPtr(pc_start),
39 .end = @intFromPtr(pc_start),
40 };
11}41}
1242
13export fn __sanitizer_cov_trace_const_cmp1(arg1: u8, arg2: u8) void {43export fn __sanitizer_cov_trace_const_cmp1(arg1: u8, arg2: u8) void {
...@@ -47,16 +77,241 @@ export fn __sanitizer_cov_trace_switch(val: u64, cases_ptr: [*]u64) void {...@@ -47,16 +77,241 @@ export fn __sanitizer_cov_trace_switch(val: u64, cases_ptr: [*]u64) void {
47 const len = cases_ptr[0];77 const len = cases_ptr[0];
48 const val_size_in_bits = cases_ptr[1];78 const val_size_in_bits = cases_ptr[1];
49 const cases = cases_ptr[2..][0..len];79 const cases = cases_ptr[2..][0..len];
50 std.debug.print("0x{x}: switch on value {d} ({d} bits) with {d} cases\n", .{80 _ = val;
51 pc, val, val_size_in_bits, cases.len,81 fuzzer.visitPc(pc);
52 });82 _ = val_size_in_bits;
83 _ = cases;
84 //std.log.debug("0x{x}: switch on value {d} ({d} bits) with {d} cases", .{
85 // pc, val, val_size_in_bits, cases.len,
86 //});
53}87}
5488
55export fn __sanitizer_cov_trace_pc_indir(callee: usize) void {89export fn __sanitizer_cov_trace_pc_indir(callee: usize) void {
56 const pc = @returnAddress();90 const pc = @returnAddress();
57 std.debug.print("0x{x}: indirect call to 0x{x}\n", .{ pc, callee });91 _ = callee;
92 fuzzer.visitPc(pc);
93 //std.log.debug("0x{x}: indirect call to 0x{x}", .{ pc, callee });
58}94}
5995
60fn handleCmp(pc: usize, arg1: u64, arg2: u64) void {96fn handleCmp(pc: usize, arg1: u64, arg2: u64) void {
61 std.debug.print("0x{x}: comparison of {d} and {d}\n", .{ pc, arg1, arg2 });97 fuzzer.visitPc(pc ^ arg1 ^ arg2);
98 //std.log.debug("0x{x}: comparison of {d} and {d}", .{ pc, arg1, arg2 });
99}
100
101const Fuzzer = struct {
102 gpa: Allocator,
103 rng: std.Random.DefaultPrng,
104 input: std.ArrayListUnmanaged(u8),
105 pc_range: PcRange,
106 count: usize,
107 recent_cases: RunMap,
108 deduplicated_runs: usize,
109 coverage: Coverage,
110
111 const RunMap = std.ArrayHashMapUnmanaged(Run, void, Run.HashContext, false);
112
113 const Coverage = struct {
114 pc_table: std.AutoArrayHashMapUnmanaged(usize, void),
115 run_id_hasher: std.hash.Wyhash,
116
117 fn reset(cov: *Coverage) void {
118 cov.pc_table.clearRetainingCapacity();
119 cov.run_id_hasher = std.hash.Wyhash.init(0);
120 }
121 };
122
123 const Run = struct {
124 id: Id,
125 input: []const u8,
126 score: usize,
127
128 const Id = u64;
129
130 const HashContext = struct {
131 pub fn eql(ctx: HashContext, a: Run, b: Run, b_index: usize) bool {
132 _ = b_index;
133 _ = ctx;
134 return a.id == b.id;
135 }
136 pub fn hash(ctx: HashContext, a: Run) u32 {
137 _ = ctx;
138 return @truncate(a.id);
139 }
140 };
141
142 fn deinit(run: *Run, gpa: Allocator) void {
143 gpa.free(run.input);
144 run.* = undefined;
145 }
146 };
147
148 const Slice = extern struct {
149 ptr: [*]const u8,
150 len: usize,
151
152 fn toZig(s: Slice) []const u8 {
153 return s.ptr[0..s.len];
154 }
155
156 fn fromZig(s: []const u8) Slice {
157 return .{
158 .ptr = s.ptr,
159 .len = s.len,
160 };
161 }
162 };
163
164 const PcRange = struct {
165 start: usize,
166 end: usize,
167 };
168
169 const Analysis = struct {
170 score: usize,
171 id: Run.Id,
172 };
173
174 fn analyzeLastRun(f: *Fuzzer) Analysis {
175 return .{
176 .id = f.coverage.run_id_hasher.final(),
177 .score = f.coverage.pc_table.count(),
178 };
179 }
180
181 fn next(f: *Fuzzer) ![]const u8 {
182 const gpa = f.gpa;
183 const rng = fuzzer.rng.random();
184
185 if (f.recent_cases.entries.len == 0) {
186 // Prepare initial input.
187 try f.recent_cases.ensureUnusedCapacity(gpa, 100);
188 const len = rng.uintLessThanBiased(usize, 80);
189 try f.input.resize(gpa, len);
190 rng.bytes(f.input.items);
191 f.recent_cases.putAssumeCapacity(.{
192 .id = 0,
193 .input = try gpa.dupe(u8, f.input.items),
194 .score = 0,
195 }, {});
196 } else {
197 if (f.count % 1000 == 0) f.dumpStats();
198
199 const analysis = f.analyzeLastRun();
200 const gop = f.recent_cases.getOrPutAssumeCapacity(.{
201 .id = analysis.id,
202 .input = undefined,
203 .score = undefined,
204 });
205 if (gop.found_existing) {
206 //std.log.info("duplicate analysis: score={d} id={d}", .{ analysis.score, analysis.id });
207 f.deduplicated_runs += 1;
208 if (f.input.items.len < gop.key_ptr.input.len or gop.key_ptr.score == 0) {
209 gpa.free(gop.key_ptr.input);
210 gop.key_ptr.input = try gpa.dupe(u8, f.input.items);
211 gop.key_ptr.score = analysis.score;
212 }
213 } else {
214 std.log.info("unique analysis: score={d} id={d}", .{ analysis.score, analysis.id });
215 gop.key_ptr.* = .{
216 .id = analysis.id,
217 .input = try gpa.dupe(u8, f.input.items),
218 .score = analysis.score,
219 };
220 }
221
222 if (f.recent_cases.entries.len >= 100) {
223 const Context = struct {
224 values: []const Run,
225 pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
226 return ctx.values[b_index].score < ctx.values[a_index].score;
227 }
228 };
229 f.recent_cases.sortUnstable(Context{ .values = f.recent_cases.keys() });
230 const cap = 50;
231 // This has to be done before deinitializing the deleted items.
232 const doomed_runs = f.recent_cases.keys()[cap..];
233 f.recent_cases.shrinkRetainingCapacity(cap);
234 for (doomed_runs) |*run| {
235 std.log.info("culling score={d} id={d}", .{ run.score, run.id });
236 run.deinit(gpa);
237 }
238 }
239 }
240
241 const chosen_index = rng.uintLessThanBiased(usize, f.recent_cases.entries.len);
242 const run = &f.recent_cases.keys()[chosen_index];
243 f.input.clearRetainingCapacity();
244 f.input.appendSliceAssumeCapacity(run.input);
245 try f.mutate();
246
247 f.coverage.reset();
248 f.count += 1;
249 return f.input.items;
250 }
251
252 fn visitPc(f: *Fuzzer, pc: usize) void {
253 errdefer |err| oom(err);
254 try f.coverage.pc_table.put(f.gpa, pc, {});
255 f.coverage.run_id_hasher.update(std.mem.asBytes(&pc));
256 }
257
258 fn dumpStats(f: *Fuzzer) void {
259 std.log.info("stats: runs={d} deduplicated={d}", .{
260 f.count,
261 f.deduplicated_runs,
262 });
263 for (f.recent_cases.keys()[0..@min(f.recent_cases.entries.len, 5)], 0..) |run, i| {
264 std.log.info("best[{d}] id={x} score={d} input: '{}'", .{
265 i, run.id, run.score, std.zig.fmtEscapes(run.input),
266 });
267 }
268 }
269
270 fn mutate(f: *Fuzzer) !void {
271 const gpa = f.gpa;
272 const rng = fuzzer.rng.random();
273
274 if (f.input.items.len == 0) {
275 const len = rng.uintLessThanBiased(usize, 80);
276 try f.input.resize(gpa, len);
277 rng.bytes(f.input.items);
278 return;
279 }
280
281 const index = rng.uintLessThanBiased(usize, f.input.items.len * 3);
282 if (index < f.input.items.len) {
283 f.input.items[index] = rng.int(u8);
284 } else if (index < f.input.items.len * 2) {
285 _ = f.input.orderedRemove(index - f.input.items.len);
286 } else if (index < f.input.items.len * 3) {
287 try f.input.insert(gpa, index - f.input.items.len * 2, rng.int(u8));
288 } else {
289 unreachable;
290 }
291 }
292};
293
294fn oom(err: anytype) noreturn {
295 switch (err) {
296 error.OutOfMemory => @panic("out of memory"),
297 }
298}
299
300var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .{};
301
302var fuzzer: Fuzzer = .{
303 .gpa = general_purpose_allocator.allocator(),
304 .rng = std.Random.DefaultPrng.init(0),
305 .input = .{},
306 .pc_range = .{ .start = 0, .end = 0 },
307 .count = 0,
308 .deduplicated_runs = 0,
309 .recent_cases = .{},
310 .coverage = undefined,
311};
312
313export fn fuzzer_next() Fuzzer.Slice {
314 return Fuzzer.Slice.fromZig(fuzzer.next() catch |err| switch (err) {
315 error.OutOfMemory => @panic("out of memory"),
316 });
62}317}
lib/std/Build.zig+3
...@@ -21,6 +21,7 @@ pub const Cache = @import("Build/Cache.zig");...@@ -21,6 +21,7 @@ pub const Cache = @import("Build/Cache.zig");
21pub const Step = @import("Build/Step.zig");21pub const Step = @import("Build/Step.zig");
22pub const Module = @import("Build/Module.zig");22pub const Module = @import("Build/Module.zig");
23pub const Watch = @import("Build/Watch.zig");23pub const Watch = @import("Build/Watch.zig");
24pub const Fuzz = @import("Build/Fuzz.zig");
2425
25/// Shared state among all Build instances.26/// Shared state among all Build instances.
26graph: *Graph,27graph: *Graph,
...@@ -112,6 +113,7 @@ pub const Graph = struct {...@@ -112,6 +113,7 @@ pub const Graph = struct {
112 arena: Allocator,113 arena: Allocator,
113 system_library_options: std.StringArrayHashMapUnmanaged(SystemLibraryMode) = .{},114 system_library_options: std.StringArrayHashMapUnmanaged(SystemLibraryMode) = .{},
114 system_package_mode: bool = false,115 system_package_mode: bool = false,
116 debug_compiler_runtime_libs: bool = false,
115 cache: Cache,117 cache: Cache,
116 zig_exe: [:0]const u8,118 zig_exe: [:0]const u8,
117 env_map: EnvMap,119 env_map: EnvMap,
...@@ -977,6 +979,7 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {...@@ -977,6 +979,7 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
977 // Consider that this is declarative; the run step may not be run unless a user979 // Consider that this is declarative; the run step may not be run unless a user
978 // option is supplied.980 // option is supplied.
979 const run_step = Step.Run.create(b, b.fmt("run {s}", .{exe.name}));981 const run_step = Step.Run.create(b, b.fmt("run {s}", .{exe.name}));
982 run_step.producer = exe;
980 if (exe.kind == .@"test") {983 if (exe.kind == .@"test") {
981 if (exe.exec_cmd_args) |exec_cmd_args| {984 if (exe.exec_cmd_args) |exec_cmd_args| {
982 for (exec_cmd_args) |cmd_arg| {985 for (exec_cmd_args) |cmd_arg| {
lib/std/Build/Fuzz.zig created+114
...@@ -0,0 +1,114 @@
1const std = @import("../std.zig");
2const Fuzz = @This();
3const Step = std.Build.Step;
4const assert = std.debug.assert;
5const fatal = std.process.fatal;
6const build_runner = @import("root");
7
8pub fn start(
9 thread_pool: *std.Thread.Pool,
10 all_steps: []const *Step,
11 ttyconf: std.io.tty.Config,
12 prog_node: std.Progress.Node,
13) void {
14 const count = block: {
15 const rebuild_node = prog_node.start("Rebuilding Unit Tests", 0);
16 defer rebuild_node.end();
17 var count: usize = 0;
18 var wait_group: std.Thread.WaitGroup = .{};
19 defer wait_group.wait();
20 for (all_steps) |step| {
21 const run = step.cast(Step.Run) orelse continue;
22 if (run.fuzz_tests.items.len > 0 and run.producer != null) {
23 thread_pool.spawnWg(&wait_group, rebuildTestsWorkerRun, .{ run, ttyconf, rebuild_node });
24 count += 1;
25 }
26 }
27 if (count == 0) fatal("no fuzz tests found", .{});
28 rebuild_node.setEstimatedTotalItems(count);
29 break :block count;
30 };
31
32 // Detect failure.
33 for (all_steps) |step| {
34 const run = step.cast(Step.Run) orelse continue;
35 if (run.fuzz_tests.items.len > 0 and run.rebuilt_executable == null)
36 fatal("one or more unit tests failed to be rebuilt in fuzz mode", .{});
37 }
38
39 {
40 const fuzz_node = prog_node.start("Fuzzing", count);
41 defer fuzz_node.end();
42 var wait_group: std.Thread.WaitGroup = .{};
43 defer wait_group.wait();
44
45 for (all_steps) |step| {
46 const run = step.cast(Step.Run) orelse continue;
47 for (run.fuzz_tests.items) |unit_test_index| {
48 assert(run.rebuilt_executable != null);
49 thread_pool.spawnWg(&wait_group, fuzzWorkerRun, .{ run, unit_test_index, ttyconf, fuzz_node });
50 }
51 }
52 }
53
54 fatal("all fuzz workers crashed", .{});
55}
56
57fn rebuildTestsWorkerRun(run: *Step.Run, ttyconf: std.io.tty.Config, parent_prog_node: std.Progress.Node) void {
58 const gpa = run.step.owner.allocator;
59 const stderr = std.io.getStdErr();
60
61 const compile = run.producer.?;
62 const prog_node = parent_prog_node.start(compile.step.name, 0);
63 defer prog_node.end();
64
65 const result = compile.rebuildInFuzzMode(prog_node);
66
67 const show_compile_errors = compile.step.result_error_bundle.errorMessageCount() > 0;
68 const show_error_msgs = compile.step.result_error_msgs.items.len > 0;
69 const show_stderr = compile.step.result_stderr.len > 0;
70
71 if (show_error_msgs or show_compile_errors or show_stderr) {
72 std.debug.lockStdErr();
73 defer std.debug.unlockStdErr();
74 build_runner.printErrorMessages(gpa, &compile.step, ttyconf, stderr, false) catch {};
75 }
76
77 if (result) |rebuilt_bin_path| {
78 run.rebuilt_executable = rebuilt_bin_path;
79 } else |err| switch (err) {
80 error.MakeFailed => {},
81 else => {
82 std.debug.print("step '{s}': failed to rebuild in fuzz mode: {s}\n", .{
83 compile.step.name, @errorName(err),
84 });
85 },
86 }
87}
88
89fn fuzzWorkerRun(
90 run: *Step.Run,
91 unit_test_index: u32,
92 ttyconf: std.io.tty.Config,
93 parent_prog_node: std.Progress.Node,
94) void {
95 const gpa = run.step.owner.allocator;
96 const test_name = run.cached_test_metadata.?.testName(unit_test_index);
97
98 const prog_node = parent_prog_node.start(test_name, 0);
99 defer prog_node.end();
100
101 run.rerunInFuzzMode(unit_test_index, prog_node) catch |err| switch (err) {
102 error.MakeFailed => {
103 const stderr = std.io.getStdErr();
104 std.debug.lockStdErr();
105 defer std.debug.unlockStdErr();
106 build_runner.printErrorMessages(gpa, &run.step, ttyconf, stderr, false) catch {};
107 },
108 else => {
109 std.debug.print("step '{s}': failed to rebuild '{s}' in fuzz mode: {s}\n", .{
110 run.step.name, test_name, @errorName(err),
111 });
112 },
113 };
114}
lib/std/Build/Step/Compile.zig+31-11
...@@ -1004,7 +1004,7 @@ fn getGeneratedFilePath(compile: *Compile, comptime tag_name: []const u8, asking...@@ -1004,7 +1004,7 @@ fn getGeneratedFilePath(compile: *Compile, comptime tag_name: []const u8, asking
1004 return path;1004 return path;
1005}1005}
10061006
1007fn getZigArgs(compile: *Compile) ![][]const u8 {1007fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
1008 const step = &compile.step;1008 const step = &compile.step;
1009 const b = step.owner;1009 const b = step.owner;
1010 const arena = b.allocator;1010 const arena = b.allocator;
...@@ -1055,6 +1055,10 @@ fn getZigArgs(compile: *Compile) ![][]const u8 {...@@ -1055,6 +1055,10 @@ fn getZigArgs(compile: *Compile) ![][]const u8 {
1055 try zig_args.append(try std.fmt.allocPrint(arena, "{}", .{stack_size}));1055 try zig_args.append(try std.fmt.allocPrint(arena, "{}", .{stack_size}));
1056 }1056 }
10571057
1058 if (fuzz) {
1059 try zig_args.append("-ffuzz");
1060 }
1061
1058 {1062 {
1059 // Stores system libraries that have already been seen for at least one1063 // Stores system libraries that have already been seen for at least one
1060 // module, along with any arguments that need to be passed to the1064 // module, along with any arguments that need to be passed to the
...@@ -1479,6 +1483,8 @@ fn getZigArgs(compile: *Compile) ![][]const u8 {...@@ -1479,6 +1483,8 @@ fn getZigArgs(compile: *Compile) ![][]const u8 {
1479 try zig_args.append("--global-cache-dir");1483 try zig_args.append("--global-cache-dir");
1480 try zig_args.append(b.graph.global_cache_root.path orelse ".");1484 try zig_args.append(b.graph.global_cache_root.path orelse ".");
14811485
1486 if (b.graph.debug_compiler_runtime_libs) try zig_args.append("--debug-rt");
1487
1482 try zig_args.append("--name");1488 try zig_args.append("--name");
1483 try zig_args.append(compile.name);1489 try zig_args.append(compile.name);
14841490
...@@ -1757,7 +1763,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -1757,7 +1763,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
1757 const b = step.owner;1763 const b = step.owner;
1758 const compile: *Compile = @fieldParentPtr("step", step);1764 const compile: *Compile = @fieldParentPtr("step", step);
17591765
1760 const zig_args = try getZigArgs(compile);1766 const zig_args = try getZigArgs(compile, false);
17611767
1762 const maybe_output_bin_path = step.evalZigProcess(1768 const maybe_output_bin_path = step.evalZigProcess(
1763 zig_args,1769 zig_args,
...@@ -1835,6 +1841,20 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -1835,6 +1841,20 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
1835 }1841 }
1836}1842}
18371843
1844pub fn rebuildInFuzzMode(c: *Compile, progress_node: std.Progress.Node) ![]const u8 {
1845 const gpa = c.step.owner.allocator;
1846
1847 c.step.result_error_msgs.clearRetainingCapacity();
1848 c.step.result_stderr = "";
1849
1850 c.step.result_error_bundle.deinit(gpa);
1851 c.step.result_error_bundle = std.zig.ErrorBundle.empty;
1852
1853 const zig_args = try getZigArgs(c, true);
1854 const maybe_output_bin_path = try c.step.evalZigProcess(zig_args, progress_node, false);
1855 return maybe_output_bin_path.?;
1856}
1857
1838pub fn doAtomicSymLinks(1858pub fn doAtomicSymLinks(
1839 step: *Step,1859 step: *Step,
1840 output_path: []const u8,1860 output_path: []const u8,
...@@ -1861,10 +1881,10 @@ pub fn doAtomicSymLinks(...@@ -1861,10 +1881,10 @@ pub fn doAtomicSymLinks(
1861 };1881 };
1862}1882}
18631883
1864fn execPkgConfigList(compile: *std.Build, out_code: *u8) (PkgConfigError || RunError)![]const PkgConfigPkg {1884fn execPkgConfigList(b: *std.Build, out_code: *u8) (PkgConfigError || RunError)![]const PkgConfigPkg {
1865 const pkg_config_exe = compile.graph.env_map.get("PKG_CONFIG") orelse "pkg-config";1885 const pkg_config_exe = b.graph.env_map.get("PKG_CONFIG") orelse "pkg-config";
1866 const stdout = try compile.runAllowFail(&[_][]const u8{ pkg_config_exe, "--list-all" }, out_code, .Ignore);1886 const stdout = try b.runAllowFail(&[_][]const u8{ pkg_config_exe, "--list-all" }, out_code, .Ignore);
1867 var list = ArrayList(PkgConfigPkg).init(compile.allocator);1887 var list = ArrayList(PkgConfigPkg).init(b.allocator);
1868 errdefer list.deinit();1888 errdefer list.deinit();
1869 var line_it = mem.tokenizeAny(u8, stdout, "\r\n");1889 var line_it = mem.tokenizeAny(u8, stdout, "\r\n");
1870 while (line_it.next()) |line| {1890 while (line_it.next()) |line| {
...@@ -1878,13 +1898,13 @@ fn execPkgConfigList(compile: *std.Build, out_code: *u8) (PkgConfigError || RunE...@@ -1878,13 +1898,13 @@ fn execPkgConfigList(compile: *std.Build, out_code: *u8) (PkgConfigError || RunE
1878 return list.toOwnedSlice();1898 return list.toOwnedSlice();
1879}1899}
18801900
1881fn getPkgConfigList(compile: *std.Build) ![]const PkgConfigPkg {1901fn getPkgConfigList(b: *std.Build) ![]const PkgConfigPkg {
1882 if (compile.pkg_config_pkg_list) |res| {1902 if (b.pkg_config_pkg_list) |res| {
1883 return res;1903 return res;
1884 }1904 }
1885 var code: u8 = undefined;1905 var code: u8 = undefined;
1886 if (execPkgConfigList(compile, &code)) |list| {1906 if (execPkgConfigList(b, &code)) |list| {
1887 compile.pkg_config_pkg_list = list;1907 b.pkg_config_pkg_list = list;
1888 return list;1908 return list;
1889 } else |err| {1909 } else |err| {
1890 const result = switch (err) {1910 const result = switch (err) {
...@@ -1896,7 +1916,7 @@ fn getPkgConfigList(compile: *std.Build) ![]const PkgConfigPkg {...@@ -1896,7 +1916,7 @@ fn getPkgConfigList(compile: *std.Build) ![]const PkgConfigPkg {
1896 error.PkgConfigInvalidOutput => error.PkgConfigInvalidOutput,1916 error.PkgConfigInvalidOutput => error.PkgConfigInvalidOutput,
1897 else => return err,1917 else => return err,
1898 };1918 };
1899 compile.pkg_config_pkg_list = result;1919 b.pkg_config_pkg_list = result;
1900 return result;1920 return result;
1901 }1921 }
1902}1922}
lib/std/Build/Step/Run.zig+101-11
...@@ -86,6 +86,18 @@ dep_output_file: ?*Output,...@@ -86,6 +86,18 @@ dep_output_file: ?*Output,
8686
87has_side_effects: bool,87has_side_effects: bool,
8888
89/// If this is a Zig unit test binary, this tracks the indexes of the unit
90/// tests that are also fuzz tests.
91fuzz_tests: std.ArrayListUnmanaged(u32),
92cached_test_metadata: ?CachedTestMetadata = null,
93
94/// Populated during the fuzz phase if this run step corresponds to a unit test
95/// executable that contains fuzz tests.
96rebuilt_executable: ?[]const u8,
97
98/// If this Run step was produced by a Compile step, it is tracked here.
99producer: ?*Step.Compile,
100
89pub const StdIn = union(enum) {101pub const StdIn = union(enum) {
90 none,102 none,
91 bytes: []const u8,103 bytes: []const u8,
...@@ -175,6 +187,9 @@ pub fn create(owner: *std.Build, name: []const u8) *Run {...@@ -175,6 +187,9 @@ pub fn create(owner: *std.Build, name: []const u8) *Run {
175 .captured_stderr = null,187 .captured_stderr = null,
176 .dep_output_file = null,188 .dep_output_file = null,
177 .has_side_effects = false,189 .has_side_effects = false,
190 .fuzz_tests = .{},
191 .rebuilt_executable = null,
192 .producer = null,
178 };193 };
179 return run;194 return run;
180}195}
...@@ -741,7 +756,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -741,7 +756,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
741 b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path });756 b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path });
742 }757 }
743758
744 try runCommand(run, argv_list.items, has_side_effects, output_dir_path, prog_node);759 try runCommand(run, argv_list.items, has_side_effects, output_dir_path, prog_node, null);
745 if (!has_side_effects) try step.writeManifestAndWatch(&man);760 if (!has_side_effects) try step.writeManifestAndWatch(&man);
746 return;761 return;
747 };762 };
...@@ -771,7 +786,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -771,7 +786,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
771 b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path });786 b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path });
772 }787 }
773788
774 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, prog_node);789 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, prog_node, null);
775790
776 const dep_file_dir = std.fs.cwd();791 const dep_file_dir = std.fs.cwd();
777 const dep_file_basename = dep_output_file.generated_file.getPath();792 const dep_file_basename = dep_output_file.generated_file.getPath();
...@@ -830,6 +845,41 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -830,6 +845,41 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
830 );845 );
831}846}
832847
848pub fn rerunInFuzzMode(run: *Run, unit_test_index: u32, prog_node: std.Progress.Node) !void {
849 const step = &run.step;
850 const b = step.owner;
851 const arena = b.allocator;
852 var argv_list: std.ArrayListUnmanaged([]const u8) = .{};
853 for (run.argv.items) |arg| {
854 switch (arg) {
855 .bytes => |bytes| {
856 try argv_list.append(arena, bytes);
857 },
858 .lazy_path => |file| {
859 const file_path = file.lazy_path.getPath2(b, step);
860 try argv_list.append(arena, b.fmt("{s}{s}", .{ file.prefix, file_path }));
861 },
862 .directory_source => |file| {
863 const file_path = file.lazy_path.getPath2(b, step);
864 try argv_list.append(arena, b.fmt("{s}{s}", .{ file.prefix, file_path }));
865 },
866 .artifact => |pa| {
867 const artifact = pa.artifact;
868 const file_path = if (artifact == run.producer.?)
869 run.rebuilt_executable.?
870 else
871 (artifact.installed_path orelse artifact.generated_bin.?.path.?);
872 try argv_list.append(arena, b.fmt("{s}{s}", .{ pa.prefix, file_path }));
873 },
874 .output_file, .output_directory => unreachable,
875 }
876 }
877 const has_side_effects = false;
878 const rand_int = std.crypto.random.int(u64);
879 const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
880 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, prog_node, unit_test_index);
881}
882
833fn populateGeneratedPaths(883fn populateGeneratedPaths(
834 arena: std.mem.Allocator,884 arena: std.mem.Allocator,
835 output_placeholders: []const IndexedOutput,885 output_placeholders: []const IndexedOutput,
...@@ -908,6 +958,7 @@ fn runCommand(...@@ -908,6 +958,7 @@ fn runCommand(
908 has_side_effects: bool,958 has_side_effects: bool,
909 output_dir_path: []const u8,959 output_dir_path: []const u8,
910 prog_node: std.Progress.Node,960 prog_node: std.Progress.Node,
961 fuzz_unit_test_index: ?u32,
911) !void {962) !void {
912 const step = &run.step;963 const step = &run.step;
913 const b = step.owner;964 const b = step.owner;
...@@ -926,7 +977,7 @@ fn runCommand(...@@ -926,7 +977,7 @@ fn runCommand(
926 var interp_argv = std.ArrayList([]const u8).init(b.allocator);977 var interp_argv = std.ArrayList([]const u8).init(b.allocator);
927 defer interp_argv.deinit();978 defer interp_argv.deinit();
928979
929 const result = spawnChildAndCollect(run, argv, has_side_effects, prog_node) catch |err| term: {980 const result = spawnChildAndCollect(run, argv, has_side_effects, prog_node, fuzz_unit_test_index) catch |err| term: {
930 // InvalidExe: cpu arch mismatch981 // InvalidExe: cpu arch mismatch
931 // FileNotFound: can happen with a wrong dynamic linker path982 // FileNotFound: can happen with a wrong dynamic linker path
932 if (err == error.InvalidExe or err == error.FileNotFound) interpret: {983 if (err == error.InvalidExe or err == error.FileNotFound) interpret: {
...@@ -1062,7 +1113,7 @@ fn runCommand(...@@ -1062,7 +1113,7 @@ fn runCommand(
10621113
1063 try Step.handleVerbose2(step.owner, cwd, run.env_map, interp_argv.items);1114 try Step.handleVerbose2(step.owner, cwd, run.env_map, interp_argv.items);
10641115
1065 break :term spawnChildAndCollect(run, interp_argv.items, has_side_effects, prog_node) catch |e| {1116 break :term spawnChildAndCollect(run, interp_argv.items, has_side_effects, prog_node, fuzz_unit_test_index) catch |e| {
1066 if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped;1117 if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped;
10671118
1068 return step.fail("unable to spawn interpreter {s}: {s}", .{1119 return step.fail("unable to spawn interpreter {s}: {s}", .{
...@@ -1077,6 +1128,15 @@ fn runCommand(...@@ -1077,6 +1128,15 @@ fn runCommand(
1077 step.result_duration_ns = result.elapsed_ns;1128 step.result_duration_ns = result.elapsed_ns;
1078 step.result_peak_rss = result.peak_rss;1129 step.result_peak_rss = result.peak_rss;
1079 step.test_results = result.stdio.test_results;1130 step.test_results = result.stdio.test_results;
1131 if (result.stdio.test_metadata) |tm|
1132 run.cached_test_metadata = tm.toCachedTestMetadata();
1133
1134 const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items;
1135
1136 if (fuzz_unit_test_index != null) {
1137 try step.handleChildProcessTerm(result.term, cwd, final_argv);
1138 return;
1139 }
10801140
1081 // Capture stdout and stderr to GeneratedFile objects.1141 // Capture stdout and stderr to GeneratedFile objects.
1082 const Stream = struct {1142 const Stream = struct {
...@@ -1113,8 +1173,6 @@ fn runCommand(...@@ -1113,8 +1173,6 @@ fn runCommand(
1113 }1173 }
1114 }1174 }
11151175
1116 const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items;
1117
1118 switch (run.stdio) {1176 switch (run.stdio) {
1119 .check => |checks| for (checks.items) |check| switch (check) {1177 .check => |checks| for (checks.items) |check| switch (check) {
1120 .expect_stderr_exact => |expected_bytes| {1178 .expect_stderr_exact => |expected_bytes| {
...@@ -1240,10 +1298,16 @@ fn spawnChildAndCollect(...@@ -1240,10 +1298,16 @@ fn spawnChildAndCollect(
1240 argv: []const []const u8,1298 argv: []const []const u8,
1241 has_side_effects: bool,1299 has_side_effects: bool,
1242 prog_node: std.Progress.Node,1300 prog_node: std.Progress.Node,
1301 fuzz_unit_test_index: ?u32,
1243) !ChildProcResult {1302) !ChildProcResult {
1244 const b = run.step.owner;1303 const b = run.step.owner;
1245 const arena = b.allocator;1304 const arena = b.allocator;
12461305
1306 if (fuzz_unit_test_index != null) {
1307 assert(!has_side_effects);
1308 assert(run.stdio == .zig_test);
1309 }
1310
1247 var child = std.process.Child.init(argv, arena);1311 var child = std.process.Child.init(argv, arena);
1248 if (run.cwd) |lazy_cwd| {1312 if (run.cwd) |lazy_cwd| {
1249 child.cwd = lazy_cwd.getPath2(b, &run.step);1313 child.cwd = lazy_cwd.getPath2(b, &run.step);
...@@ -1293,7 +1357,7 @@ fn spawnChildAndCollect(...@@ -1293,7 +1357,7 @@ fn spawnChildAndCollect(
1293 var timer = try std.time.Timer.start();1357 var timer = try std.time.Timer.start();
12941358
1295 const result = if (run.stdio == .zig_test)1359 const result = if (run.stdio == .zig_test)
1296 evalZigTest(run, &child, prog_node)1360 evalZigTest(run, &child, prog_node, fuzz_unit_test_index)
1297 else1361 else
1298 evalGeneric(run, &child);1362 evalGeneric(run, &child);
12991363
...@@ -1319,6 +1383,7 @@ fn evalZigTest(...@@ -1319,6 +1383,7 @@ fn evalZigTest(
1319 run: *Run,1383 run: *Run,
1320 child: *std.process.Child,1384 child: *std.process.Child,
1321 prog_node: std.Progress.Node,1385 prog_node: std.Progress.Node,
1386 fuzz_unit_test_index: ?u32,
1322) !StdIoResult {1387) !StdIoResult {
1323 const gpa = run.step.owner.allocator;1388 const gpa = run.step.owner.allocator;
1324 const arena = run.step.owner.allocator;1389 const arena = run.step.owner.allocator;
...@@ -1329,7 +1394,12 @@ fn evalZigTest(...@@ -1329,7 +1394,12 @@ fn evalZigTest(
1329 });1394 });
1330 defer poller.deinit();1395 defer poller.deinit();
13311396
1332 try sendMessage(child.stdin.?, .query_test_metadata);1397 if (fuzz_unit_test_index) |index| {
1398 try sendRunTestMessage(child.stdin.?, .start_fuzzing, index);
1399 } else {
1400 run.fuzz_tests.clearRetainingCapacity();
1401 try sendMessage(child.stdin.?, .query_test_metadata);
1402 }
13331403
1334 const Header = std.zig.Server.Message.Header;1404 const Header = std.zig.Server.Message.Header;
13351405
...@@ -1367,6 +1437,7 @@ fn evalZigTest(...@@ -1367,6 +1437,7 @@ fn evalZigTest(
1367 }1437 }
1368 },1438 },
1369 .test_metadata => {1439 .test_metadata => {
1440 assert(fuzz_unit_test_index == null);
1370 const TmHdr = std.zig.Server.Message.TestMetadata;1441 const TmHdr = std.zig.Server.Message.TestMetadata;
1371 const tm_hdr = @as(*align(1) const TmHdr, @ptrCast(body));1442 const tm_hdr = @as(*align(1) const TmHdr, @ptrCast(body));
1372 test_count = tm_hdr.tests_len;1443 test_count = tm_hdr.tests_len;
...@@ -1395,6 +1466,7 @@ fn evalZigTest(...@@ -1395,6 +1466,7 @@ fn evalZigTest(
1395 try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node);1466 try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node);
1396 },1467 },
1397 .test_results => {1468 .test_results => {
1469 assert(fuzz_unit_test_index == null);
1398 const md = metadata.?;1470 const md = metadata.?;
13991471
1400 const TrHdr = std.zig.Server.Message.TestResults;1472 const TrHdr = std.zig.Server.Message.TestResults;
...@@ -1404,6 +1476,8 @@ fn evalZigTest(...@@ -1404,6 +1476,8 @@ fn evalZigTest(
1404 leak_count +|= @intFromBool(tr_hdr.flags.leak);1476 leak_count +|= @intFromBool(tr_hdr.flags.leak);
1405 log_err_count +|= tr_hdr.flags.log_err_count;1477 log_err_count +|= tr_hdr.flags.log_err_count;
14061478
1479 if (tr_hdr.flags.fuzz) try run.fuzz_tests.append(gpa, tr_hdr.index);
1480
1407 if (tr_hdr.flags.fail or tr_hdr.flags.leak or tr_hdr.flags.log_err_count > 0) {1481 if (tr_hdr.flags.fail or tr_hdr.flags.leak or tr_hdr.flags.log_err_count > 0) {
1408 const name = std.mem.sliceTo(md.string_bytes[md.names[tr_hdr.index]..], 0);1482 const name = std.mem.sliceTo(md.string_bytes[md.names[tr_hdr.index]..], 0);
1409 const orig_msg = stderr.readableSlice(0);1483 const orig_msg = stderr.readableSlice(0);
...@@ -1462,7 +1536,23 @@ const TestMetadata = struct {...@@ -1462,7 +1536,23 @@ const TestMetadata = struct {
1462 next_index: u32,1536 next_index: u32,
1463 prog_node: std.Progress.Node,1537 prog_node: std.Progress.Node,
14641538
1539 fn toCachedTestMetadata(tm: TestMetadata) CachedTestMetadata {
1540 return .{
1541 .names = tm.names,
1542 .string_bytes = tm.string_bytes,
1543 };
1544 }
1545
1465 fn testName(tm: TestMetadata, index: u32) []const u8 {1546 fn testName(tm: TestMetadata, index: u32) []const u8 {
1547 return tm.toCachedTestMetadata().testName(index);
1548 }
1549};
1550
1551pub const CachedTestMetadata = struct {
1552 names: []const u32,
1553 string_bytes: []const u8,
1554
1555 pub fn testName(tm: CachedTestMetadata, index: u32) []const u8 {
1466 return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0);1556 return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0);
1467 }1557 }
1468};1558};
...@@ -1478,7 +1568,7 @@ fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Pr...@@ -1478,7 +1568,7 @@ fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Pr
1478 if (sub_prog_node.*) |n| n.end();1568 if (sub_prog_node.*) |n| n.end();
1479 sub_prog_node.* = metadata.prog_node.start(name, 0);1569 sub_prog_node.* = metadata.prog_node.start(name, 0);
14801570
1481 try sendRunTestMessage(in, i);1571 try sendRunTestMessage(in, .run_test, i);
1482 return;1572 return;
1483 } else {1573 } else {
1484 try sendMessage(in, .exit);1574 try sendMessage(in, .exit);
...@@ -1493,9 +1583,9 @@ fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {...@@ -1493,9 +1583,9 @@ fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
1493 try file.writeAll(std.mem.asBytes(&header));1583 try file.writeAll(std.mem.asBytes(&header));
1494}1584}
14951585
1496fn sendRunTestMessage(file: std.fs.File, index: u32) !void {1586fn sendRunTestMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag, index: u32) !void {
1497 const header: std.zig.Client.Message.Header = .{1587 const header: std.zig.Client.Message.Header = .{
1498 .tag = .run_test,1588 .tag = tag,
1499 .bytes_len = 4,1589 .bytes_len = 4,
1500 };1590 };
1501 const full_msg = std.mem.asBytes(&header) ++ std.mem.asBytes(&index);1591 const full_msg = std.mem.asBytes(&header) ++ std.mem.asBytes(&index);
lib/std/io/test.zig+1-1
...@@ -16,7 +16,7 @@ test "write a file, read it, then delete it" {...@@ -16,7 +16,7 @@ test "write a file, read it, then delete it" {
16 defer tmp.cleanup();16 defer tmp.cleanup();
1717
18 var data: [1024]u8 = undefined;18 var data: [1024]u8 = undefined;
19 var prng = DefaultPrng.init(1234);19 var prng = DefaultPrng.init(std.testing.random_seed);
20 const random = prng.random();20 const random = prng.random();
21 random.bytes(data[0..]);21 random.bytes(data[0..]);
22 const tmp_file_name = "temp_test_file.txt";22 const tmp_file_name = "temp_test_file.txt";
lib/std/testing.zig+8
...@@ -1136,3 +1136,11 @@ pub fn refAllDeclsRecursive(comptime T: type) void {...@@ -1136,3 +1136,11 @@ pub fn refAllDeclsRecursive(comptime T: type) void {
1136 _ = &@field(T, decl.name);1136 _ = &@field(T, decl.name);
1137 }1137 }
1138}1138}
1139
1140pub const FuzzInputOptions = struct {
1141 corpus: []const []const u8 = &.{},
1142};
1143
1144pub inline fn fuzzInput(options: FuzzInputOptions) []const u8 {
1145 return @import("root").fuzzInput(options);
1146}
lib/std/zig/Client.zig+3
...@@ -33,6 +33,9 @@ pub const Message = struct {...@@ -33,6 +33,9 @@ pub const Message = struct {
33 /// Ask the test runner to run a particular test.33 /// Ask the test runner to run a particular test.
34 /// The message body is a u32 test index.34 /// The message body is a u32 test index.
35 run_test,35 run_test,
36 /// Ask the test runner to start fuzzing a particular test.
37 /// The message body is a u32 test index.
38 start_fuzzing,
3639
37 _,40 _,
38 };41 };
lib/std/zig/Server.zig+3-2
...@@ -53,7 +53,7 @@ pub const Message = struct {...@@ -53,7 +53,7 @@ pub const Message = struct {
53 /// - null-terminated string_bytes index53 /// - null-terminated string_bytes index
54 /// * expected_panic_msg: [tests_len]u32,54 /// * expected_panic_msg: [tests_len]u32,
55 /// - null-terminated string_bytes index55 /// - null-terminated string_bytes index
56 /// - 0 means does not expect pani56 /// - 0 means does not expect panic
57 /// * string_bytes: [string_bytes_len]u8,57 /// * string_bytes: [string_bytes_len]u8,
58 pub const TestMetadata = extern struct {58 pub const TestMetadata = extern struct {
59 string_bytes_len: u32,59 string_bytes_len: u32,
...@@ -68,7 +68,8 @@ pub const Message = struct {...@@ -68,7 +68,8 @@ pub const Message = struct {
68 fail: bool,68 fail: bool,
69 skip: bool,69 skip: bool,
70 leak: bool,70 leak: bool,
71 log_err_count: u29 = 0,71 fuzz: bool,
72 log_err_count: u28 = 0,
72 };73 };
73 };74 };
7475
src/Compilation.zig+3-1
...@@ -2180,7 +2180,9 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {...@@ -2180,7 +2180,9 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
2180 comp.bin_file = try link.File.createEmpty(arena, comp, emit, whole.lf_open_opts);2180 comp.bin_file = try link.File.createEmpty(arena, comp, emit, whole.lf_open_opts);
2181 }2181 }
2182 },2182 },
2183 .incremental => {},2183 .incremental => {
2184 log.debug("Compilation.update for {s}, CacheMode.incremental", .{comp.root_name});
2185 },
2184 }2186 }
21852187
2186 // From this point we add a preliminary set of file system inputs that2188 // From this point we add a preliminary set of file system inputs that
src/codegen/llvm.zig+4-8
...@@ -1392,16 +1392,12 @@ pub const Object = struct {...@@ -1392,16 +1392,12 @@ pub const Object = struct {
1392 }1392 }
1393 if (owner_mod.fuzz and !func_analysis.disable_instrumentation) {1393 if (owner_mod.fuzz and !func_analysis.disable_instrumentation) {
1394 try attributes.addFnAttr(.optforfuzzing, &o.builder);1394 try attributes.addFnAttr(.optforfuzzing, &o.builder);
1395 if (comp.config.any_fuzz) {1395 _ = try attributes.removeFnAttr(.skipprofile);
1396 _ = try attributes.removeFnAttr(.skipprofile);1396 _ = try attributes.removeFnAttr(.nosanitize_coverage);
1397 _ = try attributes.removeFnAttr(.nosanitize_coverage);
1398 }
1399 } else {1397 } else {
1400 _ = try attributes.removeFnAttr(.optforfuzzing);1398 _ = try attributes.removeFnAttr(.optforfuzzing);
1401 if (comp.config.any_fuzz) {1399 try attributes.addFnAttr(.skipprofile, &o.builder);
1402 try attributes.addFnAttr(.skipprofile, &o.builder);1400 try attributes.addFnAttr(.nosanitize_coverage, &o.builder);
1403 try attributes.addFnAttr(.nosanitize_coverage, &o.builder);
1404 }
1405 }1401 }
14061402
1407 // TODO: disable this if safety is off for the function scope1403 // TODO: disable this if safety is off for the function scope
src/link/Elf.zig+2
...@@ -2286,6 +2286,8 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s...@@ -2286,6 +2286,8 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
2286 }2286 }
2287 try man.addOptionalFile(module_obj_path);2287 try man.addOptionalFile(module_obj_path);
2288 try man.addOptionalFile(compiler_rt_path);2288 try man.addOptionalFile(compiler_rt_path);
2289 try man.addOptionalFile(if (comp.tsan_lib) |l| l.full_object_path else null);
2290 try man.addOptionalFile(if (comp.fuzzer_lib) |l| l.full_object_path else null);
22892291
2290 // We can skip hashing libc and libc++ components that we are in charge of building from Zig2292 // We can skip hashing libc and libc++ components that we are in charge of building from Zig
2291 // installation sources because they are always a product of the compiler version + target information.2293 // installation sources because they are always a product of the compiler version + target information.
src/main.zig+5
...@@ -655,6 +655,7 @@ const usage_build_generic =...@@ -655,6 +655,7 @@ const usage_build_generic =
655 \\ --debug-log [scope] Enable printing debug/info log messages for scope655 \\ --debug-log [scope] Enable printing debug/info log messages for scope
656 \\ --debug-compile-errors Crash with helpful diagnostics at the first compile error656 \\ --debug-compile-errors Crash with helpful diagnostics at the first compile error
657 \\ --debug-link-snapshot Enable dumping of the linker's state in JSON format657 \\ --debug-link-snapshot Enable dumping of the linker's state in JSON format
658 \\ --debug-rt Debug compiler runtime libraries
658 \\659 \\
659;660;
660661
...@@ -912,6 +913,7 @@ fn buildOutputType(...@@ -912,6 +913,7 @@ fn buildOutputType(
912 var minor_subsystem_version: ?u16 = null;913 var minor_subsystem_version: ?u16 = null;
913 var mingw_unicode_entry_point: bool = false;914 var mingw_unicode_entry_point: bool = false;
914 var enable_link_snapshots: bool = false;915 var enable_link_snapshots: bool = false;
916 var debug_compiler_runtime_libs = false;
915 var opt_incremental: ?bool = null;917 var opt_incremental: ?bool = null;
916 var install_name: ?[]const u8 = null;918 var install_name: ?[]const u8 = null;
917 var hash_style: link.File.Elf.HashStyle = .both;919 var hash_style: link.File.Elf.HashStyle = .both;
...@@ -1367,6 +1369,8 @@ fn buildOutputType(...@@ -1367,6 +1369,8 @@ fn buildOutputType(
1367 } else {1369 } else {
1368 enable_link_snapshots = true;1370 enable_link_snapshots = true;
1369 }1371 }
1372 } else if (mem.eql(u8, arg, "--debug-rt")) {
1373 debug_compiler_runtime_libs = true;
1370 } else if (mem.eql(u8, arg, "-fincremental")) {1374 } else if (mem.eql(u8, arg, "-fincremental")) {
1371 dev.check(.incremental);1375 dev.check(.incremental);
1372 opt_incremental = true;1376 opt_incremental = true;
...@@ -3408,6 +3412,7 @@ fn buildOutputType(...@@ -3408,6 +3412,7 @@ fn buildOutputType(
3408 // noise when --search-prefix and --mod are combined.3412 // noise when --search-prefix and --mod are combined.
3409 .global_cc_argv = try cc_argv.toOwnedSlice(arena),3413 .global_cc_argv = try cc_argv.toOwnedSlice(arena),
3410 .file_system_inputs = &file_system_inputs,3414 .file_system_inputs = &file_system_inputs,
3415 .debug_compiler_runtime_libs = debug_compiler_runtime_libs,
3411 }) catch |err| switch (err) {3416 }) catch |err| switch (err) {
3412 error.LibCUnavailable => {3417 error.LibCUnavailable => {
3413 const triple_name = try target.zigTriple(arena);3418 const triple_name = try target.zigTriple(arena);