Skip to content

Commit 262b547

Browse files
Cut duplicated boilerplate from the seed generators and run.rs
1 parent f4ce3c9 commit 262b547

11 files changed

Lines changed: 365 additions & 928 deletions

‎fuzz/examples/gen_attach_seeds.rs‎

Lines changed: 18 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,15 @@
55
//! `corpus/findings/` instead, which nothing replays, and the search continues past it for a
66
//! clean representative.
77
8-
use std::{fs, path::Path};
8+
use std::path::Path;
99

10-
use arbitrary::{Arbitrary, Unstructured};
11-
use sqlite3mc_src_fuzz::{run_attach, AttachCase};
10+
use sqlite3mc_src_fuzz::{
11+
run_attach,
12+
seedgen::{run_search, DivergencePolicy, Family, Rng},
13+
AttachCase,
14+
};
1215

13-
/// A cheap deterministic byte stream, xorshift64*, fast enough for millions of draws.
14-
struct Rng(u64);
15-
16-
impl Rng {
17-
const fn next_u64(&mut self) -> u64 {
18-
let mut x = self.0;
19-
x ^= x << 13;
20-
x ^= x >> 7;
21-
x ^= x << 17;
22-
self.0 = x;
23-
x.wrapping_mul(0x2545_f491_4f6c_dd1d)
24-
}
25-
26-
fn bytes(&mut self, len: usize) -> Vec<u8> {
27-
let mut out = Vec::with_capacity(len);
28-
while out.len() < len {
29-
out.extend_from_slice(&self.next_u64().to_le_bytes());
30-
}
31-
out.truncate(len);
32-
out
33-
}
34-
}
35-
36-
/// One family to search for, named for the seed file, matched against the decoded case.
37-
struct Family {
38-
name: &'static str,
39-
matches: fn(&AttachCase) -> bool,
40-
}
41-
42-
fn families() -> &'static [Family] {
16+
fn families() -> &'static [Family<AttachCase>] {
4317
&[
4418
Family {
4519
name: "no-attached",
@@ -87,80 +61,19 @@ fn families() -> &'static [Family] {
8761
]
8862
}
8963

