pub(super) const DEFAULT_FUZZ_SEED: u64 = 0xDEAD_BEEF;
pub(super) const DEFAULT_FUZZ_ITERS: usize = 200;
pub(super) const FUZZ_BYTES_PER_ITER: usize = 256;
pub(super) const MAX_FAILURES_SHOWN: usize = 20;
pub(super) enum FuzzOutcome {
Passed,
ExpectedBail,
}
pub(super) fn fuzz_knobs(label: &str) -> (usize, u64) {
let iters: usize = std::env::var("SPLICER_FUZZ_ITERS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_FUZZ_ITERS);
let base_seed: u64 = std::env::var("SPLICER_FUZZ_SEED")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(DEFAULT_FUZZ_SEED);
eprintln!("{label}: iters={iters} base_seed={base_seed}");
(iters, base_seed)
}
pub(super) fn fuzz_seeded_bytes(seed: u64, len: usize) -> Vec<u8> {
let mut state = seed ^ 0x9E37_79B9_7F4A_7C15;
(0..len)
.map(|_| {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
(state >> 32) as u8
})
.collect()
}
pub(super) fn run_structural_fuzz(
label: &str,
iter_fn: impl Fn(&[u8]) -> Result<FuzzOutcome, String>,
) {
let (iters, base_seed) = fuzz_knobs(label);
let mut passed = 0usize;
let mut expected_bails = 0usize;
let mut failures: Vec<String> = Vec::new();
for i in 0..iters {
let iter_seed = base_seed.wrapping_add(i as u64);
let bytes = fuzz_seeded_bytes(iter_seed, FUZZ_BYTES_PER_ITER);
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| iter_fn(&bytes)));
match outcome {
Ok(Ok(FuzzOutcome::Passed)) => passed += 1,
Ok(Ok(FuzzOutcome::ExpectedBail)) => expected_bails += 1,
Ok(Err(msg)) => failures.push(format!("iter {i} seed {iter_seed}: {msg}")),
Err(_) => failures.push(format!("iter {i} seed {iter_seed}: PANIC")),
}
}
eprintln!(
"{label}: passed={passed} expected_bails={expected_bails} failures={}",
failures.len()
);
if !failures.is_empty() {
report_failures_and_panic(&failures);
}
}
fn report_failures_and_panic(failures: &[String]) -> ! {
for f in failures.iter().take(MAX_FAILURES_SHOWN) {
eprintln!(" {f}");
}
if failures.len() > MAX_FAILURES_SHOWN {
eprintln!(" ... and {} more", failures.len() - MAX_FAILURES_SHOWN);
}
panic!(
"{} structural fuzz iterations failed -- replay a single case with \
SPLICER_FUZZ_SEED=<iter_seed_from_output> SPLICER_FUZZ_ITERS=1",
failures.len()
);
}