Commit 6f3767862d

Andrew Kelley <andrew@ziglang.org>
2024-07-23 03:06:58
implement std.testing.fuzzInput
For now this returns a dummy fuzz input.
1 parent 3256df2
Changed files (4)
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)];
+        }
+    }
+}
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,
         };
     };
 
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);
 }
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"),
+    });
 }