Skip to content

Commit b0fcd6f

Browse files
committed
begin work on differential fuzzers
1 parent a91d953 commit b0fcd6f

File tree

2 files changed

+69
-0
lines changed

2 files changed

+69
-0
lines changed

fuzz/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ path = "fuzz_targets/ast_fuzz_regex.rs"
6767
name = "ast_fuzz_match_bytes"
6868
path = "fuzz_targets/ast_fuzz_match_bytes.rs"
6969

70+
[[bin]]
71+
name = "ast_diff_default"
72+
path = "fuzz_targets/ast_diff_default.rs"
73+
7074
[profile.release]
7175
opt-level = 3
7276
debug = true

fuzz/fuzz_targets/ast_diff_default.rs

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#![no_main]
2+
3+
use {
4+
libfuzzer_sys::{fuzz_target, Corpus},
5+
regex::RegexBuilder,
6+
regex_automata::nfa::thompson::pikevm::PikeVM as NfaRegex,
7+
regex_syntax::ast::Ast,
8+
};
9+
10+
#[derive(Eq, PartialEq, arbitrary::Arbitrary)]
11+
struct FuzzData {
12+
ast: Ast,
13+
haystack: String,
14+
}
15+
16+
impl std::fmt::Debug for FuzzData {
17+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18+
let mut builder = f.debug_struct("FuzzData");
19+
builder.field("ast", &format!("{}", self.ast));
20+
builder.field("haystack", &self.haystack);
21+
builder.finish()
22+
}
23+
}
24+
25+
fuzz_target!(|data: FuzzData| -> Corpus {
26+
let _ = env_logger::try_init();
27+
28+
let pattern = format!("{}", data.ast);
29+
let Ok(re) = RegexBuilder::new(&pattern).size_limit(1<<20).build() else {
30+
return Corpus::Reject;
31+
};
32+
let Ok(baseline) = NfaRegex::new(&pattern) else {
33+
return Corpus::Reject; // should we error here?
34+
};
35+
let mut cache = baseline.create_cache();
36+
37+
assert_eq!(
38+
re.is_match(&data.haystack),
39+
baseline.is_match(&mut cache, &data.haystack)
40+
);
41+
let found1 = re.find(&data.haystack);
42+
let found2 = baseline.find(&mut cache, &data.haystack);
43+
if let Some(found1) = found1 {
44+
let found2 = found2.expect("Found in target, but not in baseline!");
45+
assert_eq!(found1.start(), found2.start());
46+
assert_eq!(found1.end(), found2.end());
47+
}
48+
if let Some(captures) = re.captures(&data.haystack) {
49+
let mut baseline_captures = baseline.create_captures();
50+
51+
baseline.captures(&mut cache, &data.haystack, &mut baseline_captures);
52+
drop(cache);
53+
assert_eq!(captures.len(), baseline_captures.group_len());
54+
for (c1, c2) in captures.iter().zip(baseline_captures.iter()) {
55+
if let Some(c1) = c1 {
56+
let c2 = c2.expect("Matched in target, but not baseline!");
57+
assert_eq!(c1.start(), c2.start);
58+
assert_eq!(c1.end(), c2.end);
59+
} else {
60+
assert!(!c2.is_some(), "Matched in baseline, but not target!");
61+
}
62+
}
63+
}
64+
Corpus::Keep
65+
});

0 commit comments

Comments
 (0)