90-
/// Searches for a draw matching `family` that runs clean, saving it to `dir`. The first draw that
91-
/// matches but panics is saved once to `findings_dir` instead, and search continues past it for a
92-
/// clean representative of the family.
93-
fn search_family(
94-
family: &Family,
95-
rng: &mut Rng,
96-
dir: &Path,
97-
findings_dir: &Path,
98-
attempts: usize,
99-
) -> (bool, bool) {
100-
let mut saved_finding = false;
101-
for attempt in 0..attempts {
102-
// Vary the buffer length so arbitrary's Vec/Option draws see different amounts of
103-
// entropy; too short starves attached databases, too long wastes most attempts on decode
104-
// failures the length check below already prunes.
105-
let len = 64 + (attempt * 37) % 12000;
106-
let bytes = rng.bytes(len);
107-
let Ok(case) = AttachCase::arbitrary_take_rest(Unstructured::new(&bytes)) else {
108-
continue;
109-
};
110-
if !(family.matches)(&case) {
111-
continue;
112-
}
113-
let result = std::panic::catch_unwind(|| run_attach(&case));
114-
let Err(payload) = result else {
115-
fs::write(dir.join(family.name), &bytes).expect("corpus seed is writable");
116-
return (true, saved_finding);
117-
};
118-
if !saved_finding {
119-
let message = payload
120-
.downcast_ref::<String>()
121-
.map(String::as_str)
122-
.or_else(|| payload.downcast_ref::<&str>().copied())
123-
.unwrap_or("<non-string panic payload>");
124-
println!(
125-
"DIVERGENCE family={} case={case:#?}\n{message}",
126-
family.name
127-
);
128-
fs::write(findings_dir.join(format!("attach-{}", family.name)), &bytes)
129-
.expect("findings directory is writable");
130-
saved_finding = true;
131-
}
132-
}
133-
(false, saved_finding)
134-
}
135-
13664
fn main() {
13765
let dir = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/corpus/attach"));
138-
fs::create_dir_all(dir).expect("the corpus directory can be created");
13966
let findings_dir = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/corpus/findings"));
140-
fs::create_dir_all(findings_dir).expect("the findings directory can be created");
141-
142-
let mut rng = Rng(0x4154_4143_4154_4143);
143-
let attempts_per_family = 200_000usize;
144-
let mut saved = 0usize;
145-
let mut findings = 0usize;
146-
let families = families();
147-
148-
for family in families {
149-
let (found, divergence) =
150-
search_family(family, &mut rng, dir, findings_dir, attempts_per_family);
151-
findings += usize::from(divergence);
152-
if found {
153-
saved += 1;
154-
} else {
155-
println!(
156-
"MISS family={} (no clean match in {attempts_per_family} attempts)",
157-
family.name
158-
);
159-
}
160-
}
161-
162-
println!(
163-
"saved={saved} families={} findings={findings}",
164-
families.len()
67+
run_search(
68+
families(),
69+
Rng::new(0x4154_4143_4154_4143),
70+
dir,
71+
12000,
72+
200_000,
73+
run_attach,
74+
&DivergencePolicy::QuarantineTo {
75+
dir: findings_dir,
76+
prefix: "attach-",
77+
},
16578
);
16679
}

‎fuzz/examples/gen_backup_seeds.rs‎

Lines changed: 18 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,19 @@
55
//! `corpus/findings/` instead, which nothing replays, and the search continues past it for a
66
//! clean representative.
77
8-
use std::{fs, path::Path};
8+
use std::path::Path;
99

10-
use arbitrary::{Arbitrary, Unstructured};
11-
use sqlite3mc_src_fuzz::{run_backup, BackupCase, Cipher};
12-
13-
/// A cheap deterministic byte stream, xorshift64*, fast enough for millions of draws.
14-
struct Rng(u64);
15-
16-
impl Rng {
17-
const fn next_u64(&mut self) -> u64 {
18-
let mut x = self.0;
19-
x ^= x << 13;
20-
x ^= x >> 7;
21-
x ^= x << 17;
22-
self.0 = x;
23-
x.wrapping_mul(0x2545_f491_4f6c_dd1d)
24-
}
25-
26-
fn bytes(&mut self, len: usize) -> Vec<u8> {
27-
let mut out = Vec::with_capacity(len);
28-
while out.len() < len {
29-
out.extend_from_slice(&self.next_u64().to_le_bytes());
30-
}
31-
out.truncate(len);
32-
out
33-
}
34-
}
35-
36-
/// One family to search for, named for the seed file, matched against the decoded case.
37-
struct Family {
38-
name: &'static str,
39-
matches: fn(&BackupCase) -> bool,
40-
}
10+
use sqlite3mc_src_fuzz::{
11+
run_backup,
12+
seedgen::{run_search, DivergencePolicy, Family, Rng},
13+
BackupCase, Cipher,
14+
};
4115

4216
const fn has_content(case: &BackupCase) -> bool {
4317
!case.src_ops.is_empty()
4418
}
4519

46-
fn families() -> &'static [Family] {
20+
fn families() -> &'static [Family<BackupCase>] {
4721
&[
4822
Family {
4923
name: "same-cipher-matched-page-size",
@@ -94,80 +68,19 @@ fn families() -> &'static [Family] {
9468
]
9569
}
9670

97-
/// Searches for a draw matching `family` that runs clean, saving it to `dir`. The first draw that
98-
/// matches but panics is saved once to `findings_dir` instead, and search continues past it for a
99-
/// clean representative of the family.
100-
fn search_family(
101-
family: &Family,
102-
rng: &mut Rng,
103-
dir: &Path,
104-
findings_dir: &Path,
105-
attempts: usize,
106-
) -> (bool, bool) {
107-
let mut saved_finding = false;
108-
for attempt in 0..attempts {
109-
// Vary the buffer length so arbitrary's Vec/Option draws see different amounts of
110-
// entropy; too short starves ops, too long wastes most attempts on decode failures the
111-
// length check below already prunes.
112-
let len = 64 + (attempt * 37) % 12000;
113-
let bytes = rng.bytes(len);
114-
let Ok(case) = BackupCase::arbitrary_take_rest(Unstructured::new(&bytes)) else {
115-
continue;
116-
};
117-
if !(family.matches)(&case) {
118-
continue;
119-
}
120-
let result = std::panic::catch_unwind(|| run_backup(&case));
121-
let Err(payload) = result else {
122-
fs::write(dir.join(family.name), &bytes).expect("corpus seed is writable");
123-
return (true, saved_finding);
124-
};
125-
if !saved_finding {
126-
let message = payload
127-
.downcast_ref::<String>()
128-
.map(String::as_str)
129-
.or_else(|| payload.downcast_ref::<&str>().copied())
130-
.unwrap_or("<non-string panic payload>");
131-
println!(
132-
"DIVERGENCE family={} case={case:#?}\n{message}",
133-
family.name
134-
);
135-
fs::write(findings_dir.join(format!("backup-{}", family.name)), &bytes)
136-
.expect("findings directory is writable");
137-
saved_finding = true;
138-
}
139-
}
140-
(false, saved_finding)
141-
}
142-
14371
fn main() {
14472
let dir = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/corpus/backup"));
145-
fs::create_dir_all(dir).expect("the corpus directory can be created");
14673
let findings_dir = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/corpus/findings"));
147-
fs::create_dir_all(findings_dir).expect("the findings directory can be created");
148-
149-
let mut rng = Rng(0xBACC_0000_BACC_0001);
150-
let attempts_per_family = 400_000usize;
151-
let mut saved = 0usize;
152-
let mut findings = 0usize;
153-
let families = families();
154-
155-
for family in families {
156-
let (found, divergence) =
157-
search_family(family, &mut rng, dir, findings_dir, attempts_per_family);
158-
findings += usize::from(divergence);
159-
if found {
160-
saved += 1;
161-
} else {
162-
println!(
163-
"MISS family={} (no clean match in {attempts_per_family} attempts)",
164-
family.name
165-
);
166-
}
167-
}
168-
169-
println!(
170-
"saved={saved} families={} findings={findings}",
171-
families.len()
74+
run_search(
75+
families(),
76+
Rng::new(0xBACC_0000_BACC_0001),
77+
dir,
78+
12000,
79+
400_000,
80+
run_backup,
81+
&DivergencePolicy::QuarantineTo {
82+
dir: findings_dir,
83+
prefix: "backup-",
84+
},
17285
);
17386
}

0 commit comments

Comments
 (0)