diff --git a/lib/compiler/test_runner.zig b/lib/compiler/test_runner.zig index ac9629a57d..83d53626c3 100644 --- a/lib/compiler/test_runner.zig +++ b/lib/compiler/test_runner.zig @@ -145,31 +145,23 @@ fn mainServer() !void { .start_fuzzing => { if (!builtin.fuzz) unreachable; const index = try server.receiveBody_u32(); - var first = true; 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"); - if (first) { - first = false; - const entry_addr = @intFromPtr(test_fn.func); - try server.serveU64Message(.fuzz_start_addr, entry_addr); - } - } + const entry_addr = @intFromPtr(test_fn.func); + try server.serveU64Message(.fuzz_start_addr, entry_addr); + defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1); + is_fuzz_test = false; + test_fn.func() catch |err| switch (err) { + error.SkipZigTest => return, + 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.fuzz"); + if (log_err_count != 0) @panic("error logs detected"); }, else => { @@ -349,19 +341,72 @@ const FuzzerSlice = extern struct { var is_fuzz_test: bool = undefined; -extern fn fuzzer_next() FuzzerSlice; +extern fn fuzzer_start(testOne: *const fn ([*]const u8, usize) callconv(.C) void) void; extern fn fuzzer_init(cache_dir: FuzzerSlice) void; extern fn fuzzer_coverage_id() u64; -pub fn fuzzInput(options: testing.FuzzInputOptions) []const u8 { +pub fn fuzz( + comptime testOne: fn ([]const u8) anyerror!void, + options: testing.FuzzInputOptions, +) anyerror!void { + // Prevent this function from confusing the fuzzer by omitting its own code + // coverage from being considered. @disableInstrumentation(); - if (crippled) return ""; + + // Some compiler backends are not capable of handling fuzz testing yet but + // we still want CI test coverage enabled. + if (crippled) return; + + // Smoke test to ensure the test did not use conditional compilation to + // contradict itself by making it not actually be a fuzz test when the test + // is built in fuzz mode. is_fuzz_test = true; + + // Ensure no test failure occurred before starting fuzzing. + if (log_err_count != 0) @panic("error logs detected"); + + // libfuzzer is in a separate compilation unit so that its own code can be + // excluded from code coverage instrumentation. It needs a function pointer + // it can call for checking exactly one input. Inside this function we do + // our standard unit test checks such as memory leaks, and interaction with + // error logs. + const global = struct { + fn fuzzer_one(input_ptr: [*]const u8, input_len: usize) callconv(.C) void { + @disableInstrumentation(); + testing.allocator_instance = .{}; + defer if (testing.allocator_instance.deinit() == .leak) std.process.exit(1); + log_err_count = 0; + testOne(input_ptr[0..input_len]) catch |err| switch (err) { + error.SkipZigTest => return, + else => { + std.debug.lockStdErr(); + if (@errorReturnTrace()) |trace| std.debug.dumpStackTrace(trace.*); + std.debug.print("failed with error.{s}\n", .{@errorName(err)}); + std.process.exit(1); + }, + }; + if (log_err_count != 0) { + std.debug.lockStdErr(); + std.debug.print("error logs detected\n", .{}); + std.process.exit(1); + } + } + }; if (builtin.fuzz) { - return fuzzer_next().toSlice(); + const prev_allocator_state = testing.allocator_instance; + testing.allocator_instance = .{}; + fuzzer_start(&global.fuzzer_one); + testing.allocator_instance = prev_allocator_state; + return; } - 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)]; + + // When the unit test executable is not built in fuzz mode, only run the + // provided corpus. + for (options.corpus) |input| { + try testOne(input); + } + + // In case there is no provided corpus, also use an empty + // string as a smoke test. + try testOne(""); } diff --git a/lib/fuzzer.zig b/lib/fuzzer.zig index 9c67756a6d..3f8a991148 100644 --- a/lib/fuzzer.zig +++ b/lib/fuzzer.zig @@ -28,7 +28,8 @@ fn logOverride( f.writer().print(prefix1 ++ prefix2 ++ format ++ "\n", args) catch @panic("failed to write to fuzzer log"); } -export threadlocal var __sancov_lowest_stack: usize = std.math.maxInt(usize); +/// Helps determine run uniqueness in the face of recursion. +export threadlocal var __sancov_lowest_stack: usize = 0; export fn __sanitizer_cov_trace_const_cmp1(arg1: u8, arg2: u8) void { handleCmp(@returnAddress(), arg1, arg2); @@ -220,7 +221,6 @@ const Fuzzer = struct { .n_runs = 0, .unique_runs = 0, .pcs_len = pcs.len, - .lowest_stack = std.math.maxInt(usize), }; f.seen_pcs.appendSliceAssumeCapacity(std.mem.asBytes(&header)); f.seen_pcs.appendNTimesAssumeCapacity(0, n_bitset_elems * @sizeOf(usize)); @@ -235,22 +235,41 @@ const Fuzzer = struct { }; } - fn next(f: *Fuzzer) ![]const u8 { + fn start(f: *Fuzzer) !void { 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 { + // Prepare initial input. + assert(f.recent_cases.entries.len == 0); + assert(f.n_runs == 0); + 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, + }, {}); + + const header: *volatile SeenPcsHeader = @ptrCast(f.seen_pcs.items[0..@sizeOf(SeenPcsHeader)]); + + while (true) { + 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(); + + @memset(f.pc_counters, 0); + __sancov_lowest_stack = std.math.maxInt(usize); + f.coverage.reset(); + + fuzzer_one(f.input.items.ptr, f.input.items.len); + + f.n_runs += 1; + _ = @atomicRmw(usize, &header.n_runs, .Add, 1, .monotonic); + if (f.n_runs % 10000 == 0) f.dumpStats(); const analysis = f.analyzeLastRun(); @@ -301,7 +320,6 @@ const Fuzzer = struct { } } - const header: *volatile SeenPcsHeader = @ptrCast(f.seen_pcs.items[0..@sizeOf(SeenPcsHeader)]); _ = @atomicRmw(usize, &header.unique_runs, .Add, 1, .monotonic); } @@ -317,26 +335,12 @@ const Fuzzer = struct { // 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); + for (doomed_runs) |*doomed_run| { + std.log.info("culling score={d} id={d}", .{ doomed_run.score, doomed_run.id }); + doomed_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.n_runs += 1; - const header: *volatile SeenPcsHeader = @ptrCast(f.seen_pcs.items[0..@sizeOf(SeenPcsHeader)]); - _ = @atomicRmw(usize, &header.n_runs, .Add, 1, .monotonic); - _ = @atomicRmw(usize, &header.lowest_stack, .Min, __sancov_lowest_stack, .monotonic); - @memset(f.pc_counters, 0); - f.coverage.reset(); - return f.input.items; } fn visitPc(f: *Fuzzer, pc: usize) void { @@ -419,10 +423,13 @@ export fn fuzzer_coverage_id() u64 { return fuzzer.coverage_id; } -export fn fuzzer_next() Fuzzer.Slice { - return Fuzzer.Slice.fromZig(fuzzer.next() catch |err| switch (err) { - error.OutOfMemory => @panic("out of memory"), - }); +var fuzzer_one: *const fn (input_ptr: [*]const u8, input_len: usize) callconv(.C) void = undefined; + +export fn fuzzer_start(testOne: @TypeOf(fuzzer_one)) void { + fuzzer_one = testOne; + fuzzer.start() catch |err| switch (err) { + error.OutOfMemory => fatal("out of memory", .{}), + }; } export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void { @@ -432,24 +439,24 @@ export fn fuzzer_init(cache_dir_struct: Fuzzer.Slice) void { const pc_counters_start = @extern([*]u8, .{ .name = "__start___sancov_cntrs", .linkage = .weak, - }) orelse fatal("missing __start___sancov_cntrs symbol"); + }) orelse fatal("missing __start___sancov_cntrs symbol", .{}); const pc_counters_end = @extern([*]u8, .{ .name = "__stop___sancov_cntrs", .linkage = .weak, - }) orelse fatal("missing __stop___sancov_cntrs symbol"); + }) orelse fatal("missing __stop___sancov_cntrs symbol", .{}); const pc_counters = pc_counters_start[0 .. pc_counters_end - pc_counters_start]; const pcs_start = @extern([*]usize, .{ .name = "__start___sancov_pcs1", .linkage = .weak, - }) orelse fatal("missing __start___sancov_pcs1 symbol"); + }) orelse fatal("missing __start___sancov_pcs1 symbol", .{}); const pcs_end = @extern([*]usize, .{ .name = "__stop___sancov_pcs1", .linkage = .weak, - }) orelse fatal("missing __stop___sancov_pcs1 symbol"); + }) orelse fatal("missing __stop___sancov_pcs1 symbol", .{}); const pcs = pcs_start[0 .. pcs_end - pcs_start]; diff --git a/lib/fuzzer/index.html b/lib/fuzzer/web/index.html similarity index 98% rename from lib/fuzzer/index.html rename to lib/fuzzer/web/index.html index 16fa879913..325342e8eb 100644 --- a/lib/fuzzer/index.html +++ b/lib/fuzzer/web/index.html @@ -146,8 +146,8 @@ diff --git a/lib/fuzzer/main.js b/lib/fuzzer/web/main.js similarity index 97% rename from lib/fuzzer/main.js rename to lib/fuzzer/web/main.js index ce02276f98..94f09391bb 100644 --- a/lib/fuzzer/main.js +++ b/lib/fuzzer/web/main.js @@ -5,8 +5,8 @@ const domSourceText = document.getElementById("sourceText"); const domStatTotalRuns = document.getElementById("statTotalRuns"); const domStatUniqueRuns = document.getElementById("statUniqueRuns"); + const domStatSpeed = document.getElementById("statSpeed"); const domStatCoverage = document.getElementById("statCoverage"); - const domStatLowestStack = document.getElementById("statLowestStack"); const domEntryPointsList = document.getElementById("entryPointsList"); let wasm_promise = fetch("main.wasm"); @@ -32,6 +32,9 @@ const msg = decodeString(ptr, len); throw new Error("panic: " + msg); }, + timestamp: function () { + return BigInt(new Date()); + }, emitSourceIndexChange: onSourceIndexChange, emitCoverageUpdate: onCoverageUpdate, emitEntryPointsUpdate: renderStats, @@ -158,7 +161,7 @@ domStatTotalRuns.innerText = totalRuns; domStatUniqueRuns.innerText = uniqueRuns + " (" + percent(uniqueRuns, totalRuns) + "%)"; domStatCoverage.innerText = coveredSourceLocations + " / " + totalSourceLocations + " (" + percent(coveredSourceLocations, totalSourceLocations) + "%)"; - domStatLowestStack.innerText = unwrapString(wasm_exports.lowestStack()); + domStatSpeed.innerText = wasm_exports.totalRunsPerSecond().toFixed(0); const entryPoints = unwrapInt32Array(wasm_exports.entryPoints()); resizeDomList(domEntryPointsList, entryPoints.length, "
  • "); diff --git a/lib/fuzzer/wasm/main.zig b/lib/fuzzer/web/main.zig similarity index 92% rename from lib/fuzzer/wasm/main.zig rename to lib/fuzzer/web/main.zig index 342adc3b56..9c50704e8a 100644 --- a/lib/fuzzer/wasm/main.zig +++ b/lib/fuzzer/web/main.zig @@ -10,9 +10,17 @@ const Walk = @import("Walk"); const Decl = Walk.Decl; const html_render = @import("html_render"); +/// Nanoseconds. +var server_base_timestamp: i64 = 0; +/// Milliseconds. +var client_base_timestamp: i64 = 0; +/// Relative to `server_base_timestamp`. +var start_fuzzing_timestamp: i64 = undefined; + const js = struct { extern "js" fn log(ptr: [*]const u8, len: usize) void; extern "js" fn panic(ptr: [*]const u8, len: usize) noreturn; + extern "js" fn timestamp() i64; extern "js" fn emitSourceIndexChange() void; extern "js" fn emitCoverageUpdate() void; extern "js" fn emitEntryPointsUpdate() void; @@ -64,6 +72,7 @@ export fn message_end() void { const tag: abi.ToClientTag = @enumFromInt(msg_bytes[0]); switch (tag) { + .current_time => return currentTimeMessage(msg_bytes), .source_index => return sourceIndexMessage(msg_bytes) catch @panic("OOM"), .coverage_update => return coverageUpdateMessage(msg_bytes) catch @panic("OOM"), .entry_points => return entryPointsMessage(msg_bytes) catch @panic("OOM"), @@ -106,13 +115,6 @@ export fn decl_source_html(decl_index: Decl.Index) String { return String.init(string_result.items); } -export fn lowestStack() String { - const header: *abi.CoverageUpdateHeader = @ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)]); - string_result.clearRetainingCapacity(); - string_result.writer(gpa).print("0x{d}", .{header.lowest_stack}) catch @panic("OOM"); - return String.init(string_result.items); -} - export fn totalSourceLocations() usize { return coverage_source_locations.items.len; } @@ -124,16 +126,28 @@ export fn coveredSourceLocations() usize { return count; } +fn getCoverageUpdateHeader() *abi.CoverageUpdateHeader { + return @alignCast(@ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)])); +} + export fn totalRuns() u64 { - const header: *abi.CoverageUpdateHeader = @alignCast(@ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)])); + const header = getCoverageUpdateHeader(); return header.n_runs; } export fn uniqueRuns() u64 { - const header: *abi.CoverageUpdateHeader = @alignCast(@ptrCast(recent_coverage_update.items[0..@sizeOf(abi.CoverageUpdateHeader)])); + const header = getCoverageUpdateHeader(); return header.unique_runs; } +export fn totalRunsPerSecond() f64 { + @setFloatMode(.optimized); + const header = getCoverageUpdateHeader(); + const ns_elapsed: f64 = @floatFromInt(nsSince(start_fuzzing_timestamp)); + const n_runs: f64 = @floatFromInt(header.n_runs); + return n_runs / (ns_elapsed / std.time.ns_per_s); +} + const String = Slice(u8); fn Slice(T: type) type { @@ -196,6 +210,18 @@ fn fatal(comptime format: []const u8, args: anytype) noreturn { js.panic(line.ptr, line.len); } +fn currentTimeMessage(msg_bytes: []u8) void { + client_base_timestamp = js.timestamp(); + server_base_timestamp = @bitCast(msg_bytes[1..][0..8].*); +} + +/// Nanoseconds passed since a server timestamp. +fn nsSince(server_timestamp: i64) i64 { + const ms_passed = js.timestamp() - client_base_timestamp; + const ns_passed = server_base_timestamp - server_timestamp; + return ns_passed + ms_passed * std.time.ns_per_ms; +} + fn sourceIndexMessage(msg_bytes: []u8) error{OutOfMemory}!void { const Header = abi.SourceIndexHeader; const header: Header = @bitCast(msg_bytes[0..@sizeOf(Header)].*); @@ -212,6 +238,7 @@ fn sourceIndexMessage(msg_bytes: []u8) error{OutOfMemory}!void { const files: []const Coverage.File = @alignCast(std.mem.bytesAsSlice(Coverage.File, msg_bytes[files_start..files_end])); const source_locations: []const Coverage.SourceLocation = @alignCast(std.mem.bytesAsSlice(Coverage.SourceLocation, msg_bytes[source_locations_start..source_locations_end])); + start_fuzzing_timestamp = header.start_timestamp; try updateCoverage(directories, files, source_locations, string_bytes); js.emitSourceIndexChange(); } diff --git a/lib/init/src/main.zig b/lib/init/src/main.zig index 0c4bb73429..ba5a2ccef2 100644 --- a/lib/init/src/main.zig +++ b/lib/init/src/main.zig @@ -27,7 +27,11 @@ test "simple test" { } test "fuzz example" { - // Try passing `--fuzz` to `zig build test` and see if it manages to fail this test case! - const input_bytes = std.testing.fuzzInput(.{}); - try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input_bytes)); + const global = struct { + fn testOne(input: []const u8) anyerror!void { + // Try passing `--fuzz` to `zig build test` and see if it manages to fail this test case! + try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input)); + } + }; + try std.testing.fuzz(global.testOne, .{}); } diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig index 23f8a02692..6258f4cdda 100644 --- a/lib/std/Build/Fuzz.zig +++ b/lib/std/Build/Fuzz.zig @@ -66,6 +66,8 @@ pub fn start( .coverage_files = .{}, .coverage_mutex = .{}, .coverage_condition = .{}, + + .base_timestamp = std.time.nanoTimestamp(), }; // For accepting HTTP connections. diff --git a/lib/std/Build/Fuzz/WebServer.zig b/lib/std/Build/Fuzz/WebServer.zig index a0ab018cf5..fb78e96abb 100644 --- a/lib/std/Build/Fuzz/WebServer.zig +++ b/lib/std/Build/Fuzz/WebServer.zig @@ -33,6 +33,9 @@ coverage_mutex: std.Thread.Mutex, /// Signaled when `coverage_files` changes. coverage_condition: std.Thread.Condition, +/// Time at initialization of WebServer. +base_timestamp: i128, + const fuzzer_bin_name = "fuzzer"; const fuzzer_arch_os_abi = "wasm32-freestanding"; const fuzzer_cpu_features = "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext"; @@ -43,6 +46,7 @@ const CoverageMap = struct { source_locations: []Coverage.SourceLocation, /// Elements are indexes into `source_locations` pointing to the unit tests that are being fuzz tested. entry_points: std.ArrayListUnmanaged(u32), + start_timestamp: i64, fn deinit(cm: *CoverageMap, gpa: Allocator) void { std.posix.munmap(cm.mapped_memory); @@ -87,6 +91,10 @@ pub fn run(ws: *WebServer) void { } } +fn now(s: *const WebServer) i64 { + return @intCast(std.time.nanoTimestamp() - s.base_timestamp); +} + fn accept(ws: *WebServer, connection: std.net.Server.Connection) void { defer connection.stream.close(); @@ -128,11 +136,11 @@ fn serveRequest(ws: *WebServer, request: *std.http.Server.Request) !void { std.mem.eql(u8, request.head.target, "/debug") or std.mem.eql(u8, request.head.target, "/debug/")) { - try serveFile(ws, request, "fuzzer/index.html", "text/html"); + try serveFile(ws, request, "fuzzer/web/index.html", "text/html"); } else if (std.mem.eql(u8, request.head.target, "/main.js") or std.mem.eql(u8, request.head.target, "/debug/main.js")) { - try serveFile(ws, request, "fuzzer/main.js", "application/javascript"); + try serveFile(ws, request, "fuzzer/web/main.js", "application/javascript"); } else if (std.mem.eql(u8, request.head.target, "/main.wasm")) { try serveWasm(ws, request, .ReleaseFast); } else if (std.mem.eql(u8, request.head.target, "/debug/main.wasm")) { @@ -217,7 +225,7 @@ fn buildWasmBinary( const main_src_path: Build.Cache.Path = .{ .root_dir = ws.zig_lib_directory, - .sub_path = "fuzzer/wasm/main.zig", + .sub_path = "fuzzer/web/main.zig", }; const walk_src_path: Build.Cache.Path = .{ .root_dir = ws.zig_lib_directory, @@ -381,6 +389,13 @@ fn serveWebSocket(ws: *WebServer, web_socket: *std.http.WebSocket) !void { ws.coverage_mutex.lock(); defer ws.coverage_mutex.unlock(); + // On first connection, the client needs to know what time the server + // thinks it is to rebase timestamps. + { + const timestamp_message: abi.CurrentTime = .{ .base = ws.now() }; + try web_socket.writeMessage(std.mem.asBytes(×tamp_message), .binary); + } + // On first connection, the client needs all the coverage information // so that subsequent updates can contain only the updated bits. var prev_unique_runs: usize = 0; @@ -406,7 +421,6 @@ fn sendCoverageContext( const seen_pcs = cov_header.seenBits(); const n_runs = @atomicLoad(usize, &cov_header.n_runs, .monotonic); const unique_runs = @atomicLoad(usize, &cov_header.unique_runs, .monotonic); - const lowest_stack = @atomicLoad(usize, &cov_header.lowest_stack, .monotonic); if (prev_unique_runs.* != unique_runs) { // There has been an update. if (prev_unique_runs.* == 0) { @@ -417,6 +431,7 @@ fn sendCoverageContext( .files_len = @intCast(coverage_map.coverage.files.entries.len), .source_locations_len = @intCast(coverage_map.source_locations.len), .string_bytes_len = @intCast(coverage_map.coverage.string_bytes.items.len), + .start_timestamp = coverage_map.start_timestamp, }; const iovecs: [5]std.posix.iovec_const = .{ makeIov(std.mem.asBytes(&header)), @@ -431,7 +446,6 @@ fn sendCoverageContext( const header: abi.CoverageUpdateHeader = .{ .n_runs = n_runs, .unique_runs = unique_runs, - .lowest_stack = lowest_stack, }; const iovecs: [2]std.posix.iovec_const = .{ makeIov(std.mem.asBytes(&header)), @@ -584,6 +598,7 @@ fn prepareTables( .mapped_memory = undefined, // populated below .source_locations = undefined, // populated below .entry_points = .{}, + .start_timestamp = ws.now(), }; errdefer gop.value_ptr.coverage.deinit(gpa); diff --git a/lib/std/Build/Fuzz/abi.zig b/lib/std/Build/Fuzz/abi.zig index 0e16f0d5fa..a6abc13fee 100644 --- a/lib/std/Build/Fuzz/abi.zig +++ b/lib/std/Build/Fuzz/abi.zig @@ -13,7 +13,6 @@ pub const SeenPcsHeader = extern struct { n_runs: usize, unique_runs: usize, pcs_len: usize, - lowest_stack: usize, /// Used for comptime assertions. Provides a mechanism for strategically /// causing compile errors. @@ -44,12 +43,19 @@ pub const SeenPcsHeader = extern struct { }; pub const ToClientTag = enum(u8) { + current_time, source_index, coverage_update, entry_points, _, }; +pub const CurrentTime = extern struct { + tag: ToClientTag = .current_time, + /// Number of nanoseconds that all other timestamps are in reference to. + base: i64 align(1), +}; + /// Sent to the fuzzer web client on first connection to the websocket URL. /// /// Trailing: @@ -63,6 +69,8 @@ pub const SourceIndexHeader = extern struct { files_len: u32, source_locations_len: u32, string_bytes_len: u32, + /// When, according to the server, fuzzing started. + start_timestamp: i64 align(4), pub const Flags = packed struct(u32) { tag: ToClientTag = .source_index, @@ -79,7 +87,6 @@ pub const CoverageUpdateHeader = extern struct { flags: Flags = .{}, n_runs: u64, unique_runs: u64, - lowest_stack: u64, pub const Flags = packed struct(u64) { tag: ToClientTag = .coverage_update, diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 35bb13bf0d..2cc38749eb 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -1141,6 +1141,10 @@ pub const FuzzInputOptions = struct { corpus: []const []const u8 = &.{}, }; -pub inline fn fuzzInput(options: FuzzInputOptions) []const u8 { - return @import("root").fuzzInput(options); +/// Inline to avoid coverage instrumentation. +pub inline fn fuzz( + comptime testOne: fn (input: []const u8) anyerror!void, + options: FuzzInputOptions, +) anyerror!void { + return @import("root").fuzz(testOne, options); } diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index 06c6b859ac..db69693a93 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -1708,6 +1708,10 @@ test "invalid tabs and carriage returns" { try testTokenize("\rpub\rswitch\r", &.{ .keyword_pub, .keyword_switch }); } +test "fuzzable properties upheld" { + return std.testing.fuzz(testPropertiesUpheld, .{}); +} + fn testTokenize(source: [:0]const u8, expected_token_tags: []const Token.Tag) !void { var tokenizer = Tokenizer.init(source); for (expected_token_tags) |expected_token_tag| { @@ -1723,8 +1727,7 @@ fn testTokenize(source: [:0]const u8, expected_token_tags: []const Token.Tag) !v try std.testing.expectEqual(source.len, last_token.loc.end); } -test "fuzzable properties upheld" { - const source = std.testing.fuzzInput(.{}); +fn testPropertiesUpheld(source: []const u8) anyerror!void { const source0 = try std.testing.allocator.dupeZ(u8, source); defer std.testing.allocator.free(source0); var tokenizer = Tokenizer.init(source0);