wasmtime_fuzzing/generators/
table_ops.rs

1//! Generating series of `table.get` and `table.set` operations.
2
3use arbitrary::{Arbitrary, Result, Unstructured};
4use std::ops::RangeInclusive;
5use wasm_encoder::{
6    CodeSection, ConstExpr, EntityType, ExportKind, ExportSection, Function, FunctionSection,
7    GlobalSection, ImportSection, Instruction, Module, RefType, TableSection, TableType,
8    TypeSection, ValType,
9};
10
11/// A description of a Wasm module that makes a series of `externref` table
12/// operations.
13#[derive(Debug)]
14pub struct TableOps {
15    pub(crate) num_params: u32,
16    pub(crate) num_globals: u32,
17    pub(crate) table_size: i32,
18    ops: Vec<TableOp>,
19}
20
21const NUM_PARAMS_RANGE: RangeInclusive<u32> = 0..=10;
22const NUM_GLOBALS_RANGE: RangeInclusive<u32> = 0..=10;
23const TABLE_SIZE_RANGE: RangeInclusive<i32> = 0..=100;
24const MAX_OPS: usize = 100;
25
26impl TableOps {
27    /// Serialize this module into a Wasm binary.
28    ///
29    /// The module requires several function imports. See this function's
30    /// implementation for their exact types.
31    ///
32    /// The single export of the module is a function "run" that takes
33    /// `self.num_params` parameters of type `externref`.
34    ///
35    /// The "run" function does not terminate; you should run it with limited
36    /// fuel. It also is not guaranteed to avoid traps: it may access
37    /// out-of-bounds of the table.
38    pub fn to_wasm_binary(&self) -> Vec<u8> {
39        let mut module = Module::new();
40
41        // Encode the types for all functions that we are using.
42        let mut types = TypeSection::new();
43
44        // 0: "gc"
45        types.ty().function(
46            vec![],
47            // Return a bunch of stuff from `gc` so that we exercise GCing when
48            // there is return pointer space allocated on the stack. This is
49            // especially important because the x64 backend currently
50            // dynamically adjusts the stack pointer for each call that uses
51            // return pointers rather than statically allocating space in the
52            // stack frame.
53            vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
54        );
55
56        // 1: "run"
57        let mut params: Vec<ValType> = Vec::with_capacity(self.num_params as usize);
58        for _i in 0..self.num_params {
59            params.push(ValType::EXTERNREF);
60        }
61        let results = vec![];
62        types.ty().function(params, results);
63
64        // 2: `take_refs`
65        types.ty().function(
66            vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
67            vec![],
68        );
69
70        // 3: `make_refs`
71        types.ty().function(
72            vec![],
73            vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
74        );
75
76        // Import the GC function.
77        let mut imports = ImportSection::new();
78        imports.import("", "gc", EntityType::Function(0));
79        imports.import("", "take_refs", EntityType::Function(2));
80        imports.import("", "make_refs", EntityType::Function(3));
81
82        // Define our table.
83        let mut tables = TableSection::new();
84        tables.table(TableType {
85            element_type: RefType::EXTERNREF,
86            minimum: self.table_size as u64,
87            maximum: None,
88            table64: false,
89            shared: false,
90        });
91
92        // Define our globals.
93        let mut globals = GlobalSection::new();
94        for _ in 0..self.num_globals {
95            globals.global(
96                wasm_encoder::GlobalType {
97                    val_type: wasm_encoder::ValType::EXTERNREF,
98                    mutable: true,
99                    shared: false,
100                },
101                &ConstExpr::ref_null(wasm_encoder::HeapType::EXTERN),
102            );
103        }
104
105        // Define the "run" function export.
106        let mut functions = FunctionSection::new();
107        functions.function(1);
108
109        let mut exports = ExportSection::new();
110        exports.export("run", ExportKind::Func, 3);
111
112        // Give ourselves one scratch local that we can use in various `TableOp`
113        // implementations.
114        let mut func = Function::new(vec![(1, ValType::EXTERNREF)]);
115
116        func.instruction(&Instruction::Loop(wasm_encoder::BlockType::Empty));
117        for op in &self.ops {
118            op.insert(&mut func, self.num_params);
119        }
120        func.instruction(&Instruction::Br(0));
121        func.instruction(&Instruction::End);
122        func.instruction(&Instruction::End);
123
124        let mut code = CodeSection::new();
125        code.function(&func);
126
127        module
128            .section(&types)
129            .section(&imports)
130            .section(&functions)
131            .section(&tables)
132            .section(&globals)
133            .section(&exports)
134            .section(&code);
135
136        module.finish()
137    }
138}
139
140impl<'a> Arbitrary<'a> for TableOps {
141    fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self> {
142        let mut result = TableOps {
143            num_params: u.int_in_range(NUM_PARAMS_RANGE)?,
144            num_globals: u.int_in_range(NUM_GLOBALS_RANGE)?,
145            table_size: u.int_in_range(TABLE_SIZE_RANGE)?,
146            ops: Vec::new(),
147        };
148
149        let mut stack = 0;
150        let mut choices = vec![];
151        while result.ops.len() < MAX_OPS && !u.is_empty() {
152            add_table_op(&mut result, u, &mut stack, &mut choices)?;
153        }
154
155        // Drop any extant refs on the stack.
156        for _ in 0..stack {
157            result.ops.push(TableOp::Drop);
158        }
159
160        Ok(result)
161    }
162}
163
164macro_rules! define_table_ops {
165	(
166        $(
167            $op:ident $( ( $($limit:expr => $ty:ty),* ) )? : $params:expr => $results:expr ,
168        )*
169    ) => {
170        #[derive(Copy, Clone, Debug)]
171        pub(crate) enum TableOp {
172            $(
173                $op $( ( $($ty),* ) )? ,
174            )*
175        }
176
177        #[expect(unused_comparisons, reason = "macro-generated code")]
178        fn add_table_op(
179            ops: &mut TableOps,
180            u: &mut Unstructured,
181            stack: &mut u32,
182            choices: &mut Vec<fn(&TableOps, &mut Unstructured, &mut u32) -> Result<TableOp>>,
183        ) -> Result<()> {
184            choices.clear();
185
186            // Add all the choices of valid `TableOp`s we could generate.
187            $(
188                if $( $(($limit as fn(&TableOps) -> $ty)(&*ops) > 0 &&)* )? *stack >= $params {
189                    choices.push(|_ops, _u, stack| {
190                        *stack = *stack - $params + $results;
191                        Ok(TableOp::$op $( ( $(_u.int_in_range(0..=($limit as fn(&TableOps) -> $ty)(_ops) - 1)?),* ) )? )
192                    });
193                }
194            )*
195
196            // Choose a table op to insert.
197            let f = u.choose(&choices)?;
198            let v = f(ops, u, stack)?;
199            Ok(ops.ops.push(v))
200        }
201	};
202}
203
204define_table_ops! {
205    Gc : 0 => 3,
206
207    MakeRefs : 0 => 3,
208    TakeRefs : 3 => 0,
209
210    // Add one to make sure that out of bounds table accesses are possible, but still rare.
211    TableGet(|ops| ops.table_size + 1 => i32) : 0 => 1,
212    TableSet(|ops| ops.table_size + 1 => i32) : 1 => 0,
213
214    GlobalGet(|ops| ops.num_globals => u32) : 0 => 1,
215    GlobalSet(|ops| ops.num_globals => u32) : 1 => 0,
216
217    LocalGet(|ops| ops.num_params => u32) : 0 => 1,
218    LocalSet(|ops| ops.num_params => u32) : 1 => 0,
219
220    Drop : 1 => 0,
221
222    Null : 0 => 1,
223}
224
225impl TableOp {
226    fn insert(self, func: &mut Function, scratch_local: u32) {
227        let gc_func_idx = 0;
228        let take_refs_func_idx = 1;
229        let make_refs_func_idx = 2;
230
231        match self {
232            Self::Gc => {
233                func.instruction(&Instruction::Call(gc_func_idx));
234            }
235            Self::MakeRefs => {
236                func.instruction(&Instruction::Call(make_refs_func_idx));
237            }
238            Self::TakeRefs => {
239                func.instruction(&Instruction::Call(take_refs_func_idx));
240            }
241            Self::TableGet(x) => {
242                func.instruction(&Instruction::I32Const(x));
243                func.instruction(&Instruction::TableGet(0));
244            }
245            Self::TableSet(x) => {
246                func.instruction(&Instruction::LocalSet(scratch_local));
247                func.instruction(&Instruction::I32Const(x));
248                func.instruction(&Instruction::LocalGet(scratch_local));
249                func.instruction(&Instruction::TableSet(0));
250            }
251            Self::GlobalGet(x) => {
252                func.instruction(&Instruction::GlobalGet(x));
253            }
254            Self::GlobalSet(x) => {
255                func.instruction(&Instruction::GlobalSet(x));
256            }
257            Self::LocalGet(x) => {
258                func.instruction(&Instruction::LocalGet(x));
259            }
260            Self::LocalSet(x) => {
261                func.instruction(&Instruction::LocalSet(x));
262            }
263            Self::Drop => {
264                func.instruction(&Instruction::Drop);
265            }
266            Self::Null => {
267                func.instruction(&Instruction::RefNull(wasm_encoder::HeapType::EXTERN));
268            }
269        }
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276    use rand::rngs::SmallRng;
277    use rand::{RngCore, SeedableRng};
278
279    #[test]
280    fn test_valid() {
281        let mut rng = SmallRng::seed_from_u64(0);
282        let mut buf = vec![0; 2048];
283        for _ in 0..1024 {
284            rng.fill_bytes(&mut buf);
285            let u = Unstructured::new(&buf);
286            if let Ok(ops) = TableOps::arbitrary_take_rest(u) {
287                let wasm = ops.to_wasm_binary();
288                let mut validator = wasmparser::Validator::new();
289                let result = validator.validate_all(&wasm);
290                assert!(result.is_ok());
291            }
292        }
293    }
294
295    #[test]
296    fn test_wat_string() {
297        let ops = TableOps {
298            num_params: 10,
299            num_globals: 10,
300            table_size: 20,
301            ops: vec![
302                TableOp::Gc,
303                TableOp::MakeRefs,
304                TableOp::TakeRefs,
305                TableOp::TableGet(0),
306                TableOp::TableSet(1),
307                TableOp::GlobalGet(2),
308                TableOp::GlobalSet(3),
309                TableOp::LocalGet(4),
310                TableOp::LocalSet(5),
311                TableOp::Drop,
312                TableOp::Null,
313            ],
314        };
315
316        let expected = r#"
317(module
318  (type (;0;) (func (result externref externref externref)))
319  (type (;1;) (func (param externref externref externref externref externref externref externref externref externref externref)))
320  (type (;2;) (func (param externref externref externref)))
321  (type (;3;) (func (result externref externref externref)))
322  (import "" "gc" (func (;0;) (type 0)))
323  (import "" "take_refs" (func (;1;) (type 2)))
324  (import "" "make_refs" (func (;2;) (type 3)))
325  (table (;0;) 20 externref)
326  (global (;0;) (mut externref) ref.null extern)
327  (global (;1;) (mut externref) ref.null extern)
328  (global (;2;) (mut externref) ref.null extern)
329  (global (;3;) (mut externref) ref.null extern)
330  (global (;4;) (mut externref) ref.null extern)
331  (global (;5;) (mut externref) ref.null extern)
332  (global (;6;) (mut externref) ref.null extern)
333  (global (;7;) (mut externref) ref.null extern)
334  (global (;8;) (mut externref) ref.null extern)
335  (global (;9;) (mut externref) ref.null extern)
336  (export "run" (func 3))
337  (func (;3;) (type 1) (param externref externref externref externref externref externref externref externref externref externref)
338    (local externref)
339    loop ;; label = @1
340      call 0
341      call 2
342      call 1
343      i32.const 0
344      table.get 0
345      local.set 10
346      i32.const 1
347      local.get 10
348      table.set 0
349      global.get 2
350      global.set 3
351      local.get 4
352      local.set 5
353      drop
354      ref.null extern
355      br 0 (;@1;)
356    end
357  )
358)
359"#;
360        eprintln!("expected WAT = {expected}");
361
362        let actual = ops.to_wasm_binary();
363        if let Err(e) = wasmparser::validate(&actual) {
364            panic!("TableOps should generate valid Wasm; got error: {e}");
365        }
366
367        let actual = wasmprinter::print_bytes(&actual).unwrap();
368        eprintln!("actual WAT = {actual}");
369
370        assert_eq!(actual.trim(), expected.trim());
371    }
372}