Coverage Report

Created: 2026-09-14 07:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wasmtime/fuzz/fuzz_targets/misc.rs
Line
Count
Source
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!(
18
        init: wasmtime_fuzzing::misc_init(),
19
        |bytes: &[u8]| {
20
            // Use the first byte of input as a discriminant of which fuzzer to
21
            // select.
22
            let Some((which_fuzzer, bytes)) = bytes.split_first() else {
23
                return;
24
            };
25
26
            // Lazily initialize this fuzzer in terms of logging as well as
27
            // enabled fuzzers via the `FUZZER` env var. This creates a bitmask
28
            // inside of `ENABLED` of enabled fuzzers, returned here as
29
            // `enabled`.
30
1
            let enabled = *ENABLED.get_or_init(|| {
31
1
                let configured = std::env::var("FUZZER").ok();
32
1
                let configured = configured.as_deref();
33
1
                let mut enabled = 0;
34
1
                let mut index = 0;
35
36
                $(
37
1
                    if configured.is_none() || configured == Some(stringify!($fuzzer)) {
38
1
                        enabled |= 1 << index;
39
1
                    }
40
1
                    index += 1;
41
                )*
42
1
                let _ = index;
43
44
1
                enabled
45
1
            });
46
47
            // Generate a linear check for each fuzzer. Only run each fuzzer if
48
            // the fuzzer is enabled, and also only if the `which_fuzzer`
49
            // discriminant matches the fuzzer being run.
50
            //
51
            // Note that it's a bit wonky here due to rust macros.
52
            let mut index = 0;
53
            $(
54
                if enabled & (1 << index) != 0 && *which_fuzzer == index {
55
                    let _: Result<()> = $fuzzer(Unstructured::new(bytes));
56
                }
57
                index += 1;
58
            )*
59
            let _ = index;
60
        });
61
    };
