Commit 4e32edbff5

Andrew Kelley <andrew@ziglang.org>
2024-08-08 06:05:26
fuzzing: comptime assertions to protect the ABI
compile errors are nice
1 parent 2cfad20
Changed files (3)
lib/std/Build/Fuzz/abi.zig
@@ -8,12 +8,19 @@
 ///
 /// Trailing:
 /// * pc_addr: usize for each pcs_len
-/// * 1 bit per pc_addr, usize elements
+/// * 1 bit per pc_addr, u8 elements
 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.
+    pub const trailing = .{
+        .pc_addr,
+        .{ .pc_bits, u8 },
+    };
 };
 
 pub const ToClientTag = enum(u8) {
lib/std/Build/Fuzz/WebServer.zig
@@ -7,6 +7,7 @@ const Step = std.Build.Step;
 const Coverage = std.debug.Coverage;
 const abi = std.Build.Fuzz.abi;
 const log = std.log;
+const assert = std.debug.assert;
 
 const WebServer = @This();
 
@@ -383,7 +384,10 @@ fn sendCoverageContext(
     // TODO: make each events URL correspond to one coverage map
     const coverage_map = &coverage_maps[0];
     const cov_header: *const abi.SeenPcsHeader = @ptrCast(coverage_map.mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
+    comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_addr);
     const seen_pcs = coverage_map.mapped_memory[@sizeOf(abi.SeenPcsHeader) + coverage_map.source_locations.len * @sizeOf(usize) ..];
+    comptime assert(abi.SeenPcsHeader.trailing[1][0] == .pc_bits);
+    comptime assert(abi.SeenPcsHeader.trailing[1][1] == u8);
     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);
@@ -630,6 +634,7 @@ fn prepareTables(
     gop.value_ptr.mapped_memory = mapped_memory;
 
     const header: *const abi.SeenPcsHeader = @ptrCast(mapped_memory[0..@sizeOf(abi.SeenPcsHeader)]);
+    comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_addr);
     const pcs_bytes = mapped_memory[@sizeOf(abi.SeenPcsHeader)..][0 .. header.pcs_len * @sizeOf(usize)];
     const pcs = std.mem.bytesAsSlice(usize, pcs_bytes);
     const source_locations = try gpa.alloc(Coverage.SourceLocation, pcs.len);
@@ -649,6 +654,7 @@ fn addEntryPoint(ws: *WebServer, coverage_id: u64, addr: u64) error{ AlreadyRepo
 
     const coverage_map = ws.coverage_files.getPtr(coverage_id).?;
     const ptr = coverage_map.mapped_memory;
+    comptime assert(abi.SeenPcsHeader.trailing[0] == .pc_addr);
     const pcs_bytes = ptr[@sizeOf(abi.SeenPcsHeader)..][0 .. coverage_map.source_locations.len * @sizeOf(usize)];
     const pcs: []const usize = @alignCast(std.mem.bytesAsSlice(usize, pcs_bytes));
     const index = std.sort.upperBound(usize, pcs, addr, struct {
lib/fuzzer.zig
@@ -214,6 +214,9 @@ const Fuzzer = struct {
         });
         defer coverage_file.close();
         const n_bitset_elems = (flagged_pcs.len + 7) / 8;
+        comptime assert(SeenPcsHeader.trailing[0] == .pc_addr);
+        comptime assert(SeenPcsHeader.trailing[1][0] == .pc_bits);
+        comptime assert(SeenPcsHeader.trailing[1][1] == u8);
         const bytes_len = @sizeOf(SeenPcsHeader) + flagged_pcs.len * @sizeOf(usize) + n_bitset_elems;
         const existing_len = coverage_file.getEndPos() catch |err| {
             fatal("unable to check len of coverage file: {s}", .{@errorName(err)});
@@ -301,6 +304,10 @@ const Fuzzer = struct {
 
                 // Track code coverage from all runs.
                 {
+                    comptime assert(SeenPcsHeader.trailing[0] == .pc_addr);
+                    comptime assert(SeenPcsHeader.trailing[1][0] == .pc_bits);
+                    comptime assert(SeenPcsHeader.trailing[1][1] == u8);
+
                     const seen_pcs = f.seen_pcs.items[@sizeOf(SeenPcsHeader) + f.flagged_pcs.len * @sizeOf(usize) ..];
                     for (seen_pcs, 0..) |*elem, i| {
                         const byte_i = i * 8;