ruff/fuzz/fuzz_targets/ruff_parse_idempotency.rs

55 lines
2.3 KiB
Rust

//! Fuzzer harness which searches for situations where the parser does not parse or unparse a
//! particular source snippet consistently.
#![no_main]
use libfuzzer_sys::{fuzz_target, Corpus};
use ruff_python_ast::source_code::round_trip;
use similar::TextDiff;
fn do_fuzz(case: &[u8]) -> Corpus {
let Ok(code) = std::str::from_utf8(case) else { return Corpus::Reject; };
// round trip it once to get a formatted version
if let Ok(first) = round_trip(code, "fuzzed-source.py") {
// round trip it a second time to get a case to compare against
if let Ok(second) = round_trip(&first, "fuzzed-source.py") {
if cfg!(feature = "full-idempotency") {
// potentially, we don't want to test for full idempotency, but just for unsteady states
// enable the "full-idempotency" feature when fuzzing for full idempotency
let diff = TextDiff::from_lines(&first, &second)
.unified_diff()
.header("Parsed once", "Parsed twice")
.to_string();
assert_eq!(
first, second,
"\nIdempotency violation (orig => first => second); original: {:?}\ndiff:\n{}",
code, diff
);
} else if first != second {
// by the third time we've round-tripped it, we shouldn't be introducing any more
// changes; if we do, then it's likely that we're in an unsteady parsing state
let third = round_trip(&second, "fuzzed-source.py")
.expect("Couldn't round-trip the processed source.");
let diff = TextDiff::from_lines(&second, &third)
.unified_diff()
.header("Parsed twice", "Parsed three times")
.to_string();
assert_eq!(
second, third,
"\nPotential unsteady state (orig => first => second => third); original: {:?}\ndiff:\n{}",
code, diff
);
}
} else {
panic!(
"Unable to perform the second round trip!\nbefore: {:?}\nfirst: {:?}",
code, first
);
}
}
Corpus::Keep
}
fuzz_target!(|case: &[u8]| -> Corpus { do_fuzz(case) });