From 6f3767862d6886d5fde7e3734455a30f168ba80b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Jul 2024 18:06:58 -0700 Subject: [PATCH] implement std.testing.fuzzInput For now this returns a dummy fuzz input. --- lib/compiler/test_runner.zig | 91 +++++++++++++++++++++++++++--------- lib/fuzzer.zig | 56 ++++++++++++++++++++-- lib/std/testing.zig | 26 +---------- lib/std/zig/Server.zig | 5 +- 4 files changed, 125 insertions(+), 53 deletions(-) diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index 478728455b..15905ca7f9 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -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, @@ -118,7 +130,7 @@ fn mainServer() !void { }, 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,6 +138,7 @@ fn mainServer() !void { } fn mainTerminal() void { + @disableInstrumentation(); const test_fn_list = builtin.test_functions; var ok_count: usize = 0; var skip_count: usize = 0; @@ -143,18 +156,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 }); } + // Track in a global variable so that `fuzzInput` can see it. if (test_fn.func()) |_| { ok_count += 1; test_node.end(); @@ -208,10 +222,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 +237,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 +282,34 @@ 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 ""; + } else if (builtin.fuzz) { + return fuzzer_next().toSlice(); + } else { + is_fuzz_test = true; + if (options.corpus.len == 0) { + return ""; + } else { + var prng = std.Random.DefaultPrng.init(testing.random_seed); + const random = prng.random(); + return options.corpus[random.uintLessThan(usize, options.corpus.len)]; + } + } +} diff --git a/lib/fuzzer.zig b/lib/fuzzer.zig index 49dd33894b..8329d1eb2e 100644 --- a/lib/fuzzer.zig +++ b/lib/fuzzer.zig @@ -1,13 +1,14 @@ const std = @import("std"); +const Allocator = std.mem.Allocator; 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 }); + std.log.debug("__sanitizer_cov_pcs_init pcs_beg={*}, pcs_end={*}", .{ pcs_beg, pcs_end }); } export fn __sanitizer_cov_trace_const_cmp1(arg1: u8, arg2: u8) void { @@ -47,16 +48,61 @@ 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", .{ + 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 }); + 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 }); + 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), + + const Slice = extern struct { + ptr: [*]const u8, + len: usize, + + fn toSlice(s: Slice) []const u8 { + return s.ptr[0..s.len]; + } + + fn fromSlice(s: []const u8) Slice { + return .{ + .ptr = s.ptr, + .len = s.len, + }; + } + }; + + fn next(f: *Fuzzer) ![]const u8 { + const gpa = f.gpa; + const rng = fuzzer.rng.random(); + const len = rng.uintLessThan(usize, 64); + try f.input.resize(gpa, len); + rng.bytes(f.input.items); + return f.input.items; + } +}; + +var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .{}; + +var fuzzer: Fuzzer = .{ + .gpa = general_purpose_allocator.allocator(), + .rng = std.Random.DefaultPrng.init(0), + .input = .{}, +}; + +export fn fuzzer_next() Fuzzer.Slice { + return Fuzzer.Slice.fromSlice(fuzzer.next() catch |err| switch (err) { + error.OutOfMemory => @panic("out of memory"), + }); } diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 35f3c5a6dd..80e8ab13bb 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -1137,32 +1137,10 @@ pub fn refAllDeclsRecursive(comptime T: type) void { } } -const FuzzerSlice = extern struct { - ptr: [*]const u8, - len: usize, - - fn toSlice(s: FuzzerSlice) []const u8 { - return s.ptr[0..s.len]; - } -}; - -extern fn fuzzer_next() FuzzerSlice; - pub const FuzzInputOptions = struct { corpus: []const []const u8 = &.{}, }; -pub fn fuzzInput(options: FuzzInputOptions) []const u8 { - @disableInstrumentation(); - if (builtin.fuzz) { - return fuzzer_next().toSlice(); - } else { - if (options.corpus.len == 0) { - return ""; - } else { - var prng = std.Random.DefaultPrng.init(std.testing.random_seed); - const random = prng.random(); - return options.corpus[random.uintLessThan(usize, options.corpus.len)]; - } - } +pub inline fn fuzzInput(options: FuzzInputOptions) []const u8 { + return @import("root").fuzzInput(options); } diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig index 4046fe4014..f1e564d43e 100644 --- a/lib/std/zig/Server.zig +++ b/lib/std/zig/Server.zig @@ -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, }; };