Skip to content

Commit 840f544

Browse files
authored
Consolidate some fuzzers into one (#10385)
This commit consolidates a number of fuzzers that we have in Wasmtime into a single fuzzer. This follows the pattern of [wasm-tools's `run` fuzzer][tools] where the first byte in the fuzz input says which fuzzer to run and then the rest of the input is the input to the fuzzer. This is intended to improve our time-slicing on OSS-Fuzz where less important fuzzers don't hog time from the "important fuzzers" such as `differential`. Some minor-ish fuzzers have been folded in to this new fuzzer, but we can also always move things around as needed [tools]: https://github.com/bytecodealliance/wasm-tools/blob/main/fuzz/fuzz_targets/run.rs
1 parent 2c615b2 commit 840f544

File tree

8 files changed

+180
-194
lines changed

8 files changed

+180
-194
lines changed

fuzz/Cargo.toml

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,6 @@ path = "fuzz_targets/instantiate.rs"
5757
test = false
5858
doc = false
5959

60-
[[bin]]
61-
name = "api_calls"
62-
path = "fuzz_targets/api_calls.rs"
63-
test = false
64-
doc = false
65-
6660
[[bin]]
6761
name = "differential"
6862
path = "fuzz_targets/differential.rs"
@@ -75,18 +69,6 @@ path = "fuzz_targets/wast_tests.rs"
7569
test = false
7670
doc = false
7771

78-
[[bin]]
79-
name = "table_ops"
80-
path = "fuzz_targets/table_ops.rs"
81-
test = false
82-
doc = false
83-
84-
[[bin]]
85-
name = "stacks"
86-
path = "fuzz_targets/stacks.rs"
87-
test = false
88-
doc = false
89-
9072
[[bin]]
9173
name = "cranelift-fuzzgen"
9274
path = "fuzz_targets/cranelift-fuzzgen.rs"
@@ -118,22 +100,8 @@ test = false
118100
doc = false
119101

120102
[[bin]]
121-
name = "memory_accesses"
122-
path = "fuzz_targets/memory_accesses.rs"
123-
test = false
124-
doc = false
125-
bench = false
126-
127-
[[bin]]
128-
name = "pulley"
129-
path = "fuzz_targets/pulley.rs"
130-
test = false
131-
doc = false
132-
bench = false
133-
134-
[[bin]]
135-
name = "dominator_tree"
136-
path = "fuzz_targets/dominator_tree.rs"
103+
name = "misc"
104+
path = "fuzz_targets/misc.rs"
137105
test = false
138106
doc = false
139107
bench = false

fuzz/fuzz_targets/api_calls.rs

Lines changed: 0 additions & 8 deletions
This file was deleted.

fuzz/fuzz_targets/dominator_tree.rs

Lines changed: 0 additions & 105 deletions
This file was deleted.

fuzz/fuzz_targets/memory_accesses.rs

Lines changed: 0 additions & 8 deletions
This file was deleted.

fuzz/fuzz_targets/misc.rs

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#![no_main]
2+
3+
use libfuzzer_sys::arbitrary::{Arbitrary, Result, Unstructured};
4+
use libfuzzer_sys::fuzz_target;
5+
use std::sync::OnceLock;
6+
7+
// Helper macro which takes a static list of fuzzers as input which are then
8+
// delegated to internally based on the fuzz target selected.
9+
//
10+
// In general this fuzz target will execute a number of fuzzers all with the
11+
// same input. The `FUZZER` environment variable can be used to forcibly disable
12+
// all but one.
13+
macro_rules! run_fuzzers {
14+
($($fuzzer:ident)*) => {
15+
static ENABLED: OnceLock<u32> = OnceLock::new();
16+
17+
fuzz_target!(|bytes: &[u8]| {
18+
// Use the first byte of input as a discriminant of which fuzzer to
19+
// select.
20+
let Some((which_fuzzer, bytes)) = bytes.split_first() else {
21+
return;
22+
};
23+
24+
// Lazily initialize this fuzzer in terms of logging as well as
25+
// enabled fuzzers via the `FUZZER` env var. This creates a bitmask
26+
// inside of `ENABLED` of enabled fuzzers, returned here as
27+
// `enabled`.
28+
let enabled = *ENABLED.get_or_init(|| {
29+
env_logger::init();
30+
let configured = std::env::var("FUZZER").ok();
31+
let configured = configured.as_deref();
32+
let mut enabled = 0;
33+
let mut index = 0;
34+
35+
$(
36+
if configured.is_none() || configured == Some(stringify!($fuzzer)) {
37+
enabled |= 1 << index;
38+
}
39+
index += 1;
40+
)*
41+
let _ = index;
42+
43+
enabled
44+
});
45+
46+
// Generate a linear check for each fuzzer. Only run each fuzzer if
47+
// the fuzzer is enabled, and also only if the `which_fuzzer`
48+
// discriminant matches the fuzzer being run.
49+
//
50+
// Note that it's a bit wonky here due to rust macros.
51+
let mut index = 0;
52+
$(
53+
if enabled & (1 << index) != 0 && *which_fuzzer == index {
54+
let _: Result<()> = $fuzzer(Unstructured::new(bytes));
55+
}
56+
index += 1;
57+
)*
58+
let _ = index;
59+
});
60+
};
61+
}
62+
63+
run_fuzzers! {
64+
pulley_roundtrip
65+
memory_accesses
66+
table_ops
67+
stacks
68+
api_calls
69+
dominator_tree
70+
}
71+
72+
fn pulley_roundtrip(u: Unstructured<'_>) -> Result<()> {
73+
pulley_interpreter_fuzz::roundtrip(Arbitrary::arbitrary_take_rest(u)?);
74+
Ok(())
75+
}
76+
77+
fn memory_accesses(u: Unstructured<'_>) -> Result<()> {
78+
wasmtime_fuzzing::oracles::memory::check_memory_accesses(Arbitrary::arbitrary_take_rest(u)?);
79+
Ok(())
80+
}
81+
82+
fn table_ops(u: Unstructured<'_>) -> Result<()> {
83+
let (config, ops) = Arbitrary::arbitrary_take_rest(u)?;
84+
let _ = wasmtime_fuzzing::oracles::table_ops(config, ops);
85+
Ok(())
86+
}
87+
88+
fn stacks(u: Unstructured<'_>) -> Result<()> {
89+
wasmtime_fuzzing::oracles::check_stacks(Arbitrary::arbitrary_take_rest(u)?);
90+
Ok(())
91+
}
92+
93+
fn api_calls(u: Unstructured<'_>) -> Result<()> {
94+
wasmtime_fuzzing::oracles::make_api_calls(Arbitrary::arbitrary_take_rest(u)?);
95+
Ok(())
96+
}
97+
98+
fn dominator_tree(mut data: Unstructured<'_>) -> Result<()> {
99+
use cranelift_codegen::cursor::{Cursor, FuncCursor};
100+
use cranelift_codegen::dominator_tree::{DominatorTree, SimpleDominatorTree};
101+
use cranelift_codegen::flowgraph::ControlFlowGraph;
102+
use cranelift_codegen::ir::{
103+
types::I32, Block, BlockCall, Function, InstBuilder, JumpTableData, Value,
104+
};
105+
use std::collections::HashMap;
106+
107+
const MAX_BLOCKS: u16 = 1 << 12;
108+
109+
let mut func = Function::new();
110+
111+
let mut num_to_block = Vec::new();
112+
113+
let mut cfg = HashMap::<Block, Vec<Block>>::new();
114+
115+
for edge in data.arbitrary_iter::<(u16, u16)>()? {
116+
let (a, b) = edge?;
117+
118+
let a = a % MAX_BLOCKS;
119+
let b = b % MAX_BLOCKS;
120+
121+
while a >= num_to_block.len() as u16 {
122+
num_to_block.push(func.dfg.make_block());
123+
}
124+
125+
let a = num_to_block[a as usize];
126+
127+
while b >= num_to_block.len() as u16 {
128+
num_to_block.push(func.dfg.make_block());
129+
}
130+
131+
let b = num_to_block[b as usize];
132+
133+
cfg.entry(a).or_default().push(b);
134+
}
135+
136+
let mut cursor = FuncCursor::new(&mut func);
137+
138+
let mut v0: Option<Value> = None;
139+
140+
for block in num_to_block {
141+
cursor.insert_block(block);
142+
143+
if v0.is_none() {
144+
v0 = Some(cursor.ins().iconst(I32, 0));
145+
}
146+
147+
if let Some(children) = cfg.get(&block) {
148+
if children.len() == 1 {
149+
cursor.ins().jump(children[0], &[]);
150+
} else {
151+
let block_calls = children
152+
.iter()
153+
.map(|&block| BlockCall::new(block, &[], &mut cursor.func.dfg.value_lists))
154+
.collect::<Vec<_>>();
155+
156+
let data = JumpTableData::new(block_calls[0], &block_calls[1..]);
157+
let jt = cursor.func.create_jump_table(data);
158+
cursor.ins().br_table(v0.unwrap(), jt);
159+
}
160+
} else {
161+
cursor.ins().return_(&[]);
162+
}
163+
}
164+
165+
let cfg = ControlFlowGraph::with_function(&func);
166+
let domtree = DominatorTree::with_function(&func, &cfg);
167+
let expected_domtree = SimpleDominatorTree::with_function(&func, &cfg);
168+
169+
for block in func.layout.blocks() {
170+
let expected = expected_domtree.idom(block);
171+
let got = domtree.idom(block);
172+
if expected != got {
173+
panic!("Expected dominator for {block} is {expected:?}, got {got:?}");
174+
}
175+
}
176+
177+
Ok(())
178+
}

fuzz/fuzz_targets/pulley.rs

Lines changed: 0 additions & 20 deletions
This file was deleted.

fuzz/fuzz_targets/stacks.rs

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)