62
}
63
64
run_fuzzers! {
65
    pulley_roundtrip
66
    assembler_roundtrip
67
    memory_accesses
68
    stacks
69
    api_calls
70
    dominator_tree
71
    gc_access
72
    assembler_roundtrip_xed
73
}
74
75
4.65k
fn pulley_roundtrip(u: Unstructured<'_>) -> Result<()> {
76
4.65k
    pulley_interpreter_fuzz::roundtrip(Arbitrary::arbitrary_take_rest(u)?);
77
4.65k
    Ok(())
78
4.65k
}
79
80
5.29k
fn assembler_roundtrip(u: Unstructured<'_>) -> Result<()> {
81
    use cranelift_assembler_x64::{Inst, fuzz};
82
5.29k
    let inst: Inst<fuzz::FuzzRegs> = Arbitrary::arbitrary_take_rest(u)?;
83
4.99k
    fuzz::roundtrip(&inst);
84
4.99k
    Ok(())
85
5.29k
}
86
87
/// Same as [`assembler_roundtrip`], but checks the assembler against Intel XED
88
/// rather than Capstone. XED understands newer encodings (e.g. APX) that the
89
/// bundled Capstone cannot decode.
90
///
91
/// Building XED requires a C compiler and Python, so this is gated behind the
92
/// `fuzz-xed` feature and is a no-op without it. The fuzzer is always listed in
93
/// `run_fuzzers!` regardless so that the input-byte discriminants of the other
94
/// fuzzers stay stable.
95
0
fn assembler_roundtrip_xed(u: Unstructured<'_>) -> Result<()> {
96
    #[cfg(all(feature = "fuzz-xed", target_arch = "x86_64", target_os = "linux"))]
97
    {
98
        use cranelift_assembler_x64::{Inst, fuzz};
99
        let inst: Inst<fuzz::FuzzRegs> = Arbitrary::arbitrary_take_rest(u)?;
100
        fuzz::roundtrip_xed(&inst);
101
    }
102
    #[cfg(not(all(feature = "fuzz-xed", target_arch = "x86_64", target_os = "linux")))]
103
0
    let _ = u;
104
0
    Ok(())
105
0
}
106
107
744
fn memory_accesses(u: Unstructured<'_>) -> Result<()> {
108
744
    wasmtime_fuzzing::oracles::memory::check_memory_accesses(Arbitrary::arbitrary_take_rest(u)?);
109
669
    Ok(())
110
744
}
111
112
2.18k
fn stacks(u: Unstructured<'_>) -> Result<()> {
113
2.18k
    wasmtime_fuzzing::oracles::check_stacks(Arbitrary::arbitrary_take_rest(u)?);
114
2.18k
    Ok(())
115
2.18k
}
116
117
3.44k
fn api_calls(u: Unstructured<'_>) -> Result<()> {
118
3.44k
    wasmtime_fuzzing::oracles::make_api_calls(Arbitrary::arbitrary_take_rest(u)?);
119
3.43k
    Ok(())
120
3.44k
}
121
122
114
fn dominator_tree(mut data: Unstructured<'_>) -> Result<()> {
123
    use cranelift_codegen::cursor::{Cursor, FuncCursor};
124
    use cranelift_codegen::dominator_tree::{DominatorTree, SimpleDominatorTree};
125
    use cranelift_codegen::flowgraph::ControlFlowGraph;
126
    use cranelift_codegen::ir::{
127
        Block, BlockCall, Function, InstBuilder, JumpTableData, Value, types::I32,
128
    };
129
    use std::collections::HashMap;
130
131
    const MAX_BLOCKS: u16 = 1 << 12;
132
133
114
    let mut func = Function::new();
134
135
114
    let mut num_to_block = Vec::new();
136
137
114
    let mut cfg = HashMap::<Block, Vec<Block>>::new();
138
139
3.78k
    for edge in data.arbitrary_iter::<(u16, u16)>()? {
140
3.78k
        let (a, b) = edge?;
141
142
3.78k
        let a = a % MAX_BLOCKS;
143
3.78k
        let b = b % MAX_BLOCKS;
144
145
270k
        while a >= num_to_block.len() as u16 {
146
266k
            num_to_block.push(func.dfg.make_block());
147
266k
        }
148
149
3.78k
        let a = num_to_block[a as usize];
150
151
87.2k
        while b >= num_to_block.len() as u16 {
152
83.4k
            num_to_block.push(func.dfg.make_block());
153
83.4k
        }
154
155
3.78k
        let b = num_to_block[b as usize];
156
157
3.78k
        cfg.entry(a).or_default().push(b);
158
    }
159
160
114
    let mut cursor = FuncCursor::new(&mut func);
161
162
114
    let mut v0: Option<Value> = None;
163
164
350k
    for block in num_to_block {
165
350k
        cursor.insert_block(block);
166
167
350k
        if v0.is_none() {
168
113
            v0 = Some(cursor.ins().iconst(I32, 0));
169
350k
        }
170
171
350k
        if let Some(children) = cfg.get(&block) {
172
535
            if children.len() == 1 {
173
346
                cursor.ins().jump(children[0], &[]);
174
346
            } else {
175
189
                let block_calls = children
176
189
                    .iter()
177
3.43k
                    .map(|&block| {
178
3.43k
                        BlockCall::new(block, core::iter::empty(), &mut cursor.func.dfg.value_lists)
179
3.43k
                    })
180
189
                    .collect::<Vec<_>>();
181
182
189
                let data = JumpTableData::new(block_calls[0], &block_calls[1..]);
183
189
                let jt = cursor.func.create_jump_table(data);
184
189
                cursor.ins().br_table(v0.unwrap(), jt);
185
            }
186
349k
        } else {
187
349k
            cursor.ins().return_(&[]);
188
349k
        }
189
    }
190
191
114
    let cfg = ControlFlowGraph::with_function(&func);
192
114
    let domtree = DominatorTree::with_function(&func, &cfg);
193
114
    let expected_domtree = SimpleDominatorTree::with_function(&func, &cfg);
194
195
350k
    for block in func.layout.blocks() {
196
350k
        let expected = expected_domtree.idom(block);
197
350k
        let got = domtree.idom(block);
198
350k
        if expected != got {
199
0
            panic!("Expected dominator for {block} is {expected:?}, got {got:?}");
200
350k
        }
201
    }
202
203
114
    Ok(())
204
114
}
205
206
422
fn gc_access(mut u: Unstructured<'_>) -> Result<()> {
207
422
    let config: wasmtime_fuzzing::generators::Config = u.arbitrary()?;
208
408
    let input: wasmtime_fuzzing::generators::GcAccess = u.arbitrary()?;
209
408
    wasmtime_fuzzing::oracles::gc_access(config, input);
210
408
    Ok(())
211
422
}