Artifact 5e5c715722760d6f45c4eaaf9b5d495bf83c59e5869a0e0dd76bb57b007e3b53:
- File
src/delta_fuzz.zig
— part of check-in
[1abe1089c1]
at
2026-07-20 04:06:34
on branch h9-fuzzcov
— fuzz: floor outcome reachability across the fuzz targets
serve_fuzz already fails a run when its generator stops reaching the dispatch arms it exists to exercise, and merge3 does the same for conflict shapes. Generalize that guard so a generator that has drifted to one outcome can no longer pass every no-crash round while testing nothing on the arm it dropped.
Six targets gain a measured reached-outcomes floor: delta decodeInt and targetSize (a decoded value vs MalformedDelta), artifact predicates (simple-pathname and wiki-name each accepting and rejecting), sync card parsers (push/igot/uvgimme each accepting and rejecting), logstore open (torn-tail recovery vs Corrupt), and transport deframe (a damaged container both decoding and being rejected). Every threshold is counted at a fixed seed, stated in the comment, and set below the arm's measured frequency; each floor gates above a minimum round count so short runs never trip it.
Every other target carries a one-line note on why its outcome space does not partition: round-trips have only a success arm, and several garbage generators reach only their reject arm, the accept arm covered by the round-trip and deterministic tests. (user: dmestas, size: 9531) [annotate] [blame] [check-ins using] [more...]
//! Seeded, parameterized fuzz targets for the delta format //! (draft-fossil-delta-format-00). Each target mirrors one of the fixed-seed //! blocks in delta.zig's own test suite, but takes its seed and round count //! from the caller (`zig build fuzz -- <target> [--seed N] [--rounds N]`) //! instead of a hardcoded constant, so an out-of-band runner can sweep many //! seeds at much higher round counts. Registered in fuzz_tests.zig. const std = @import("std"); const delta = @import("zit").delta; /// `apply` on arbitrary source+delta bytes never crashes: only a declared /// `Error`/`Allocator.Error` or a freed value comes back (delta §4 /// robustness; mirrors delta.zig's "apply on arbitrary source+delta never /// crashes" block). /// /// No reachability assertion: the outcome space does not partition here. A /// random source+delta never forms an applicable delta — measured 0 successes /// in 20_000 rounds at seed 1 — so `apply` only ever returns `MalformedDelta`. /// The success arm is unreachable by construction, not by generator rot, and /// is covered where it belongs, by `roundTrip`. pub fn applyGarbage(gpa: std.mem.Allocator, seed: u64, rounds: usize) !void { var prng = std.Random.DefaultPrng.init(seed); const rand = prng.random(); var round: usize = 0; while (round < rounds) : (round += 1) { var sbuf: [128]u8 = undefined; var dbuf: [160]u8 = undefined; const slen = rand.uintLessThan(usize, sbuf.len + 1); const dlen = rand.uintLessThan(usize, dbuf.len + 1); for (sbuf[0..slen]) |*b| b.* = rand.int(u8); // Bias delta bytes toward DIGIT64/opcodes so inputs reach deep into // the decoder rather than being rejected at byte 0. for (dbuf[0..dlen]) |*b| b.* = if (rand.boolean()) rand.int(u8) else delta.digit64[rand.uintLessThan(usize, delta.digit64.len)]; if (delta.apply(gpa, sbuf[0..slen], dbuf[0..dlen])) |out| gpa.free(out) else |_| {} } } /// `apply(source, create(source, target)) == target` for arbitrary inputs /// (delta §5 round-trip; mirrors delta.zig's round-trip block). /// /// No reachability assertion: every round is a well-formed create/apply pair, /// so a successful round-trip is the only outcome — there is no reject arm to /// reach. pub fn roundTrip(gpa: std.mem.Allocator, seed: u64, rounds: usize) !void { var prng = std.Random.DefaultPrng.init(seed); const rand = prng.random(); var round: usize = 0; while (round < rounds) : (round += 1) { var sbuf: [256]u8 = undefined; const slen = rand.uintLessThan(usize, sbuf.len + 1); for (sbuf[0..slen]) |*b| b.* = rand.int(u8); const source = sbuf[0..slen]; // Build target from a mix of source runs (exercises copies) and // fresh random bytes (exercises literals). var tbuf: [384]u8 = undefined; const tlen = rand.uintLessThan(usize, tbuf.len + 1); var t: usize = 0; while (t < tlen) { if (slen >= 8 and rand.boolean()) { const start = rand.uintLessThan(usize, slen); const run = 1 + rand.uintLessThan(usize, @min(slen - start, tlen - t)); @memcpy(tbuf[t..][0..run], source[start..][0..run]); t += run; } else { tbuf[t] = rand.int(u8); t += 1; } } const target = tbuf[0..tlen]; const d = try delta.create(gpa, source, target); defer gpa.free(d); const out = try delta.apply(gpa, source, d); defer gpa.free(out); try std.testing.expectEqualSlices(u8, target, out); } } /// `decodeInt` on arbitrary digit strings stays bounded: consumes no more /// than the input, and the only error it ever returns is `MalformedDelta` /// (delta §3.1; mirrors delta.zig's "decodeInt on arbitrary digit strings /// stays bounded" block). /// /// Reachability, asserted rather than assumed (the `serve_fuzz` idea): a run /// must reach BOTH a successful decode and a `MalformedDelta` rejection, or the /// generator has drifted to one arm and is testing nothing on the other. Both /// are common — measured at seed 1: 20_000 rounds gave 15_143 decoded / 4_857 /// malformed, and even 64 rounds gives ≥12 of each across seeds 1-3 — so the /// floor sits at the low end of the smoke range with an order of magnitude of /// headroom. const decode_int_min_rounds: usize = 64; pub fn decodeIntBounded(gpa: std.mem.Allocator, seed: u64, rounds: usize) !void { _ = gpa; var prng = std.Random.DefaultPrng.init(seed); const rand = prng.random(); var decoded: usize = 0; var malformed: usize = 0; var round: usize = 0; while (round < rounds) : (round += 1) { var buf: [40]u8 = undefined; const len = rand.uintLessThan(usize, buf.len + 1); // Bias toward DIGIT64 so long all-digit runs (overflow candidates) occur. for (buf[0..len]) |*b| b.* = if (rand.uintLessThan(u8, 4) == 0) rand.int(u8) else delta.digit64[rand.uintLessThan(usize, delta.digit64.len)]; if (delta.decodeInt(buf[0..len])) |dec| { try std.testing.expect(dec.len <= len); // consumed no more than the input decoded += 1; } else |err| { try std.testing.expect(err == error.MalformedDelta); // rejection is the only error malformed += 1; } } if (rounds >= decode_int_min_rounds and (decoded == 0 or malformed == 0)) { std.debug.print( "delta_decode_int_bounded: {d} rounds reached decoded={d} malformed={d} — " ++ "the digit generator no longer straddles the decodeInt accept/reject split\n", .{ rounds, decoded, malformed }, ); return error.OutcomeUnreached; } } /// `create` rejects rather than panics/wraps on synthetic source/target sizes /// `>= 2^32` (delta §3.1: every integer the format carries must be < 2^32; /// mirrors delta.zig's "create rejects synthetic sizes >= 2^32" block). Sizes /// are drawn only from `[2^32, 2^40]`: the guard in `create` returns before /// any deref or allocation, so an oversized slice can be built from a single /// fake base pointer without touching 4+ GiB of real memory. A length below /// 2^32 is out of scope here (it would proceed into the loop and dereference) /// and is covered instead by `roundTrip`. /// /// No reachability assertion: every input is deliberately oversized, so the /// only outcome is the `TargetTooLarge` rejection — a single arm by /// construction, not a partition. pub fn createLargeSizes(gpa: std.mem.Allocator, seed: u64, rounds: usize) !void { if (@bitSizeOf(usize) < 33) return; // a length >= 2^32 is unrepresentable on 32-bit var prng = std.Random.DefaultPrng.init(seed); const rand = prng.random(); var byte: u8 = 0; const min_len: usize = @as(usize, 1) << 32; const max_span: usize = (@as(usize, 1) << 40) - min_len; var round: usize = 0; while (round < rounds) : (round += 1) { const len = min_len + rand.uintLessThan(usize, max_span + 1); const huge = @as([*]const u8, @ptrCast(&byte))[0..len]; // never dereferenced: the guard returns first const variant = rand.uintLessThan(u8, 3); // 0: source oversized, 1: target oversized, 2: both const source: []const u8 = if (variant == 1) "" else huge; const target: []const u8 = if (variant == 0) "" else huge; try std.testing.expectError(delta.CreateError.TargetTooLarge, delta.create(gpa, source, target)); } } /// `targetSize` on arbitrary bytes never crashes: only `MalformedDelta` or a /// value within the u32 cap `decodeInt` enforces comes back (delta §4.1). /// /// Reachability, asserted rather than assumed (the `serve_fuzz` idea): a run /// must reach BOTH a header that parses to a size and a `MalformedDelta` /// rejection. The accept arm is thin — a random prefix has to be `digits '\n'` /// — so its floor is set for a soak run, not the smoke range: measured at seed /// 1, 20_000 rounds gave 78 sized / 19_922 malformed (≈0.4% accept), and 2_000 /// rounds gives ≥8 sized across seeds 1-3, an order of magnitude above one. const target_size_min_rounds: usize = 2_000; pub fn targetSizeGarbage(gpa: std.mem.Allocator, seed: u64, rounds: usize) !void { _ = gpa; var prng = std.Random.DefaultPrng.init(seed); const rand = prng.random(); var sized: usize = 0; var malformed: usize = 0; var round: usize = 0; while (round < rounds) : (round += 1) { var buf: [64]u8 = undefined; const len = rand.uintLessThan(usize, buf.len + 1); for (buf[0..len]) |*b| b.* = if (rand.boolean()) rand.int(u8) else delta.digit64[rand.uintLessThan(usize, delta.digit64.len)]; if (delta.targetSize(buf[0..len])) |size| { try std.testing.expect(size <= std.math.maxInt(u32)); sized += 1; } else |err| { try std.testing.expect(err == error.MalformedDelta); malformed += 1; } } if (rounds >= target_size_min_rounds and (sized == 0 or malformed == 0)) { std.debug.print( "delta_target_size_garbage: {d} rounds reached sized={d} malformed={d} — " ++ "the generator no longer produces a parseable size header\n", .{ rounds, sized, malformed }, ); return error.OutcomeUnreached; } }