diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2025-09-26 05:28:46 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-09-26 05:28:46 -0700 |
| commit | e0dc2e4e3ffe72e5e637e30bdf1d2c59b56f3cb6 (patch) | |
| tree | 23cafd6ae8e80026c2f257d291b91bae932e5730 /lib/std | |
| parent | 3b365a1f9b277dd2cf7f7dac51e71647e164ff3c (diff) | |
| parent | 52a13f6a7fb0933c065348128ee3e9aecd64255b (diff) | |
| download | zig-e0dc2e4e3ffe72e5e637e30bdf1d2c59b56f3cb6.tar.gz zig-e0dc2e4e3ffe72e5e637e30bdf1d2c59b56f3cb6.zip | |
Merge pull request #25342 from ziglang/fuzz-limit
fuzzing: implement limited fuzzing
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/Build/Fuzz.zig | 206 | ||||
| -rw-r--r-- | lib/std/Build/Step/Run.zig | 53 | ||||
| -rw-r--r-- | lib/std/Build/WebServer.zig | 10 | ||||
| -rw-r--r-- | lib/std/Build/abi.zig | 16 | ||||
| -rw-r--r-- | lib/std/zig/Client.zig | 12 | ||||
| -rw-r--r-- | lib/std/zig/Server.zig | 29 |
6 files changed, 264 insertions, 62 deletions
diff --git a/lib/std/Build/Fuzz.zig b/lib/std/Build/Fuzz.zig index 6bea7654b2..27c2f35d72 100644 --- a/lib/std/Build/Fuzz.zig +++ b/lib/std/Build/Fuzz.zig @@ -8,17 +8,22 @@ const Allocator = std.mem.Allocator; const log = std.log; const Coverage = std.debug.Coverage; const abi = Build.abi.fuzz; +const tty = std.Io.tty; const Fuzz = @This(); const build_runner = @import("root"); -ws: *Build.WebServer, +gpa: Allocator, +mode: Mode, -/// Allocated into `ws.gpa`. +/// Allocated into `gpa`. run_steps: []const *Step.Run, wait_group: std.Thread.WaitGroup, +root_prog_node: std.Progress.Node, prog_node: std.Progress.Node, +thread_pool: *std.Thread.Pool, +ttyconf: tty.Config, /// Protects `coverage_files`. coverage_mutex: std.Thread.Mutex, @@ -28,9 +33,23 @@ queue_mutex: std.Thread.Mutex, queue_cond: std.Thread.Condition, msg_queue: std.ArrayListUnmanaged(Msg), +pub const Mode = union(enum) { + forever: struct { ws: *Build.WebServer }, + limit: Limited, + + pub const Limited = struct { + amount: u64, + }; +}; + const Msg = union(enum) { coverage: struct { id: u64, + cumulative: struct { + runs: u64, + unique: u64, + coverage: u64, + }, run: *Step.Run, }, entry_point: struct { @@ -54,23 +73,28 @@ const CoverageMap = struct { } }; -pub fn init(ws: *Build.WebServer) Allocator.Error!Fuzz { - const gpa = ws.gpa; - +pub fn init( + gpa: Allocator, + thread_pool: *std.Thread.Pool, + all_steps: []const *Build.Step, + root_prog_node: std.Progress.Node, + ttyconf: tty.Config, + mode: Mode, +) Allocator.Error!Fuzz { const run_steps: []const *Step.Run = steps: { var steps: std.ArrayListUnmanaged(*Step.Run) = .empty; defer steps.deinit(gpa); - const rebuild_node = ws.root_prog_node.start("Rebuilding Unit Tests", 0); + const rebuild_node = root_prog_node.start("Rebuilding Unit Tests", 0); defer rebuild_node.end(); var rebuild_wg: std.Thread.WaitGroup = .{}; defer rebuild_wg.wait(); - for (ws.all_steps) |step| { + for (all_steps) |step| { const run = step.cast(Step.Run) orelse continue; if (run.producer == null) continue; if (run.fuzz_tests.items.len == 0) continue; try steps.append(gpa, run); - ws.thread_pool.spawnWg(&rebuild_wg, rebuildTestsWorkerRun, .{ run, gpa, ws.ttyconf, rebuild_node }); + thread_pool.spawnWg(&rebuild_wg, rebuildTestsWorkerRun, .{ run, gpa, ttyconf, rebuild_node }); } if (steps.items.len == 0) fatal("no fuzz tests found", .{}); @@ -86,9 +110,13 @@ pub fn init(ws: *Build.WebServer) Allocator.Error!Fuzz { } return .{ - .ws = ws, + .gpa = gpa, + .mode = mode, .run_steps = run_steps, .wait_group = .{}, + .thread_pool = thread_pool, + .ttyconf = ttyconf, + .root_prog_node = root_prog_node, .prog_node = .none, .coverage_files = .empty, .coverage_mutex = .{}, @@ -99,32 +127,31 @@ pub fn init(ws: *Build.WebServer) Allocator.Error!Fuzz { } pub fn start(fuzz: *Fuzz) void { - const ws = fuzz.ws; - fuzz.prog_node = ws.root_prog_node.start("Fuzzing", fuzz.run_steps.len); - - // For polling messages and sending updates to subscribers. - fuzz.wait_group.start(); - _ = std.Thread.spawn(.{}, coverageRun, .{fuzz}) catch |err| { - fuzz.wait_group.finish(); - fatal("unable to spawn coverage thread: {s}", .{@errorName(err)}); - }; + fuzz.prog_node = fuzz.root_prog_node.start("Fuzzing", fuzz.run_steps.len); + + if (fuzz.mode == .forever) { + // For polling messages and sending updates to subscribers. + fuzz.wait_group.start(); + _ = std.Thread.spawn(.{}, coverageRun, .{fuzz}) catch |err| { + fuzz.wait_group.finish(); + fatal("unable to spawn coverage thread: {s}", .{@errorName(err)}); + }; + } for (fuzz.run_steps) |run| { for (run.fuzz_tests.items) |unit_test_index| { assert(run.rebuilt_executable != null); - ws.thread_pool.spawnWg(&fuzz.wait_group, fuzzWorkerRun, .{ + fuzz.thread_pool.spawnWg(&fuzz.wait_group, fuzzWorkerRun, .{ fuzz, run, unit_test_index, }); } } } + pub fn deinit(fuzz: *Fuzz) void { - if (true) @panic("TODO: terminate the fuzzer processes"); - fuzz.wait_group.wait(); + if (!fuzz.wait_group.isDone()) @panic("TODO: terminate the fuzzer processes"); fuzz.prog_node.end(); - - const gpa = fuzz.ws.gpa; - gpa.free(fuzz.run_steps); + fuzz.gpa.free(fuzz.run_steps); } fn rebuildTestsWorkerRun(run: *Step.Run, gpa: Allocator, ttyconf: std.Io.tty.Config, parent_prog_node: std.Progress.Node) void { @@ -177,7 +204,7 @@ fn fuzzWorkerRun( var buf: [256]u8 = undefined; const w = std.debug.lockStderrWriter(&buf); defer std.debug.unlockStderrWriter(); - build_runner.printErrorMessages(gpa, &run.step, .{ .ttyconf = fuzz.ws.ttyconf }, w, false) catch {}; + build_runner.printErrorMessages(gpa, &run.step, .{ .ttyconf = fuzz.ttyconf }, w, false) catch {}; return; }, else => { @@ -190,20 +217,20 @@ fn fuzzWorkerRun( } pub fn serveSourcesTar(fuzz: *Fuzz, req: *std.http.Server.Request) !void { - const gpa = fuzz.ws.gpa; + assert(fuzz.mode == .forever); - var arena_state: std.heap.ArenaAllocator = .init(gpa); + var arena_state: std.heap.ArenaAllocator = .init(fuzz.gpa); defer arena_state.deinit(); const arena = arena_state.allocator(); const DedupTable = std.ArrayHashMapUnmanaged(Build.Cache.Path, void, Build.Cache.Path.TableAdapter, false); var dedup_table: DedupTable = .empty; - defer dedup_table.deinit(gpa); + defer dedup_table.deinit(fuzz.gpa); for (fuzz.run_steps) |run_step| { const compile_inputs = run_step.producer.?.step.inputs.table; for (compile_inputs.keys(), compile_inputs.values()) |dir_path, *file_list| { - try dedup_table.ensureUnusedCapacity(gpa, file_list.items.len); + try dedup_table.ensureUnusedCapacity(fuzz.gpa, file_list.items.len); for (file_list.items) |sub_path| { if (!std.mem.endsWith(u8, sub_path, ".zig")) continue; const joined_path = try dir_path.join(arena, sub_path); @@ -224,13 +251,18 @@ pub fn serveSourcesTar(fuzz: *Fuzz, req: *std.http.Server.Request) !void { } }; std.mem.sortUnstable(Build.Cache.Path, deduped_paths, SortContext{}, SortContext.lessThan); - return fuzz.ws.serveTarFile(req, deduped_paths); + return fuzz.mode.forever.ws.serveTarFile(req, deduped_paths); } pub const Previous = struct { unique_runs: usize, entry_points: usize, - pub const init: Previous = .{ .unique_runs = 0, .entry_points = 0 }; + sent_source_index: bool, + pub const init: Previous = .{ + .unique_runs = 0, + .entry_points = 0, + .sent_source_index = false, + }; }; pub fn sendUpdate( fuzz: *Fuzz, @@ -253,7 +285,8 @@ pub fn sendUpdate( const n_runs = @atomicLoad(usize, &cov_header.n_runs, .monotonic); const unique_runs = @atomicLoad(usize, &cov_header.unique_runs, .monotonic); { - if (unique_runs != 0 and prev.unique_runs == 0) { + if (!prev.sent_source_index) { + prev.sent_source_index = true; // We need to send initial context. const header: abi.SourceIndexHeader = .{ .directories_len = @intCast(coverage_map.coverage.directories.entries.len), @@ -319,13 +352,13 @@ fn coverageRun(fuzz: *Fuzz) void { } } fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutOfMemory, AlreadyReported }!void { - const ws = fuzz.ws; - const gpa = ws.gpa; + assert(fuzz.mode == .forever); + const ws = fuzz.mode.forever.ws; fuzz.coverage_mutex.lock(); defer fuzz.coverage_mutex.unlock(); - const gop = try fuzz.coverage_files.getOrPut(gpa, coverage_id); + const gop = try fuzz.coverage_files.getOrPut(fuzz.gpa, coverage_id); if (gop.found_existing) { // We are fuzzing the same executable with multiple threads. // Perhaps the same unit test; perhaps a different one. In any @@ -343,16 +376,16 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO .entry_points = .{}, .start_timestamp = ws.now(), }; - errdefer gop.value_ptr.coverage.deinit(gpa); + errdefer gop.value_ptr.coverage.deinit(fuzz.gpa); const rebuilt_exe_path = run_step.rebuilt_executable.?; - var debug_info = std.debug.Info.load(gpa, rebuilt_exe_path, &gop.value_ptr.coverage) catch |err| { + var debug_info = std.debug.Info.load(fuzz.gpa, rebuilt_exe_path, &gop.value_ptr.coverage) catch |err| { log.err("step '{s}': failed to load debug information for '{f}': {s}", .{ run_step.step.name, rebuilt_exe_path, @errorName(err), }); return error.AlreadyReported; }; - defer debug_info.deinit(gpa); + defer debug_info.deinit(fuzz.gpa); const coverage_file_path: Build.Cache.Path = .{ .root_dir = run_step.step.owner.cache_root, @@ -386,14 +419,14 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO const header: *const abi.SeenPcsHeader = @ptrCast(mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]); const pcs = header.pcAddrs(); - const source_locations = try gpa.alloc(Coverage.SourceLocation, pcs.len); - errdefer gpa.free(source_locations); + const source_locations = try fuzz.gpa.alloc(Coverage.SourceLocation, pcs.len); + errdefer fuzz.gpa.free(source_locations); // Unfortunately the PCs array that LLVM gives us from the 8-bit PC // counters feature is not sorted. var sorted_pcs: std.MultiArrayList(struct { pc: u64, index: u32, sl: Coverage.SourceLocation }) = .{}; - defer sorted_pcs.deinit(gpa); - try sorted_pcs.resize(gpa, pcs.len); + defer sorted_pcs.deinit(fuzz.gpa); + try sorted_pcs.resize(fuzz.gpa, pcs.len); @memcpy(sorted_pcs.items(.pc), pcs); for (sorted_pcs.items(.index), 0..) |*v, i| v.* = @intCast(i); sorted_pcs.sortUnstable(struct { @@ -404,7 +437,7 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO } }{ .addrs = sorted_pcs.items(.pc) }); - debug_info.resolveAddresses(gpa, sorted_pcs.items(.pc), sorted_pcs.items(.sl)) catch |err| { + debug_info.resolveAddresses(fuzz.gpa, sorted_pcs.items(.pc), sorted_pcs.items(.sl)) catch |err| { log.err("failed to resolve addresses to source locations: {s}", .{@errorName(err)}); return error.AlreadyReported; }; @@ -414,6 +447,7 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO ws.notifyUpdate(); } + fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReported, OutOfMemory }!void { fuzz.coverage_mutex.lock(); defer fuzz.coverage_mutex.unlock(); @@ -445,5 +479,89 @@ fn addEntryPoint(fuzz: *Fuzz, coverage_id: u64, addr: u64) error{ AlreadyReporte addr, file_name, sl.line, sl.column, index, pcs[index - 1], pcs[index + 1], }); } - try coverage_map.entry_points.append(fuzz.ws.gpa, @intCast(index)); + try coverage_map.entry_points.append(fuzz.gpa, @intCast(index)); +} + +pub fn waitAndPrintReport(fuzz: *Fuzz) void { + assert(fuzz.mode == .limit); + + fuzz.wait_group.wait(); + fuzz.wait_group.reset(); + + std.debug.print("======= FUZZING REPORT =======\n", .{}); + for (fuzz.msg_queue.items) |msg| { + if (msg != .coverage) continue; + + const cov = msg.coverage; + const coverage_file_path: std.Build.Cache.Path = .{ + .root_dir = cov.run.step.owner.cache_root, + .sub_path = "v/" ++ std.fmt.hex(cov.id), + }; + var coverage_file = coverage_file_path.root_dir.handle.openFile(coverage_file_path.sub_path, .{}) catch |err| { + fatal("step '{s}': failed to load coverage file '{f}': {s}", .{ + cov.run.step.name, coverage_file_path, @errorName(err), + }); + }; + defer coverage_file.close(); + + const fuzz_abi = std.Build.abi.fuzz; + var rbuf: [0x1000]u8 = undefined; + var r = coverage_file.reader(&rbuf); + + var header: fuzz_abi.SeenPcsHeader = undefined; + r.interface.readSliceAll(std.mem.asBytes(&header)) catch |err| { + fatal("step '{s}': failed to read from coverage file '{f}': {s}", .{ + cov.run.step.name, coverage_file_path, @errorName(err), + }); + }; + + if (header.pcs_len == 0) { + fatal("step '{s}': corrupted coverage file '{f}': pcs_len was zero", .{ + cov.run.step.name, coverage_file_path, + }); + } + + var seen_count: usize = 0; + const chunk_count = fuzz_abi.SeenPcsHeader.seenElemsLen(header.pcs_len); + for (0..chunk_count) |_| { + const seen = r.interface.takeInt(usize, .little) catch |err| { + fatal("step '{s}': failed to read from coverage file '{f}': {s}", .{ + cov.run.step.name, coverage_file_path, @errorName(err), + }); + }; + seen_count += @popCount(seen); + } + + const seen_f: f64 = @floatFromInt(seen_count); + const total_f: f64 = @floatFromInt(header.pcs_len); + const ratio = seen_f / total_f; + std.debug.print( + \\Step: {s} + \\Fuzz test: "{s}" ({x}) + \\Runs: {} -> {} + \\Unique runs: {} -> {} + \\Coverage: {}/{} -> {}/{} ({:.02}%) + \\ + , .{ + cov.run.step.name, + cov.run.cached_test_metadata.?.testName(cov.run.fuzz_tests.items[0]), + cov.id, + cov.cumulative.runs, + header.n_runs, + cov.cumulative.unique, + header.unique_runs, + cov.cumulative.coverage, + header.pcs_len, + seen_count, + header.pcs_len, + ratio * 100, + }); + + std.debug.print("------------------------------\n", .{}); + } + std.debug.print( + \\Values are accumulated across multiple runs when preserving the cache. + \\============================== + \\ + , .{}); } diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index fbcc0217f3..e6ee5ad233 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1662,12 +1662,24 @@ fn evalZigTest( // If this is `true`, we avoid ever entering the polling loop below, because the stdin pipe has // somehow already closed; instead, we go straight to capturing stderr in case it has anything // useful. - const first_write_failed = if (fuzz_context) |fuzz| failed: { - sendRunTestMessage(child.stdin.?, .start_fuzzing, fuzz.unit_test_index) catch |err| { - try run.step.addError("unable to write stdin: {s}", .{@errorName(err)}); - break :failed true; - }; - break :failed false; + const first_write_failed = if (fuzz_context) |fctx| failed: { + switch (fctx.fuzz.mode) { + .forever => { + const instance_id = 0; // will be used by mutiprocess forever fuzzing + sendRunFuzzTestMessage(child.stdin.?, fctx.unit_test_index, .forever, instance_id) catch |err| { + try run.step.addError("unable to write stdin: {s}", .{@errorName(err)}); + break :failed true; + }; + break :failed false; + }, + .limit => |limit| { + sendRunFuzzTestMessage(child.stdin.?, fctx.unit_test_index, .iterations, limit.amount) catch |err| { + try run.step.addError("unable to write stdin: {s}", .{@errorName(err)}); + break :failed true; + }; + break :failed false; + }, + } } else failed: { run.fuzz_tests.clearRetainingCapacity(); sendMessage(child.stdin.?, .query_test_metadata) catch |err| { @@ -1778,13 +1790,18 @@ fn evalZigTest( }, .coverage_id => { const fuzz = fuzz_context.?.fuzz; - const msg_ptr: *align(1) const u64 = @ptrCast(body); - coverage_id = msg_ptr.*; + const msg_ptr: *align(1) const [4]u64 = @ptrCast(body); + coverage_id = msg_ptr[0]; { fuzz.queue_mutex.lock(); defer fuzz.queue_mutex.unlock(); - try fuzz.msg_queue.append(fuzz.ws.gpa, .{ .coverage = .{ + try fuzz.msg_queue.append(fuzz.gpa, .{ .coverage = .{ .id = coverage_id.?, + .cumulative = .{ + .runs = msg_ptr[1], + .unique = msg_ptr[2], + .coverage = msg_ptr[3], + }, .run = run, } }); fuzz.queue_cond.signal(); @@ -1797,7 +1814,7 @@ fn evalZigTest( { fuzz.queue_mutex.lock(); defer fuzz.queue_mutex.unlock(); - try fuzz.msg_queue.append(fuzz.ws.gpa, .{ .entry_point = .{ + try fuzz.msg_queue.append(fuzz.gpa, .{ .entry_point = .{ .addr = addr, .coverage_id = coverage_id.?, } }); @@ -1900,6 +1917,22 @@ fn sendRunTestMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag, index: try file.writeAll(full_msg); } +fn sendRunFuzzTestMessage( + file: std.fs.File, + index: u32, + kind: std.Build.abi.fuzz.LimitKind, + amount_or_instance: u64, +) !void { + const header: std.zig.Client.Message.Header = .{ + .tag = .start_fuzzing, + .bytes_len = 4 + 1 + 8, + }; + const full_msg = std.mem.asBytes(&header) ++ std.mem.asBytes(&index) ++ + std.mem.asBytes(&kind) ++ std.mem.asBytes(&amount_or_instance); + + try file.writeAll(full_msg); +} + fn evalGeneric(run: *Run, child: *std.process.Child) !StdIoResult { const b = run.step.owner; const arena = b.allocator; diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 1b3e3bfe82..8f91a8580d 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -219,12 +219,20 @@ pub fn finishBuild(ws: *WebServer, opts: struct { // Affects or affected by issues #5185, #22523, and #22464. std.process.fatal("--fuzz not yet implemented on {d}-bit platforms", .{@bitSizeOf(usize)}); } + assert(ws.fuzz == null); ws.build_status.store(.fuzz_init, .monotonic); ws.notifyUpdate(); - ws.fuzz = Fuzz.init(ws) catch |err| std.process.fatal("failed to start fuzzer: {s}", .{@errorName(err)}); + ws.fuzz = Fuzz.init( + ws.gpa, + ws.thread_pool, + ws.all_steps, + ws.root_prog_node, + ws.ttyconf, + .{ .forever = .{ .ws = ws } }, + ) catch |err| std.process.fatal("failed to start fuzzer: {s}", .{@errorName(err)}); ws.fuzz.?.start(); } diff --git a/lib/std/Build/abi.zig b/lib/std/Build/abi.zig index 2398a8ed8d..020e2ed032 100644 --- a/lib/std/Build/abi.zig +++ b/lib/std/Build/abi.zig @@ -140,10 +140,10 @@ pub const Rebuild = extern struct { pub const fuzz = struct { pub const TestOne = *const fn (Slice) callconv(.c) void; pub extern fn fuzzer_init(cache_dir_path: Slice) void; - pub extern fn fuzzer_coverage_id() u64; + pub extern fn fuzzer_coverage() Coverage; pub extern fn fuzzer_init_test(test_one: TestOne, unit_test_name: Slice) void; pub extern fn fuzzer_new_input(bytes: Slice) void; - pub extern fn fuzzer_main() void; + pub extern fn fuzzer_main(limit_kind: LimitKind, amount: u64) void; pub const Slice = extern struct { ptr: [*]const u8, @@ -158,6 +158,8 @@ pub const fuzz = struct { } }; + pub const LimitKind = enum(u8) { forever, iterations }; + /// libfuzzer uses this and its usize is the one that counts. To match the ABI, /// make the ints be the size of the target used with libfuzzer. /// @@ -251,6 +253,16 @@ pub const fuzz = struct { return .{ .locs_len_raw = @bitCast(locs_len) }; } }; + + /// Sent by lib/fuzzer to test_runner to obtain information about the + /// active memory mapped input file and cumulative stats about previous + /// fuzzing runs. + pub const Coverage = extern struct { + id: u64, + runs: u64, + unique: u64, + seen: u64, + }; }; /// ABI bits specifically relating to the time report interface. diff --git a/lib/std/zig/Client.zig b/lib/std/zig/Client.zig index 345b9f9797..21168a89d7 100644 --- a/lib/std/zig/Client.zig +++ b/lib/std/zig/Client.zig @@ -33,10 +33,18 @@ 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. + /// Ask the test runner to start fuzzing a particular test forever or for a given amount of time/iterations. + /// The message body is: + /// - a u32 test index. + /// - a u8 test limit kind (std.Build.api.fuzz.LimitKind) + /// - a u64 value whose meaning depends on FuzzLimitKind (either a limit amount or an instance id) start_fuzzing, _, }; + + comptime { + const std = @import("std"); + std.debug.assert(@sizeOf(std.Build.abi.fuzz.LimitKind) == 1); + } }; diff --git a/lib/std/zig/Server.zig b/lib/std/zig/Server.zig index ea60354741..c035cbdec2 100644 --- a/lib/std/zig/Server.zig +++ b/lib/std/zig/Server.zig @@ -42,9 +42,13 @@ pub const Message = struct { /// The remaining bytes is the file path relative to that prefix. /// The prefixes are hard-coded in Compilation.create (cwd, zig lib dir, local cache dir) file_system_inputs, - /// Body is a u64le that indicates the file path within the cache used - /// to store coverage information. The integer is a hash of the PCs - /// stored within that file. + /// Body is: + /// - a u64le that indicates the file path within the cache used + /// to store coverage information. The integer is a hash of the PCs + /// stored within that file. + /// - u64le of total runs accumulated + /// - u64le of unique runs accumulated + /// - u64le of coverage accumulated coverage_id, /// Body is a u64le that indicates the function pointer virtual memory /// address of the fuzz unit test. This is used to provide a starting @@ -141,9 +145,15 @@ pub fn receiveMessage(s: *Server) !InMessage.Header { return s.in.takeStruct(InMessage.Header, .little); } +pub fn receiveBody_u8(s: *Server) !u8 { + return s.in.takeInt(u8, .little); +} pub fn receiveBody_u32(s: *Server) !u32 { return s.in.takeInt(u32, .little); } +pub fn receiveBody_u64(s: *Server) !u64 { + return s.in.takeInt(u64, .little); +} pub fn serveStringMessage(s: *Server, tag: OutMessage.Tag, msg: []const u8) !void { try s.serveMessageHeader(.{ @@ -160,6 +170,7 @@ pub fn serveMessageHeader(s: *const Server, header: OutMessage.Header) !void { } pub fn serveU64Message(s: *const Server, tag: OutMessage.Tag, int: u64) !void { + assert(tag != .coverage_id); try serveMessageHeader(s, .{ .tag = tag, .bytes_len = @sizeOf(u64), @@ -168,6 +179,18 @@ pub fn serveU64Message(s: *const Server, tag: OutMessage.Tag, int: u64) !void { try s.out.flush(); } +pub fn serveCoverageIdMessage(s: *const Server, id: u64, runs: u64, unique: u64, cov: u64) !void { + try serveMessageHeader(s, .{ + .tag = .coverage_id, + .bytes_len = @sizeOf(u64) + @sizeOf(u64) + @sizeOf(u64) + @sizeOf(u64), + }); + try s.out.writeInt(u64, id, .little); + try s.out.writeInt(u64, runs, .little); + try s.out.writeInt(u64, unique, .little); + try s.out.writeInt(u64, cov, .little); + try s.out.flush(); +} + pub fn serveEmitDigest( s: *Server, digest: *const [Cache.bin_digest_len]u8, |
