mirror of
https://github.com/ziglang/zig.git
synced 2024-12-03 01:30:15 +00:00
Merge pull request #20773 from ziglang/fuzz
integrate fuzz testing into the build system
This commit is contained in:
commit
afddfe25d8
@ -9,8 +9,10 @@ const ArrayList = std.ArrayList;
|
||||
const File = std.fs.File;
|
||||
const Step = std.Build.Step;
|
||||
const Watch = std.Build.Watch;
|
||||
const Fuzz = std.Build.Fuzz;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const fatal = std.zig.fatal;
|
||||
const fatal = std.process.fatal;
|
||||
const runner = @This();
|
||||
|
||||
pub const root = @import("@build");
|
||||
pub const dependencies = @import("@dependencies");
|
||||
@ -102,6 +104,7 @@ pub fn main() !void {
|
||||
var steps_menu = false;
|
||||
var output_tmp_nonce: ?[16]u8 = null;
|
||||
var watch = false;
|
||||
var fuzz = false;
|
||||
var debounce_interval_ms: u16 = 50;
|
||||
|
||||
while (nextArg(args, &arg_idx)) |arg| {
|
||||
@ -205,6 +208,8 @@ pub fn main() !void {
|
||||
try debug_log_scopes.append(next_arg);
|
||||
} else if (mem.eql(u8, arg, "--debug-pkg-config")) {
|
||||
builder.debug_pkg_config = true;
|
||||
} else if (mem.eql(u8, arg, "--debug-rt")) {
|
||||
graph.debug_compiler_runtime_libs = true;
|
||||
} else if (mem.eql(u8, arg, "--debug-compile-errors")) {
|
||||
builder.debug_compile_errors = true;
|
||||
} else if (mem.eql(u8, arg, "--system")) {
|
||||
@ -234,6 +239,8 @@ pub fn main() !void {
|
||||
prominent_compile_errors = true;
|
||||
} else if (mem.eql(u8, arg, "--watch")) {
|
||||
watch = true;
|
||||
} else if (mem.eql(u8, arg, "--fuzz")) {
|
||||
fuzz = true;
|
||||
} else if (mem.eql(u8, arg, "-fincremental")) {
|
||||
graph.incremental = true;
|
||||
} else if (mem.eql(u8, arg, "-fno-incremental")) {
|
||||
@ -353,6 +360,7 @@ pub fn main() !void {
|
||||
.max_rss_mutex = .{},
|
||||
.skip_oom_steps = skip_oom_steps,
|
||||
.watch = watch,
|
||||
.fuzz = fuzz,
|
||||
.memory_blocked_steps = std.ArrayList(*Step).init(arena),
|
||||
.step_stack = .{},
|
||||
.prominent_compile_errors = prominent_compile_errors,
|
||||
@ -394,6 +402,10 @@ pub fn main() !void {
|
||||
},
|
||||
else => return err,
|
||||
};
|
||||
if (fuzz) {
|
||||
Fuzz.start(&run.thread_pool, run.step_stack.keys(), run.ttyconf, main_progress_node);
|
||||
}
|
||||
|
||||
if (!watch) return cleanExit();
|
||||
|
||||
switch (builtin.os.tag) {
|
||||
@ -457,6 +469,7 @@ const Run = struct {
|
||||
max_rss_mutex: std.Thread.Mutex,
|
||||
skip_oom_steps: bool,
|
||||
watch: bool,
|
||||
fuzz: bool,
|
||||
memory_blocked_steps: std.ArrayList(*Step),
|
||||
step_stack: std.AutoArrayHashMapUnmanaged(*Step, void),
|
||||
prominent_compile_errors: bool,
|
||||
@ -466,6 +479,11 @@ const Run = struct {
|
||||
summary: Summary,
|
||||
ttyconf: std.io.tty.Config,
|
||||
stderr: File,
|
||||
|
||||
fn cleanExit(run: Run) void {
|
||||
if (run.watch or run.fuzz) return;
|
||||
return runner.cleanExit();
|
||||
}
|
||||
};
|
||||
|
||||
fn prepare(
|
||||
@ -614,8 +632,7 @@ fn runStepNames(
|
||||
else => false,
|
||||
};
|
||||
if (failure_count == 0 and failures_only) {
|
||||
if (!run.watch) cleanExit();
|
||||
return;
|
||||
return run.cleanExit();
|
||||
}
|
||||
|
||||
const ttyconf = run.ttyconf;
|
||||
@ -672,8 +689,7 @@ fn runStepNames(
|
||||
}
|
||||
|
||||
if (failure_count == 0) {
|
||||
if (!run.watch) cleanExit();
|
||||
return;
|
||||
return run.cleanExit();
|
||||
}
|
||||
|
||||
// Finally, render compile errors at the bottom of the terminal.
|
||||
@ -1058,7 +1074,8 @@ fn workerMakeOneStep(
|
||||
std.debug.lockStdErr();
|
||||
defer std.debug.unlockStdErr();
|
||||
|
||||
printErrorMessages(b, s, run) catch {};
|
||||
const gpa = b.allocator;
|
||||
printErrorMessages(gpa, s, run.ttyconf, run.stderr, run.prominent_compile_errors) catch {};
|
||||
}
|
||||
|
||||
handle_result: {
|
||||
@ -1111,11 +1128,13 @@ fn workerMakeOneStep(
|
||||
}
|
||||
}
|
||||
|
||||
fn printErrorMessages(b: *std.Build, failing_step: *Step, run: *const Run) !void {
|
||||
const gpa = b.allocator;
|
||||
const stderr = run.stderr;
|
||||
const ttyconf = run.ttyconf;
|
||||
|
||||
pub fn printErrorMessages(
|
||||
gpa: Allocator,
|
||||
failing_step: *Step,
|
||||
ttyconf: std.io.tty.Config,
|
||||
stderr: File,
|
||||
prominent_compile_errors: bool,
|
||||
) !void {
|
||||
// Provide context for where these error messages are coming from by
|
||||
// printing the corresponding Step subtree.
|
||||
|
||||
@ -1152,7 +1171,7 @@ fn printErrorMessages(b: *std.Build, failing_step: *Step, run: *const Run) !void
|
||||
}
|
||||
}
|
||||
|
||||
if (!run.prominent_compile_errors and failing_step.result_error_bundle.errorMessageCount() > 0)
|
||||
if (!prominent_compile_errors and failing_step.result_error_bundle.errorMessageCount() > 0)
|
||||
try failing_step.result_error_bundle.renderToWriter(renderOptions(ttyconf), stderr.writer());
|
||||
|
||||
for (failing_step.result_error_msgs.items) |msg| {
|
||||
@ -1226,6 +1245,7 @@ fn usage(b: *std.Build, out_stream: anytype) !void {
|
||||
\\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss
|
||||
\\ --fetch Exit after fetching dependency tree
|
||||
\\ --watch Continuously rebuild when source files are modified
|
||||
\\ --fuzz Continuously search for unit test failures
|
||||
\\ --debounce <ms> Delay before rebuilding after changed file detected
|
||||
\\ -fincremental Enable incremental compilation
|
||||
\\ -fno-incremental Disable incremental compilation
|
||||
@ -1294,6 +1314,7 @@ fn usage(b: *std.Build, out_stream: anytype) !void {
|
||||
\\ --seed [integer] For shuffling dependency traversal order (default: random)
|
||||
\\ --debug-log [scope] Enable debugging the compiler
|
||||
\\ --debug-pkg-config Fail if unknown pkg-config flags encountered
|
||||
\\ --debug-rt Debug compiler runtime libraries
|
||||
\\ --verbose-link Enable compiler debug output for linking
|
||||
\\ --verbose-air Enable compiler debug output for Zig AIR
|
||||
\\ --verbose-llvm-ir[=file] Enable compiler debug output for LLVM IR
|
||||
|
@ -1,18 +1,26 @@
|
||||
//! Default test runner for unit tests.
|
||||
const builtin = @import("builtin");
|
||||
const std = @import("std");
|
||||
const io = std.io;
|
||||
const builtin = @import("builtin");
|
||||
const testing = std.testing;
|
||||
|
||||
pub const std_options = .{
|
||||
.logFn = log,
|
||||
};
|
||||
|
||||
var log_err_count: usize = 0;
|
||||
var cmdline_buffer: [4096]u8 = undefined;
|
||||
var fba = std.heap.FixedBufferAllocator.init(&cmdline_buffer);
|
||||
var fba_buffer: [8192]u8 = undefined;
|
||||
var fba = std.heap.FixedBufferAllocator.init(&fba_buffer);
|
||||
|
||||
const crippled = switch (builtin.zig_backend) {
|
||||
.stage2_riscv64 => true,
|
||||
else => false,
|
||||
};
|
||||
|
||||
pub fn main() void {
|
||||
if (builtin.zig_backend == .stage2_riscv64) {
|
||||
@disableInstrumentation();
|
||||
|
||||
if (crippled) {
|
||||
return mainSimple() catch @panic("test failure\n");
|
||||
}
|
||||
|
||||
@ -25,13 +33,15 @@ pub fn main() void {
|
||||
if (std.mem.eql(u8, arg, "--listen=-")) {
|
||||
listen = true;
|
||||
} else if (std.mem.startsWith(u8, arg, "--seed=")) {
|
||||
std.testing.random_seed = std.fmt.parseUnsigned(u32, arg["--seed=".len..], 0) catch
|
||||
testing.random_seed = std.fmt.parseUnsigned(u32, arg["--seed=".len..], 0) catch
|
||||
@panic("unable to parse --seed command line argument");
|
||||
} else {
|
||||
@panic("unrecognized command line argument");
|
||||
}
|
||||
}
|
||||
|
||||
fba.reset();
|
||||
|
||||
if (listen) {
|
||||
return mainServer() catch @panic("internal test runner failure");
|
||||
} else {
|
||||
@ -40,6 +50,7 @@ pub fn main() void {
|
||||
}
|
||||
|
||||
fn mainServer() !void {
|
||||
@disableInstrumentation();
|
||||
var server = try std.zig.Server.init(.{
|
||||
.gpa = fba.allocator(),
|
||||
.in = std.io.getStdIn(),
|
||||
@ -55,24 +66,24 @@ fn mainServer() !void {
|
||||
return std.process.exit(0);
|
||||
},
|
||||
.query_test_metadata => {
|
||||
std.testing.allocator_instance = .{};
|
||||
defer if (std.testing.allocator_instance.deinit() == .leak) {
|
||||
testing.allocator_instance = .{};
|
||||
defer if (testing.allocator_instance.deinit() == .leak) {
|
||||
@panic("internal test runner memory leak");
|
||||
};
|
||||
|
||||
var string_bytes: std.ArrayListUnmanaged(u8) = .{};
|
||||
defer string_bytes.deinit(std.testing.allocator);
|
||||
try string_bytes.append(std.testing.allocator, 0); // Reserve 0 for null.
|
||||
defer string_bytes.deinit(testing.allocator);
|
||||
try string_bytes.append(testing.allocator, 0); // Reserve 0 for null.
|
||||
|
||||
const test_fns = builtin.test_functions;
|
||||
const names = try std.testing.allocator.alloc(u32, test_fns.len);
|
||||
defer std.testing.allocator.free(names);
|
||||
const expected_panic_msgs = try std.testing.allocator.alloc(u32, test_fns.len);
|
||||
defer std.testing.allocator.free(expected_panic_msgs);
|
||||
const names = try testing.allocator.alloc(u32, test_fns.len);
|
||||
defer testing.allocator.free(names);
|
||||
const expected_panic_msgs = try testing.allocator.alloc(u32, test_fns.len);
|
||||
defer testing.allocator.free(expected_panic_msgs);
|
||||
|
||||
for (test_fns, names, expected_panic_msgs) |test_fn, *name, *expected_panic_msg| {
|
||||
name.* = @as(u32, @intCast(string_bytes.items.len));
|
||||
try string_bytes.ensureUnusedCapacity(std.testing.allocator, test_fn.name.len + 1);
|
||||
try string_bytes.ensureUnusedCapacity(testing.allocator, test_fn.name.len + 1);
|
||||
string_bytes.appendSliceAssumeCapacity(test_fn.name);
|
||||
string_bytes.appendAssumeCapacity(0);
|
||||
expected_panic_msg.* = 0;
|
||||
@ -86,13 +97,13 @@ fn mainServer() !void {
|
||||
},
|
||||
|
||||
.run_test => {
|
||||
std.testing.allocator_instance = .{};
|
||||
testing.allocator_instance = .{};
|
||||
log_err_count = 0;
|
||||
const index = try server.receiveBody_u32();
|
||||
const test_fn = builtin.test_functions[index];
|
||||
var fail = false;
|
||||
var skip = false;
|
||||
var leak = false;
|
||||
is_fuzz_test = false;
|
||||
test_fn.func() catch |err| switch (err) {
|
||||
error.SkipZigTest => skip = true,
|
||||
else => {
|
||||
@ -102,13 +113,14 @@ fn mainServer() !void {
|
||||
}
|
||||
},
|
||||
};
|
||||
leak = std.testing.allocator_instance.deinit() == .leak;
|
||||
const leak = testing.allocator_instance.deinit() == .leak;
|
||||
try server.serveTestResults(.{
|
||||
.index = index,
|
||||
.flags = .{
|
||||
.fail = fail,
|
||||
.skip = skip,
|
||||
.leak = leak,
|
||||
.fuzz = is_fuzz_test,
|
||||
.log_err_count = std.math.lossyCast(
|
||||
@TypeOf(@as(std.zig.Server.Message.TestResults.Flags, undefined).log_err_count),
|
||||
log_err_count,
|
||||
@ -116,9 +128,31 @@ fn mainServer() !void {
|
||||
},
|
||||
});
|
||||
},
|
||||
.start_fuzzing => {
|
||||
const index = try server.receiveBody_u32();
|
||||
const test_fn = builtin.test_functions[index];
|
||||
while (true) {
|
||||
testing.allocator_instance = .{};
|
||||
defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1);
|
||||
log_err_count = 0;
|
||||
is_fuzz_test = false;
|
||||
test_fn.func() catch |err| switch (err) {
|
||||
error.SkipZigTest => continue,
|
||||
else => {
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
}
|
||||
std.debug.print("failed with error.{s}\n", .{@errorName(err)});
|
||||
std.process.exit(1);
|
||||
},
|
||||
};
|
||||
if (!is_fuzz_test) @panic("missed call to std.testing.fuzzInput");
|
||||
if (log_err_count != 0) @panic("error logs detected");
|
||||
}
|
||||
},
|
||||
|
||||
else => {
|
||||
std.debug.print("unsupported message: {x}", .{@intFromEnum(hdr.tag)});
|
||||
std.debug.print("unsupported message: {x}\n", .{@intFromEnum(hdr.tag)});
|
||||
std.process.exit(1);
|
||||
},
|
||||
}
|
||||
@ -126,10 +160,12 @@ fn mainServer() !void {
|
||||
}
|
||||
|
||||
fn mainTerminal() void {
|
||||
@disableInstrumentation();
|
||||
const test_fn_list = builtin.test_functions;
|
||||
var ok_count: usize = 0;
|
||||
var skip_count: usize = 0;
|
||||
var fail_count: usize = 0;
|
||||
var fuzz_count: usize = 0;
|
||||
const root_node = std.Progress.start(.{
|
||||
.root_name = "Test",
|
||||
.estimated_total_items = test_fn_list.len,
|
||||
@ -143,18 +179,19 @@ fn mainTerminal() void {
|
||||
|
||||
var leaks: usize = 0;
|
||||
for (test_fn_list, 0..) |test_fn, i| {
|
||||
std.testing.allocator_instance = .{};
|
||||
testing.allocator_instance = .{};
|
||||
defer {
|
||||
if (std.testing.allocator_instance.deinit() == .leak) {
|
||||
if (testing.allocator_instance.deinit() == .leak) {
|
||||
leaks += 1;
|
||||
}
|
||||
}
|
||||
std.testing.log_level = .warn;
|
||||
testing.log_level = .warn;
|
||||
|
||||
const test_node = root_node.start(test_fn.name, 0);
|
||||
if (!have_tty) {
|
||||
std.debug.print("{d}/{d} {s}...", .{ i + 1, test_fn_list.len, test_fn.name });
|
||||
}
|
||||
is_fuzz_test = false;
|
||||
if (test_fn.func()) |_| {
|
||||
ok_count += 1;
|
||||
test_node.end();
|
||||
@ -184,6 +221,7 @@ fn mainTerminal() void {
|
||||
test_node.end();
|
||||
},
|
||||
}
|
||||
fuzz_count += @intFromBool(is_fuzz_test);
|
||||
}
|
||||
root_node.end();
|
||||
if (ok_count == test_fn_list.len) {
|
||||
@ -197,6 +235,9 @@ fn mainTerminal() void {
|
||||
if (leaks != 0) {
|
||||
std.debug.print("{d} tests leaked memory.\n", .{leaks});
|
||||
}
|
||||
if (fuzz_count != 0) {
|
||||
std.debug.print("{d} fuzz tests found.\n", .{fuzz_count});
|
||||
}
|
||||
if (leaks != 0 or log_err_count != 0 or fail_count != 0) {
|
||||
std.process.exit(1);
|
||||
}
|
||||
@ -208,10 +249,11 @@ pub fn log(
|
||||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
) void {
|
||||
@disableInstrumentation();
|
||||
if (@intFromEnum(message_level) <= @intFromEnum(std.log.Level.err)) {
|
||||
log_err_count +|= 1;
|
||||
}
|
||||
if (@intFromEnum(message_level) <= @intFromEnum(std.testing.log_level)) {
|
||||
if (@intFromEnum(message_level) <= @intFromEnum(testing.log_level)) {
|
||||
std.debug.print(
|
||||
"[" ++ @tagName(scope) ++ "] (" ++ @tagName(message_level) ++ "): " ++ format ++ "\n",
|
||||
args,
|
||||
@ -222,6 +264,7 @@ pub fn log(
|
||||
/// Simpler main(), exercising fewer language features, so that
|
||||
/// work-in-progress backends can handle it.
|
||||
pub fn mainSimple() anyerror!void {
|
||||
@disableInstrumentation();
|
||||
// is the backend capable of printing to stderr?
|
||||
const enable_print = switch (builtin.zig_backend) {
|
||||
else => false,
|
||||
@ -266,3 +309,27 @@ pub fn mainSimple() anyerror!void {
|
||||
}
|
||||
if (failed != 0) std.process.exit(1);
|
||||
}
|
||||
|
||||
const FuzzerSlice = extern struct {
|
||||
ptr: [*]const u8,
|
||||
len: usize,
|
||||
|
||||
inline fn toSlice(s: FuzzerSlice) []const u8 {
|
||||
return s.ptr[0..s.len];
|
||||
}
|
||||
};
|
||||
|
||||
var is_fuzz_test: bool = undefined;
|
||||
|
||||
extern fn fuzzer_next() FuzzerSlice;
|
||||
|
||||
pub fn fuzzInput(options: testing.FuzzInputOptions) []const u8 {
|
||||
@disableInstrumentation();
|
||||
if (crippled) return "";
|
||||
is_fuzz_test = true;
|
||||
if (builtin.fuzz) return fuzzer_next().toSlice();
|
||||
if (options.corpus.len == 0) return "";
|
||||
var prng = std.Random.DefaultPrng.init(testing.random_seed);
|
||||
const random = prng.random();
|
||||
return options.corpus[random.uintLessThan(usize, options.corpus.len)];
|
||||
}
|
||||
|
271
lib/fuzzer.zig
271
lib/fuzzer.zig
@ -1,13 +1,43 @@
|
||||
const builtin = @import("builtin");
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
pub const std_options = .{
|
||||
.logFn = logOverride,
|
||||
};
|
||||
|
||||
var log_file: ?std.fs.File = null;
|
||||
|
||||
fn logOverride(
|
||||
comptime level: std.log.Level,
|
||||
comptime scope: @TypeOf(.EnumLiteral),
|
||||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
) void {
|
||||
if (builtin.mode != .Debug) return;
|
||||
const f = if (log_file) |f| f else f: {
|
||||
const f = std.fs.cwd().createFile("libfuzzer.log", .{}) catch @panic("failed to open fuzzer log file");
|
||||
log_file = f;
|
||||
break :f f;
|
||||
};
|
||||
const prefix1 = comptime level.asText();
|
||||
const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
|
||||
f.writer().print(prefix1 ++ prefix2 ++ format ++ "\n", args) catch @panic("failed to write to fuzzer log");
|
||||
}
|
||||
|
||||
export threadlocal var __sancov_lowest_stack: usize = 0;
|
||||
|
||||
export fn __sanitizer_cov_8bit_counters_init(start: [*]u8, stop: [*]u8) void {
|
||||
std.debug.print("__sanitizer_cov_8bit_counters_init start={*}, stop={*}\n", .{ start, stop });
|
||||
std.log.debug("__sanitizer_cov_8bit_counters_init start={*}, stop={*}", .{ start, stop });
|
||||
}
|
||||
|
||||
export fn __sanitizer_cov_pcs_init(pcs_beg: [*]const usize, pcs_end: [*]const usize) void {
|
||||
std.debug.print("__sanitizer_cov_pcs_init pcs_beg={*}, pcs_end={*}\n", .{ pcs_beg, pcs_end });
|
||||
export fn __sanitizer_cov_pcs_init(pc_start: [*]const usize, pc_end: [*]const usize) void {
|
||||
std.log.debug("__sanitizer_cov_pcs_init pc_start={*}, pc_end={*}", .{ pc_start, pc_end });
|
||||
fuzzer.pc_range = .{
|
||||
.start = @intFromPtr(pc_start),
|
||||
.end = @intFromPtr(pc_start),
|
||||
};
|
||||
}
|
||||
|
||||
export 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 {
|
||||
const len = cases_ptr[0];
|
||||
const val_size_in_bits = cases_ptr[1];
|
||||
const cases = cases_ptr[2..][0..len];
|
||||
std.debug.print("0x{x}: switch on value {d} ({d} bits) with {d} cases\n", .{
|
||||
pc, val, val_size_in_bits, cases.len,
|
||||
});
|
||||
_ = val;
|
||||
fuzzer.visitPc(pc);
|
||||
_ = val_size_in_bits;
|
||||
_ = cases;
|
||||
//std.log.debug("0x{x}: switch on value {d} ({d} bits) with {d} cases", .{
|
||||
// pc, val, val_size_in_bits, cases.len,
|
||||
//});
|
||||
}
|
||||
|
||||
export fn __sanitizer_cov_trace_pc_indir(callee: usize) void {
|
||||
const pc = @returnAddress();
|
||||
std.debug.print("0x{x}: indirect call to 0x{x}\n", .{ pc, callee });
|
||||
_ = callee;
|
||||
fuzzer.visitPc(pc);
|
||||
//std.log.debug("0x{x}: indirect call to 0x{x}", .{ pc, callee });
|
||||
}
|
||||
|
||||
fn handleCmp(pc: usize, arg1: u64, arg2: u64) void {
|
||||
std.debug.print("0x{x}: comparison of {d} and {d}\n", .{ pc, arg1, arg2 });
|
||||
fuzzer.visitPc(pc ^ arg1 ^ arg2);
|
||||
//std.log.debug("0x{x}: comparison of {d} and {d}", .{ pc, arg1, arg2 });
|
||||
}
|
||||
|
||||
const Fuzzer = struct {
|
||||
gpa: Allocator,
|
||||
rng: std.Random.DefaultPrng,
|
||||
input: std.ArrayListUnmanaged(u8),
|
||||
pc_range: PcRange,
|
||||
count: usize,
|
||||
recent_cases: RunMap,
|
||||
deduplicated_runs: usize,
|
||||
coverage: Coverage,
|
||||
|
||||
const RunMap = std.ArrayHashMapUnmanaged(Run, void, Run.HashContext, false);
|
||||
|
||||
const Coverage = struct {
|
||||
pc_table: std.AutoArrayHashMapUnmanaged(usize, void),
|
||||
run_id_hasher: std.hash.Wyhash,
|
||||
|
||||
fn reset(cov: *Coverage) void {
|
||||
cov.pc_table.clearRetainingCapacity();
|
||||
cov.run_id_hasher = std.hash.Wyhash.init(0);
|
||||
}
|
||||
};
|
||||
|
||||
const Run = struct {
|
||||
id: Id,
|
||||
input: []const u8,
|
||||
score: usize,
|
||||
|
||||
const Id = u64;
|
||||
|
||||
const HashContext = struct {
|
||||
pub fn eql(ctx: HashContext, a: Run, b: Run, b_index: usize) bool {
|
||||
_ = b_index;
|
||||
_ = ctx;
|
||||
return a.id == b.id;
|
||||
}
|
||||
pub fn hash(ctx: HashContext, a: Run) u32 {
|
||||
_ = ctx;
|
||||
return @truncate(a.id);
|
||||
}
|
||||
};
|
||||
|
||||
fn deinit(run: *Run, gpa: Allocator) void {
|
||||
gpa.free(run.input);
|
||||
run.* = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const Slice = extern struct {
|
||||
ptr: [*]const u8,
|
||||
len: usize,
|
||||
|
||||
fn toZig(s: Slice) []const u8 {
|
||||
return s.ptr[0..s.len];
|
||||
}
|
||||
|
||||
fn fromZig(s: []const u8) Slice {
|
||||
return .{
|
||||
.ptr = s.ptr,
|
||||
.len = s.len,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const PcRange = struct {
|
||||
start: usize,
|
||||
end: usize,
|
||||
};
|
||||
|
||||
const Analysis = struct {
|
||||
score: usize,
|
||||
id: Run.Id,
|
||||
};
|
||||
|
||||
fn analyzeLastRun(f: *Fuzzer) Analysis {
|
||||
return .{
|
||||
.id = f.coverage.run_id_hasher.final(),
|
||||
.score = f.coverage.pc_table.count(),
|
||||
};
|
||||
}
|
||||
|
||||
fn next(f: *Fuzzer) ![]const u8 {
|
||||
const gpa = f.gpa;
|
||||
const rng = fuzzer.rng.random();
|
||||
|
||||
if (f.recent_cases.entries.len == 0) {
|
||||
// Prepare initial input.
|
||||
try f.recent_cases.ensureUnusedCapacity(gpa, 100);
|
||||
const len = rng.uintLessThanBiased(usize, 80);
|
||||
try f.input.resize(gpa, len);
|
||||
rng.bytes(f.input.items);
|
||||
f.recent_cases.putAssumeCapacity(.{
|
||||
.id = 0,
|
||||
.input = try gpa.dupe(u8, f.input.items),
|
||||
.score = 0,
|
||||
}, {});
|
||||
} else {
|
||||
if (f.count % 1000 == 0) f.dumpStats();
|
||||
|
||||
const analysis = f.analyzeLastRun();
|
||||
const gop = f.recent_cases.getOrPutAssumeCapacity(.{
|
||||
.id = analysis.id,
|
||||
.input = undefined,
|
||||
.score = undefined,
|
||||
});
|
||||
if (gop.found_existing) {
|
||||
//std.log.info("duplicate analysis: score={d} id={d}", .{ analysis.score, analysis.id });
|
||||
f.deduplicated_runs += 1;
|
||||
if (f.input.items.len < gop.key_ptr.input.len or gop.key_ptr.score == 0) {
|
||||
gpa.free(gop.key_ptr.input);
|
||||
gop.key_ptr.input = try gpa.dupe(u8, f.input.items);
|
||||
gop.key_ptr.score = analysis.score;
|
||||
}
|
||||
} else {
|
||||
std.log.info("unique analysis: score={d} id={d}", .{ analysis.score, analysis.id });
|
||||
gop.key_ptr.* = .{
|
||||
.id = analysis.id,
|
||||
.input = try gpa.dupe(u8, f.input.items),
|
||||
.score = analysis.score,
|
||||
};
|
||||
}
|
||||
|
||||
if (f.recent_cases.entries.len >= 100) {
|
||||
const Context = struct {
|
||||
values: []const Run,
|
||||
pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
|
||||
return ctx.values[b_index].score < ctx.values[a_index].score;
|
||||
}
|
||||
};
|
||||
f.recent_cases.sortUnstable(Context{ .values = f.recent_cases.keys() });
|
||||
const cap = 50;
|
||||
// This has to be done before deinitializing the deleted items.
|
||||
const doomed_runs = f.recent_cases.keys()[cap..];
|
||||
f.recent_cases.shrinkRetainingCapacity(cap);
|
||||
for (doomed_runs) |*run| {
|
||||
std.log.info("culling score={d} id={d}", .{ run.score, run.id });
|
||||
run.deinit(gpa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const chosen_index = rng.uintLessThanBiased(usize, f.recent_cases.entries.len);
|
||||
const run = &f.recent_cases.keys()[chosen_index];
|
||||
f.input.clearRetainingCapacity();
|
||||
f.input.appendSliceAssumeCapacity(run.input);
|
||||
try f.mutate();
|
||||
|
||||
f.coverage.reset();
|
||||
f.count += 1;
|
||||
return f.input.items;
|
||||
}
|
||||
|
||||
fn visitPc(f: *Fuzzer, pc: usize) void {
|
||||
errdefer |err| oom(err);
|
||||
try f.coverage.pc_table.put(f.gpa, pc, {});
|
||||
f.coverage.run_id_hasher.update(std.mem.asBytes(&pc));
|
||||
}
|
||||
|
||||
fn dumpStats(f: *Fuzzer) void {
|
||||
std.log.info("stats: runs={d} deduplicated={d}", .{
|
||||
f.count,
|
||||
f.deduplicated_runs,
|
||||
});
|
||||
for (f.recent_cases.keys()[0..@min(f.recent_cases.entries.len, 5)], 0..) |run, i| {
|
||||
std.log.info("best[{d}] id={x} score={d} input: '{}'", .{
|
||||
i, run.id, run.score, std.zig.fmtEscapes(run.input),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn mutate(f: *Fuzzer) !void {
|
||||
const gpa = f.gpa;
|
||||
const rng = fuzzer.rng.random();
|
||||
|
||||
if (f.input.items.len == 0) {
|
||||
const len = rng.uintLessThanBiased(usize, 80);
|
||||
try f.input.resize(gpa, len);
|
||||
rng.bytes(f.input.items);
|
||||
return;
|
||||
}
|
||||
|
||||
const index = rng.uintLessThanBiased(usize, f.input.items.len * 3);
|
||||
if (index < f.input.items.len) {
|
||||
f.input.items[index] = rng.int(u8);
|
||||
} else if (index < f.input.items.len * 2) {
|
||||
_ = f.input.orderedRemove(index - f.input.items.len);
|
||||
} else if (index < f.input.items.len * 3) {
|
||||
try f.input.insert(gpa, index - f.input.items.len * 2, rng.int(u8));
|
||||
} else {
|
||||
unreachable;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fn oom(err: anytype) noreturn {
|
||||
switch (err) {
|
||||
error.OutOfMemory => @panic("out of memory"),
|
||||
}
|
||||
}
|
||||
|
||||
var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .{};
|
||||
|
||||
var fuzzer: Fuzzer = .{
|
||||
.gpa = general_purpose_allocator.allocator(),
|
||||
.rng = std.Random.DefaultPrng.init(0),
|
||||
.input = .{},
|
||||
.pc_range = .{ .start = 0, .end = 0 },
|
||||
.count = 0,
|
||||
.deduplicated_runs = 0,
|
||||
.recent_cases = .{},
|
||||
.coverage = undefined,
|
||||
};
|
||||
|
||||
export fn fuzzer_next() Fuzzer.Slice {
|
||||
return Fuzzer.Slice.fromZig(fuzzer.next() catch |err| switch (err) {
|
||||
error.OutOfMemory => @panic("out of memory"),
|
||||
});
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ pub const Cache = @import("Build/Cache.zig");
|
||||
pub const Step = @import("Build/Step.zig");
|
||||
pub const Module = @import("Build/Module.zig");
|
||||
pub const Watch = @import("Build/Watch.zig");
|
||||
pub const Fuzz = @import("Build/Fuzz.zig");
|
||||
|
||||
/// Shared state among all Build instances.
|
||||
graph: *Graph,
|
||||
@ -112,6 +113,7 @@ pub const Graph = struct {
|
||||
arena: Allocator,
|
||||
system_library_options: std.StringArrayHashMapUnmanaged(SystemLibraryMode) = .{},
|
||||
system_package_mode: bool = false,
|
||||
debug_compiler_runtime_libs: bool = false,
|
||||
cache: Cache,
|
||||
zig_exe: [:0]const u8,
|
||||
env_map: EnvMap,
|
||||
@ -977,6 +979,7 @@ pub fn addRunArtifact(b: *Build, exe: *Step.Compile) *Step.Run {
|
||||
// Consider that this is declarative; the run step may not be run unless a user
|
||||
// option is supplied.
|
||||
const run_step = Step.Run.create(b, b.fmt("run {s}", .{exe.name}));
|
||||
run_step.producer = exe;
|
||||
if (exe.kind == .@"test") {
|
||||
if (exe.exec_cmd_args) |exec_cmd_args| {
|
||||
for (exec_cmd_args) |cmd_arg| {
|
||||
|
114
lib/std/Build/Fuzz.zig
Normal file
114
lib/std/Build/Fuzz.zig
Normal file
@ -0,0 +1,114 @@
|
||||
const std = @import("../std.zig");
|
||||
const Fuzz = @This();
|
||||
const Step = std.Build.Step;
|
||||
const assert = std.debug.assert;
|
||||
const fatal = std.process.fatal;
|
||||
const build_runner = @import("root");
|
||||
|
||||
pub fn start(
|
||||
thread_pool: *std.Thread.Pool,
|
||||
all_steps: []const *Step,
|
||||
ttyconf: std.io.tty.Config,
|
||||
prog_node: std.Progress.Node,
|
||||
) void {
|
||||
const count = block: {
|
||||
const rebuild_node = prog_node.start("Rebuilding Unit Tests", 0);
|
||||
defer rebuild_node.end();
|
||||
var count: usize = 0;
|
||||
var wait_group: std.Thread.WaitGroup = .{};
|
||||
defer wait_group.wait();
|
||||
for (all_steps) |step| {
|
||||
const run = step.cast(Step.Run) orelse continue;
|
||||
if (run.fuzz_tests.items.len > 0 and run.producer != null) {
|
||||
thread_pool.spawnWg(&wait_group, rebuildTestsWorkerRun, .{ run, ttyconf, rebuild_node });
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
if (count == 0) fatal("no fuzz tests found", .{});
|
||||
rebuild_node.setEstimatedTotalItems(count);
|
||||
break :block count;
|
||||
};
|
||||
|
||||
// Detect failure.
|
||||
for (all_steps) |step| {
|
||||
const run = step.cast(Step.Run) orelse continue;
|
||||
if (run.fuzz_tests.items.len > 0 and run.rebuilt_executable == null)
|
||||
fatal("one or more unit tests failed to be rebuilt in fuzz mode", .{});
|
||||
}
|
||||
|
||||
{
|
||||
const fuzz_node = prog_node.start("Fuzzing", count);
|
||||
defer fuzz_node.end();
|
||||
var wait_group: std.Thread.WaitGroup = .{};
|
||||
defer wait_group.wait();
|
||||
|
||||
for (all_steps) |step| {
|
||||
const run = step.cast(Step.Run) orelse continue;
|
||||
for (run.fuzz_tests.items) |unit_test_index| {
|
||||
assert(run.rebuilt_executable != null);
|
||||
thread_pool.spawnWg(&wait_group, fuzzWorkerRun, .{ run, unit_test_index, ttyconf, fuzz_node });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fatal("all fuzz workers crashed", .{});
|
||||
}
|
||||
|
||||
fn rebuildTestsWorkerRun(run: *Step.Run, ttyconf: std.io.tty.Config, parent_prog_node: std.Progress.Node) void {
|
||||
const gpa = run.step.owner.allocator;
|
||||
const stderr = std.io.getStdErr();
|
||||
|
||||
const compile = run.producer.?;
|
||||
const prog_node = parent_prog_node.start(compile.step.name, 0);
|
||||
defer prog_node.end();
|
||||
|
||||
const result = compile.rebuildInFuzzMode(prog_node);
|
||||
|
||||
const show_compile_errors = compile.step.result_error_bundle.errorMessageCount() > 0;
|
||||
const show_error_msgs = compile.step.result_error_msgs.items.len > 0;
|
||||
const show_stderr = compile.step.result_stderr.len > 0;
|
||||
|
||||
if (show_error_msgs or show_compile_errors or show_stderr) {
|
||||
std.debug.lockStdErr();
|
||||
defer std.debug.unlockStdErr();
|
||||
build_runner.printErrorMessages(gpa, &compile.step, ttyconf, stderr, false) catch {};
|
||||
}
|
||||
|
||||
if (result) |rebuilt_bin_path| {
|
||||
run.rebuilt_executable = rebuilt_bin_path;
|
||||
} else |err| switch (err) {
|
||||
error.MakeFailed => {},
|
||||
else => {
|
||||
std.debug.print("step '{s}': failed to rebuild in fuzz mode: {s}\n", .{
|
||||
compile.step.name, @errorName(err),
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn fuzzWorkerRun(
|
||||
run: *Step.Run,
|
||||
unit_test_index: u32,
|
||||
ttyconf: std.io.tty.Config,
|
||||
parent_prog_node: std.Progress.Node,
|
||||
) void {
|
||||
const gpa = run.step.owner.allocator;
|
||||
const test_name = run.cached_test_metadata.?.testName(unit_test_index);
|
||||
|
||||
const prog_node = parent_prog_node.start(test_name, 0);
|
||||
defer prog_node.end();
|
||||
|
||||
run.rerunInFuzzMode(unit_test_index, prog_node) catch |err| switch (err) {
|
||||
error.MakeFailed => {
|
||||
const stderr = std.io.getStdErr();
|
||||
std.debug.lockStdErr();
|
||||
defer std.debug.unlockStdErr();
|
||||
build_runner.printErrorMessages(gpa, &run.step, ttyconf, stderr, false) catch {};
|
||||
},
|
||||
else => {
|
||||
std.debug.print("step '{s}': failed to rebuild '{s}' in fuzz mode: {s}\n", .{
|
||||
run.step.name, test_name, @errorName(err),
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
@ -1004,7 +1004,7 @@ fn getGeneratedFilePath(compile: *Compile, comptime tag_name: []const u8, asking
|
||||
return path;
|
||||
}
|
||||
|
||||
fn getZigArgs(compile: *Compile) ![][]const u8 {
|
||||
fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 {
|
||||
const step = &compile.step;
|
||||
const b = step.owner;
|
||||
const arena = b.allocator;
|
||||
@ -1055,6 +1055,10 @@ fn getZigArgs(compile: *Compile) ![][]const u8 {
|
||||
try zig_args.append(try std.fmt.allocPrint(arena, "{}", .{stack_size}));
|
||||
}
|
||||
|
||||
if (fuzz) {
|
||||
try zig_args.append("-ffuzz");
|
||||
}
|
||||
|
||||
{
|
||||
// Stores system libraries that have already been seen for at least one
|
||||
// module, along with any arguments that need to be passed to the
|
||||
@ -1479,6 +1483,8 @@ fn getZigArgs(compile: *Compile) ![][]const u8 {
|
||||
try zig_args.append("--global-cache-dir");
|
||||
try zig_args.append(b.graph.global_cache_root.path orelse ".");
|
||||
|
||||
if (b.graph.debug_compiler_runtime_libs) try zig_args.append("--debug-rt");
|
||||
|
||||
try zig_args.append("--name");
|
||||
try zig_args.append(compile.name);
|
||||
|
||||
@ -1757,7 +1763,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
|
||||
const b = step.owner;
|
||||
const compile: *Compile = @fieldParentPtr("step", step);
|
||||
|
||||
const zig_args = try getZigArgs(compile);
|
||||
const zig_args = try getZigArgs(compile, false);
|
||||
|
||||
const maybe_output_bin_path = step.evalZigProcess(
|
||||
zig_args,
|
||||
@ -1835,6 +1841,20 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rebuildInFuzzMode(c: *Compile, progress_node: std.Progress.Node) ![]const u8 {
|
||||
const gpa = c.step.owner.allocator;
|
||||
|
||||
c.step.result_error_msgs.clearRetainingCapacity();
|
||||
c.step.result_stderr = "";
|
||||
|
||||
c.step.result_error_bundle.deinit(gpa);
|
||||
c.step.result_error_bundle = std.zig.ErrorBundle.empty;
|
||||
|
||||
const zig_args = try getZigArgs(c, true);
|
||||
const maybe_output_bin_path = try c.step.evalZigProcess(zig_args, progress_node, false);
|
||||
return maybe_output_bin_path.?;
|
||||
}
|
||||
|
||||
pub fn doAtomicSymLinks(
|
||||
step: *Step,
|
||||
output_path: []const u8,
|
||||
@ -1861,10 +1881,10 @@ pub fn doAtomicSymLinks(
|
||||
};
|
||||
}
|
||||
|
||||
fn execPkgConfigList(compile: *std.Build, out_code: *u8) (PkgConfigError || RunError)![]const PkgConfigPkg {
|
||||
const pkg_config_exe = compile.graph.env_map.get("PKG_CONFIG") orelse "pkg-config";
|
||||
const stdout = try compile.runAllowFail(&[_][]const u8{ pkg_config_exe, "--list-all" }, out_code, .Ignore);
|
||||
var list = ArrayList(PkgConfigPkg).init(compile.allocator);
|
||||
fn execPkgConfigList(b: *std.Build, out_code: *u8) (PkgConfigError || RunError)![]const PkgConfigPkg {
|
||||
const pkg_config_exe = b.graph.env_map.get("PKG_CONFIG") orelse "pkg-config";
|
||||
const stdout = try b.runAllowFail(&[_][]const u8{ pkg_config_exe, "--list-all" }, out_code, .Ignore);
|
||||
var list = ArrayList(PkgConfigPkg).init(b.allocator);
|
||||
errdefer list.deinit();
|
||||
var line_it = mem.tokenizeAny(u8, stdout, "\r\n");
|
||||
while (line_it.next()) |line| {
|
||||
@ -1878,13 +1898,13 @@ fn execPkgConfigList(compile: *std.Build, out_code: *u8) (PkgConfigError || RunE
|
||||
return list.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn getPkgConfigList(compile: *std.Build) ![]const PkgConfigPkg {
|
||||
if (compile.pkg_config_pkg_list) |res| {
|
||||
fn getPkgConfigList(b: *std.Build) ![]const PkgConfigPkg {
|
||||
if (b.pkg_config_pkg_list) |res| {
|
||||
return res;
|
||||
}
|
||||
var code: u8 = undefined;
|
||||
if (execPkgConfigList(compile, &code)) |list| {
|
||||
compile.pkg_config_pkg_list = list;
|
||||
if (execPkgConfigList(b, &code)) |list| {
|
||||
b.pkg_config_pkg_list = list;
|
||||
return list;
|
||||
} else |err| {
|
||||
const result = switch (err) {
|
||||
@ -1896,7 +1916,7 @@ fn getPkgConfigList(compile: *std.Build) ![]const PkgConfigPkg {
|
||||
error.PkgConfigInvalidOutput => error.PkgConfigInvalidOutput,
|
||||
else => return err,
|
||||
};
|
||||
compile.pkg_config_pkg_list = result;
|
||||
b.pkg_config_pkg_list = result;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -86,6 +86,18 @@ dep_output_file: ?*Output,
|
||||
|
||||
has_side_effects: bool,
|
||||
|
||||
/// If this is a Zig unit test binary, this tracks the indexes of the unit
|
||||
/// tests that are also fuzz tests.
|
||||
fuzz_tests: std.ArrayListUnmanaged(u32),
|
||||
cached_test_metadata: ?CachedTestMetadata = null,
|
||||
|
||||
/// Populated during the fuzz phase if this run step corresponds to a unit test
|
||||
/// executable that contains fuzz tests.
|
||||
rebuilt_executable: ?[]const u8,
|
||||
|
||||
/// If this Run step was produced by a Compile step, it is tracked here.
|
||||
producer: ?*Step.Compile,
|
||||
|
||||
pub const StdIn = union(enum) {
|
||||
none,
|
||||
bytes: []const u8,
|
||||
@ -175,6 +187,9 @@ pub fn create(owner: *std.Build, name: []const u8) *Run {
|
||||
.captured_stderr = null,
|
||||
.dep_output_file = null,
|
||||
.has_side_effects = false,
|
||||
.fuzz_tests = .{},
|
||||
.rebuilt_executable = null,
|
||||
.producer = null,
|
||||
};
|
||||
return run;
|
||||
}
|
||||
@ -741,7 +756,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
|
||||
b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path });
|
||||
}
|
||||
|
||||
try runCommand(run, argv_list.items, has_side_effects, output_dir_path, prog_node);
|
||||
try runCommand(run, argv_list.items, has_side_effects, output_dir_path, prog_node, null);
|
||||
if (!has_side_effects) try step.writeManifestAndWatch(&man);
|
||||
return;
|
||||
};
|
||||
@ -771,7 +786,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
|
||||
b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path });
|
||||
}
|
||||
|
||||
try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, prog_node);
|
||||
try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, prog_node, null);
|
||||
|
||||
const dep_file_dir = std.fs.cwd();
|
||||
const dep_file_basename = dep_output_file.generated_file.getPath();
|
||||
@ -830,6 +845,41 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn rerunInFuzzMode(run: *Run, unit_test_index: u32, prog_node: std.Progress.Node) !void {
|
||||
const step = &run.step;
|
||||
const b = step.owner;
|
||||
const arena = b.allocator;
|
||||
var argv_list: std.ArrayListUnmanaged([]const u8) = .{};
|
||||
for (run.argv.items) |arg| {
|
||||
switch (arg) {
|
||||
.bytes => |bytes| {
|
||||
try argv_list.append(arena, bytes);
|
||||
},
|
||||
.lazy_path => |file| {
|
||||
const file_path = file.lazy_path.getPath2(b, step);
|
||||
try argv_list.append(arena, b.fmt("{s}{s}", .{ file.prefix, file_path }));
|
||||
},
|
||||
.directory_source => |file| {
|
||||
const file_path = file.lazy_path.getPath2(b, step);
|
||||
try argv_list.append(arena, b.fmt("{s}{s}", .{ file.prefix, file_path }));
|
||||
},
|
||||
.artifact => |pa| {
|
||||
const artifact = pa.artifact;
|
||||
const file_path = if (artifact == run.producer.?)
|
||||
run.rebuilt_executable.?
|
||||
else
|
||||
(artifact.installed_path orelse artifact.generated_bin.?.path.?);
|
||||
try argv_list.append(arena, b.fmt("{s}{s}", .{ pa.prefix, file_path }));
|
||||
},
|
||||
.output_file, .output_directory => unreachable,
|
||||
}
|
||||
}
|
||||
const has_side_effects = false;
|
||||
const rand_int = std.crypto.random.int(u64);
|
||||
const tmp_dir_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int);
|
||||
try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, prog_node, unit_test_index);
|
||||
}
|
||||
|
||||
fn populateGeneratedPaths(
|
||||
arena: std.mem.Allocator,
|
||||
output_placeholders: []const IndexedOutput,
|
||||
@ -908,6 +958,7 @@ fn runCommand(
|
||||
has_side_effects: bool,
|
||||
output_dir_path: []const u8,
|
||||
prog_node: std.Progress.Node,
|
||||
fuzz_unit_test_index: ?u32,
|
||||
) !void {
|
||||
const step = &run.step;
|
||||
const b = step.owner;
|
||||
@ -926,7 +977,7 @@ fn runCommand(
|
||||
var interp_argv = std.ArrayList([]const u8).init(b.allocator);
|
||||
defer interp_argv.deinit();
|
||||
|
||||
const result = spawnChildAndCollect(run, argv, has_side_effects, prog_node) catch |err| term: {
|
||||
const result = spawnChildAndCollect(run, argv, has_side_effects, prog_node, fuzz_unit_test_index) catch |err| term: {
|
||||
// InvalidExe: cpu arch mismatch
|
||||
// FileNotFound: can happen with a wrong dynamic linker path
|
||||
if (err == error.InvalidExe or err == error.FileNotFound) interpret: {
|
||||
@ -1062,7 +1113,7 @@ fn runCommand(
|
||||
|
||||
try Step.handleVerbose2(step.owner, cwd, run.env_map, interp_argv.items);
|
||||
|
||||
break :term spawnChildAndCollect(run, interp_argv.items, has_side_effects, prog_node) catch |e| {
|
||||
break :term spawnChildAndCollect(run, interp_argv.items, has_side_effects, prog_node, fuzz_unit_test_index) catch |e| {
|
||||
if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped;
|
||||
|
||||
return step.fail("unable to spawn interpreter {s}: {s}", .{
|
||||
@ -1077,6 +1128,15 @@ fn runCommand(
|
||||
step.result_duration_ns = result.elapsed_ns;
|
||||
step.result_peak_rss = result.peak_rss;
|
||||
step.test_results = result.stdio.test_results;
|
||||
if (result.stdio.test_metadata) |tm|
|
||||
run.cached_test_metadata = tm.toCachedTestMetadata();
|
||||
|
||||
const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items;
|
||||
|
||||
if (fuzz_unit_test_index != null) {
|
||||
try step.handleChildProcessTerm(result.term, cwd, final_argv);
|
||||
return;
|
||||
}
|
||||
|
||||
// Capture stdout and stderr to GeneratedFile objects.
|
||||
const Stream = struct {
|
||||
@ -1113,8 +1173,6 @@ fn runCommand(
|
||||
}
|
||||
}
|
||||
|
||||
const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items;
|
||||
|
||||
switch (run.stdio) {
|
||||
.check => |checks| for (checks.items) |check| switch (check) {
|
||||
.expect_stderr_exact => |expected_bytes| {
|
||||
@ -1240,10 +1298,16 @@ fn spawnChildAndCollect(
|
||||
argv: []const []const u8,
|
||||
has_side_effects: bool,
|
||||
prog_node: std.Progress.Node,
|
||||
fuzz_unit_test_index: ?u32,
|
||||
) !ChildProcResult {
|
||||
const b = run.step.owner;
|
||||
const arena = b.allocator;
|
||||
|
||||
if (fuzz_unit_test_index != null) {
|
||||
assert(!has_side_effects);
|
||||
assert(run.stdio == .zig_test);
|
||||
}
|
||||
|
||||
var child = std.process.Child.init(argv, arena);
|
||||
if (run.cwd) |lazy_cwd| {
|
||||
child.cwd = lazy_cwd.getPath2(b, &run.step);
|
||||
@ -1293,7 +1357,7 @@ fn spawnChildAndCollect(
|
||||
var timer = try std.time.Timer.start();
|
||||
|
||||
const result = if (run.stdio == .zig_test)
|
||||
evalZigTest(run, &child, prog_node)
|
||||
evalZigTest(run, &child, prog_node, fuzz_unit_test_index)
|
||||
else
|
||||
evalGeneric(run, &child);
|
||||
|
||||
@ -1319,6 +1383,7 @@ fn evalZigTest(
|
||||
run: *Run,
|
||||
child: *std.process.Child,
|
||||
prog_node: std.Progress.Node,
|
||||
fuzz_unit_test_index: ?u32,
|
||||
) !StdIoResult {
|
||||
const gpa = run.step.owner.allocator;
|
||||
const arena = run.step.owner.allocator;
|
||||
@ -1329,7 +1394,12 @@ fn evalZigTest(
|
||||
});
|
||||
defer poller.deinit();
|
||||
|
||||
try sendMessage(child.stdin.?, .query_test_metadata);
|
||||
if (fuzz_unit_test_index) |index| {
|
||||
try sendRunTestMessage(child.stdin.?, .start_fuzzing, index);
|
||||
} else {
|
||||
run.fuzz_tests.clearRetainingCapacity();
|
||||
try sendMessage(child.stdin.?, .query_test_metadata);
|
||||
}
|
||||
|
||||
const Header = std.zig.Server.Message.Header;
|
||||
|
||||
@ -1367,6 +1437,7 @@ fn evalZigTest(
|
||||
}
|
||||
},
|
||||
.test_metadata => {
|
||||
assert(fuzz_unit_test_index == null);
|
||||
const TmHdr = std.zig.Server.Message.TestMetadata;
|
||||
const tm_hdr = @as(*align(1) const TmHdr, @ptrCast(body));
|
||||
test_count = tm_hdr.tests_len;
|
||||
@ -1395,6 +1466,7 @@ fn evalZigTest(
|
||||
try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node);
|
||||
},
|
||||
.test_results => {
|
||||
assert(fuzz_unit_test_index == null);
|
||||
const md = metadata.?;
|
||||
|
||||
const TrHdr = std.zig.Server.Message.TestResults;
|
||||
@ -1404,6 +1476,8 @@ fn evalZigTest(
|
||||
leak_count +|= @intFromBool(tr_hdr.flags.leak);
|
||||
log_err_count +|= tr_hdr.flags.log_err_count;
|
||||
|
||||
if (tr_hdr.flags.fuzz) try run.fuzz_tests.append(gpa, tr_hdr.index);
|
||||
|
||||
if (tr_hdr.flags.fail or tr_hdr.flags.leak or tr_hdr.flags.log_err_count > 0) {
|
||||
const name = std.mem.sliceTo(md.string_bytes[md.names[tr_hdr.index]..], 0);
|
||||
const orig_msg = stderr.readableSlice(0);
|
||||
@ -1462,7 +1536,23 @@ const TestMetadata = struct {
|
||||
next_index: u32,
|
||||
prog_node: std.Progress.Node,
|
||||
|
||||
fn toCachedTestMetadata(tm: TestMetadata) CachedTestMetadata {
|
||||
return .{
|
||||
.names = tm.names,
|
||||
.string_bytes = tm.string_bytes,
|
||||
};
|
||||
}
|
||||
|
||||
fn testName(tm: TestMetadata, index: u32) []const u8 {
|
||||
return tm.toCachedTestMetadata().testName(index);
|
||||
}
|
||||
};
|
||||
|
||||
pub const CachedTestMetadata = struct {
|
||||
names: []const u32,
|
||||
string_bytes: []const u8,
|
||||
|
||||
pub fn testName(tm: CachedTestMetadata, index: u32) []const u8 {
|
||||
return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0);
|
||||
}
|
||||
};
|
||||
@ -1478,7 +1568,7 @@ fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Pr
|
||||
if (sub_prog_node.*) |n| n.end();
|
||||
sub_prog_node.* = metadata.prog_node.start(name, 0);
|
||||
|
||||
try sendRunTestMessage(in, i);
|
||||
try sendRunTestMessage(in, .run_test, i);
|
||||
return;
|
||||
} else {
|
||||
try sendMessage(in, .exit);
|
||||
@ -1493,9 +1583,9 @@ fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
|
||||
try file.writeAll(std.mem.asBytes(&header));
|
||||
}
|
||||
|
||||
fn sendRunTestMessage(file: std.fs.File, index: u32) !void {
|
||||
fn sendRunTestMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag, index: u32) !void {
|
||||
const header: std.zig.Client.Message.Header = .{
|
||||
.tag = .run_test,
|
||||
.tag = tag,
|
||||
.bytes_len = 4,
|
||||
};
|
||||
const full_msg = std.mem.asBytes(&header) ++ std.mem.asBytes(&index);
|
||||
|
@ -16,7 +16,7 @@ test "write a file, read it, then delete it" {
|
||||
defer tmp.cleanup();
|
||||
|
||||
var data: [1024]u8 = undefined;
|
||||
var prng = DefaultPrng.init(1234);
|
||||
var prng = DefaultPrng.init(std.testing.random_seed);
|
||||
const random = prng.random();
|
||||
random.bytes(data[0..]);
|
||||
const tmp_file_name = "temp_test_file.txt";
|
||||
|
@ -1136,3 +1136,11 @@ pub fn refAllDeclsRecursive(comptime T: type) void {
|
||||
_ = &@field(T, decl.name);
|
||||
}
|
||||
}
|
||||
|
||||
pub const FuzzInputOptions = struct {
|
||||
corpus: []const []const u8 = &.{},
|
||||
};
|
||||
|
||||
pub inline fn fuzzInput(options: FuzzInputOptions) []const u8 {
|
||||
return @import("root").fuzzInput(options);
|
||||
}
|
||||
|
@ -33,6 +33,9 @@ pub const Message = struct {
|
||||
/// Ask the test runner to run a particular test.
|
||||
/// The message body is a u32 test index.
|
||||
run_test,
|
||||
/// Ask the test runner to start fuzzing a particular test.
|
||||
/// The message body is a u32 test index.
|
||||
start_fuzzing,
|
||||
|
||||
_,
|
||||
};
|
||||
|
@ -53,7 +53,7 @@ pub const Message = struct {
|
||||
/// - null-terminated string_bytes index
|
||||
/// * expected_panic_msg: [tests_len]u32,
|
||||
/// - null-terminated string_bytes index
|
||||
/// - 0 means does not expect pani
|
||||
/// - 0 means does not expect panic
|
||||
/// * string_bytes: [string_bytes_len]u8,
|
||||
pub const TestMetadata = extern struct {
|
||||
string_bytes_len: u32,
|
||||
@ -68,7 +68,8 @@ pub const Message = struct {
|
||||
fail: bool,
|
||||
skip: bool,
|
||||
leak: bool,
|
||||
log_err_count: u29 = 0,
|
||||
fuzz: bool,
|
||||
log_err_count: u28 = 0,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -2180,7 +2180,9 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
|
||||
comp.bin_file = try link.File.createEmpty(arena, comp, emit, whole.lf_open_opts);
|
||||
}
|
||||
},
|
||||
.incremental => {},
|
||||
.incremental => {
|
||||
log.debug("Compilation.update for {s}, CacheMode.incremental", .{comp.root_name});
|
||||
},
|
||||
}
|
||||
|
||||
// From this point we add a preliminary set of file system inputs that
|
||||
|
@ -1392,16 +1392,12 @@ pub const Object = struct {
|
||||
}
|
||||
if (owner_mod.fuzz and !func_analysis.disable_instrumentation) {
|
||||
try attributes.addFnAttr(.optforfuzzing, &o.builder);
|
||||
if (comp.config.any_fuzz) {
|
||||
_ = try attributes.removeFnAttr(.skipprofile);
|
||||
_ = try attributes.removeFnAttr(.nosanitize_coverage);
|
||||
}
|
||||
_ = try attributes.removeFnAttr(.skipprofile);
|
||||
_ = try attributes.removeFnAttr(.nosanitize_coverage);
|
||||
} else {
|
||||
_ = try attributes.removeFnAttr(.optforfuzzing);
|
||||
if (comp.config.any_fuzz) {
|
||||
try attributes.addFnAttr(.skipprofile, &o.builder);
|
||||
try attributes.addFnAttr(.nosanitize_coverage, &o.builder);
|
||||
}
|
||||
try attributes.addFnAttr(.skipprofile, &o.builder);
|
||||
try attributes.addFnAttr(.nosanitize_coverage, &o.builder);
|
||||
}
|
||||
|
||||
// TODO: disable this if safety is off for the function scope
|
||||
|
@ -2286,6 +2286,8 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
|
||||
}
|
||||
try man.addOptionalFile(module_obj_path);
|
||||
try man.addOptionalFile(compiler_rt_path);
|
||||
try man.addOptionalFile(if (comp.tsan_lib) |l| l.full_object_path else null);
|
||||
try man.addOptionalFile(if (comp.fuzzer_lib) |l| l.full_object_path else null);
|
||||
|
||||
// We can skip hashing libc and libc++ components that we are in charge of building from Zig
|
||||
// installation sources because they are always a product of the compiler version + target information.
|
||||
|
@ -655,6 +655,7 @@ const usage_build_generic =
|
||||
\\ --debug-log [scope] Enable printing debug/info log messages for scope
|
||||
\\ --debug-compile-errors Crash with helpful diagnostics at the first compile error
|
||||
\\ --debug-link-snapshot Enable dumping of the linker's state in JSON format
|
||||
\\ --debug-rt Debug compiler runtime libraries
|
||||
\\
|
||||
;
|
||||
|
||||
@ -912,6 +913,7 @@ fn buildOutputType(
|
||||
var minor_subsystem_version: ?u16 = null;
|
||||
var mingw_unicode_entry_point: bool = false;
|
||||
var enable_link_snapshots: bool = false;
|
||||
var debug_compiler_runtime_libs = false;
|
||||
var opt_incremental: ?bool = null;
|
||||
var install_name: ?[]const u8 = null;
|
||||
var hash_style: link.File.Elf.HashStyle = .both;
|
||||
@ -1367,6 +1369,8 @@ fn buildOutputType(
|
||||
} else {
|
||||
enable_link_snapshots = true;
|
||||
}
|
||||
} else if (mem.eql(u8, arg, "--debug-rt")) {
|
||||
debug_compiler_runtime_libs = true;
|
||||
} else if (mem.eql(u8, arg, "-fincremental")) {
|
||||
dev.check(.incremental);
|
||||
opt_incremental = true;
|
||||
@ -3408,6 +3412,7 @@ fn buildOutputType(
|
||||
// noise when --search-prefix and --mod are combined.
|
||||
.global_cc_argv = try cc_argv.toOwnedSlice(arena),
|
||||
.file_system_inputs = &file_system_inputs,
|
||||
.debug_compiler_runtime_libs = debug_compiler_runtime_libs,
|
||||
}) catch |err| switch (err) {
|
||||
error.LibCUnavailable => {
|
||||
const triple_name = try target.zigTriple(arena);
|
||||
|
Loading…
Reference in New Issue
Block a user