wasmtime_fuzzing/generators/
table_ops.rs1use 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#[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 pub fn to_wasm_binary(&self) -> Vec<u8> {
39 let mut module = Module::new();
40
41 let mut types = TypeSection::new();
43
44 types.ty().function(
46 vec![],
47 vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
54 );
55
56 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 types.ty().function(
66 vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
67 vec![],
68 );
69
70 types.ty().function(
72 vec![],
73 vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
74 );
75
76 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 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 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 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 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 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 $(
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 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 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}