1use mutatis::mutators as m;
3use mutatis::{Candidates, Context, DefaultMutate, Generate, Mutate, Result as MutResult};
4use smallvec::SmallVec;
5use std::ops::RangeInclusive;
6use wasm_encoder::{
7 CodeSection, ConstExpr, EntityType, ExportKind, ExportSection, Function, FunctionSection,
8 GlobalSection, ImportSection, Instruction, Module, RefType, TableSection, TableType,
9 TypeSection, ValType,
10};
11
12#[derive(Debug, Default)]
15pub struct TableOps {
16 pub(crate) num_params: u32,
17 pub(crate) num_globals: u32,
18 pub(crate) table_size: i32,
19 ops: Vec<TableOp>,
20}
21
22const NUM_PARAMS_RANGE: RangeInclusive<u32> = 0..=10;
23const NUM_GLOBALS_RANGE: RangeInclusive<u32> = 0..=10;
24const TABLE_SIZE_RANGE: RangeInclusive<i32> = 0..=100;
25const MAX_OPS: usize = 100;
26
27impl TableOps {
28 pub fn to_wasm_binary(&self) -> Vec<u8> {
40 let mut module = Module::new();
41
42 let mut types = TypeSection::new();
44
45 types.ty().function(
47 vec![],
48 vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
55 );
56
57 let mut params: Vec<ValType> = Vec::with_capacity(self.num_params as usize);
59 for _i in 0..self.num_params {
60 params.push(ValType::EXTERNREF);
61 }
62 let results = vec![];
63 types.ty().function(params, results);
64
65 types.ty().function(
67 vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
68 vec![],
69 );
70
71 types.ty().function(
73 vec![],
74 vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
75 );
76
77 let mut imports = ImportSection::new();
79 imports.import("", "gc", EntityType::Function(0));
80 imports.import("", "take_refs", EntityType::Function(2));
81 imports.import("", "make_refs", EntityType::Function(3));
82
83 let mut tables = TableSection::new();
85 tables.table(TableType {
86 element_type: RefType::EXTERNREF,
87 minimum: self.table_size as u64,
88 maximum: None,
89 table64: false,
90 shared: false,
91 });
92
93 let mut globals = GlobalSection::new();
95 for _ in 0..self.num_globals {
96 globals.global(
97 wasm_encoder::GlobalType {
98 val_type: wasm_encoder::ValType::EXTERNREF,
99 mutable: true,
100 shared: false,
101 },
102 &ConstExpr::ref_null(wasm_encoder::HeapType::EXTERN),
103 );
104 }
105
106 let mut functions = FunctionSection::new();
108 functions.function(1);
109
110 let mut exports = ExportSection::new();
111 exports.export("run", ExportKind::Func, 3);
112
113 let mut func = Function::new(vec![(1, ValType::EXTERNREF)]);
116
117 func.instruction(&Instruction::Loop(wasm_encoder::BlockType::Empty));
118 for op in &self.ops {
119 op.insert(&mut func, self.num_params);
120 }
121 func.instruction(&Instruction::Br(0));
122 func.instruction(&Instruction::End);
123 func.instruction(&Instruction::End);
124
125 let mut code = CodeSection::new();
126 code.function(&func);
127
128 module
129 .section(&types)
130 .section(&imports)
131 .section(&functions)
132 .section(&tables)
133 .section(&globals)
134 .section(&exports)
135 .section(&code);
136
137 module.finish()
138 }
139
140 pub fn abstract_stack_depth(&self, index: usize) -> usize {
142 debug_assert!(index <= self.ops.len());
143 let mut stack: usize = 0;
144 for op in self.ops.iter().take(index) {
145 let pop = op.operands_len();
146 let push = op.results_len();
147 stack = stack.saturating_sub(pop);
148 stack += push;
149 }
150 stack
151 }
152
153 fn fixup(&mut self, idx: usize, mut stack: usize) {
157 let mut new_ops = Vec::with_capacity(self.ops.len());
158 new_ops.extend_from_slice(&self.ops[..idx]);
159
160 new_ops.extend(self.ops[idx..].iter().copied().flat_map(|op| {
164 let mut temp = SmallVec::<[_; 4]>::new();
165
166 while stack < op.operands_len() {
167 temp.push(TableOp::Null());
168 stack += 1;
169 }
170
171 temp.push(op);
172 stack = stack - op.operands_len() + op.results_len();
173
174 temp
175 }));
176
177 for _ in 0..stack {
180 new_ops.push(TableOp::Drop());
181 }
182
183 self.ops = new_ops;
184 }
185}
186
187#[derive(Debug)]
189pub struct TableOpsMutator;
190
191impl Mutate<TableOps> for TableOpsMutator {
192 fn mutate(&mut self, c: &mut Candidates<'_>, ops: &mut TableOps) -> mutatis::Result<()> {
193 if !c.shrink() {
195 c.mutation(|ctx| {
196 if let Some(idx) = ctx.rng().gen_index(ops.ops.len() + 1) {
197 let stack = ops.abstract_stack_depth(idx);
198 let (op, _new_stack_size) = TableOp::generate(ctx, &ops, stack)?;
199 ops.ops.insert(idx, op);
200 ops.fixup(idx, stack);
201 }
202 Ok(())
203 })?;
204 }
205
206 if !ops.ops.is_empty() {
208 c.mutation(|ctx| {
209 let idx = ctx
210 .rng()
211 .gen_index(ops.ops.len())
212 .expect("ops is not empty");
213 let stack = ops.abstract_stack_depth(idx);
214 ops.ops.remove(idx);
215 ops.fixup(idx, stack);
216 Ok(())
217 })?;
218 }
219
220 Ok(())
221 }
222}
223
224impl DefaultMutate for TableOps {
225 type DefaultMutate = TableOpsMutator;
226}
227
228impl Default for TableOpsMutator {
229 fn default() -> Self {
230 TableOpsMutator
231 }
232}
233
234impl<'a> arbitrary::Arbitrary<'a> for TableOps {
235 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
236 let mut session = mutatis::Session::new().seed(u.arbitrary()?);
237 session
238 .generate()
239 .map_err(|_| arbitrary::Error::IncorrectFormat)
240 }
241}
242
243impl Generate<TableOps> for TableOpsMutator {
244 fn generate(&mut self, ctx: &mut Context) -> MutResult<TableOps> {
245 let num_params = m::range(NUM_PARAMS_RANGE).generate(ctx)?;
246 let num_globals = m::range(NUM_GLOBALS_RANGE).generate(ctx)?;
247 let table_size = m::range(TABLE_SIZE_RANGE).generate(ctx)?;
248
249 let mut ops = TableOps {
250 num_params,
251 num_globals,
252 table_size,
253 ops: vec![
254 TableOp::Null(),
255 TableOp::Drop(),
256 TableOp::Gc(),
257 TableOp::LocalSet(0),
258 TableOp::LocalGet(0),
259 TableOp::GlobalSet(0),
260 TableOp::GlobalGet(0),
261 ],
262 };
263
264 let mut stack: usize = 0;
265 while ops.ops.len() < MAX_OPS {
266 let (op, new_stack_len) = TableOp::generate(ctx, &ops, stack)?;
267 ops.ops.push(op);
268 stack = new_stack_len;
269 }
270
271 for _ in 0..stack {
273 ops.ops.push(TableOp::Drop());
274 }
275
276 Ok(ops)
277 }
278}
279
280macro_rules! define_table_ops {
281 (
282 $(
283 $op:ident $( ( $($limit:expr => $ty:ty),* ) )? : $params:expr => $results:expr ,
284 )*
285 ) => {
286 #[derive(Copy, Clone, Debug)]
287 pub(crate) enum TableOp {
288 $(
289 $op ( $( $($ty),* )? ),
290 )*
291 }
292 #[cfg(test)]
293 const OP_NAMES: &'static[&'static str] = &[
294 $(
295 stringify!($op),
296 )*
297 ];
298
299 impl TableOp {
300 #[cfg(test)]
301 fn name(&self) -> &'static str {
302 match self {
303 $(
304 Self::$op (..) => stringify!($op),
305 )*
306 }
307 }
308
309 pub fn operands_len(&self) -> usize {
310 match self {
311 $(
312 Self::$op (..) => $params,
313 )*
314 }
315 }
316
317 pub fn results_len(&self) -> usize {
318 match self {
319 $(
320 Self::$op (..) => $results,
321 )*
322 }
323 }
324 }
325
326 $(
327 #[allow(non_snake_case, reason = "macro-generated code")]
328 fn $op(
329 _ctx: &mut mutatis::Context,
330 _ops: &TableOps,
331 stack: usize,
332 ) -> mutatis::Result<(TableOp, usize)> {
333 #[allow(unused_comparisons, reason = "macro-generated code")]
334 {
335 debug_assert!(stack >= $params);
336 }
337
338 let op = TableOp::$op(
339 $($({
340 let limit_fn = $limit as fn(&TableOps) -> $ty;
341 let limit = (limit_fn)(_ops);
342 debug_assert!(limit > 0);
343 m::range(0..=limit - 1).generate(_ctx)?
344 })*)?
345 );
346 let new_stack = stack - $params + $results;
347 Ok((op, new_stack))
348 }
349 )*
350
351 impl TableOp {
352 fn generate(
353 ctx: &mut mutatis::Context,
354 ops: &TableOps,
355 stack: usize,
356 ) -> mutatis::Result<(TableOp, usize)> {
357 let mut valid_choices: Vec<
358 fn (&mut mutatis::Context, &TableOps, usize) -> mutatis::Result<(TableOp, usize)>
359 > = vec![];
360
361 $(
362 #[allow(unused_comparisons, reason = "macro-generated code")]
363 if stack >= $params $($(
364 && {
365 let limit_fn: fn(&TableOps) -> $ty = $limit;
366 let limit = (limit_fn)(ops);
367 limit > 0
368 }
369 )*)? {
370 valid_choices.push($op);
371 }
372 )*
373
374 let f = *ctx.rng()
375 .choose(&valid_choices)
376 .expect("should always have a valid op choice");
377
378 (f)(ctx, ops, stack)
379 }
380 }
381 };
382}
383
384define_table_ops! {
385 Gc : 0 => 3,
386
387 MakeRefs : 0 => 3,
388 TakeRefs : 3 => 0,
389
390 TableGet(|ops| ops.table_size + 1 => i32) : 0 => 1,
392 TableSet(|ops| ops.table_size + 1 => i32) : 1 => 0,
393
394 GlobalGet(|ops| ops.num_globals => u32) : 0 => 1,
395 GlobalSet(|ops| ops.num_globals => u32) : 1 => 0,
396
397 LocalGet(|ops| ops.num_params => u32) : 0 => 1,
398 LocalSet(|ops| ops.num_params => u32) : 1 => 0,
399
400 Drop : 1 => 0,
401
402 Null : 0 => 1,
403}
404
405impl TableOp {
406 fn insert(self, func: &mut Function, scratch_local: u32) {
407 let gc_func_idx = 0;
408 let take_refs_func_idx = 1;
409 let make_refs_func_idx = 2;
410
411 match self {
412 Self::Gc() => {
413 func.instruction(&Instruction::Call(gc_func_idx));
414 }
415 Self::MakeRefs() => {
416 func.instruction(&Instruction::Call(make_refs_func_idx));
417 }
418 Self::TakeRefs() => {
419 func.instruction(&Instruction::Call(take_refs_func_idx));
420 }
421 Self::TableGet(x) => {
422 func.instruction(&Instruction::I32Const(x));
423 func.instruction(&Instruction::TableGet(0));
424 }
425 Self::TableSet(x) => {
426 func.instruction(&Instruction::LocalSet(scratch_local));
427 func.instruction(&Instruction::I32Const(x));
428 func.instruction(&Instruction::LocalGet(scratch_local));
429 func.instruction(&Instruction::TableSet(0));
430 }
431 Self::GlobalGet(x) => {
432 func.instruction(&Instruction::GlobalGet(x));
433 }
434 Self::GlobalSet(x) => {
435 func.instruction(&Instruction::GlobalSet(x));
436 }
437 Self::LocalGet(x) => {
438 func.instruction(&Instruction::LocalGet(x));
439 }
440 Self::LocalSet(x) => {
441 func.instruction(&Instruction::LocalSet(x));
442 }
443 Self::Drop() => {
444 func.instruction(&Instruction::Drop);
445 }
446 Self::Null() => {
447 func.instruction(&Instruction::RefNull(wasm_encoder::HeapType::EXTERN));
448 }
449 }
450 }
451}
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456
457 fn empty_test_ops(num_params: u32, num_globals: u32, table_size: i32) -> TableOps {
459 TableOps {
460 num_params,
461 num_globals,
462 table_size,
463 ops: vec![],
464 }
465 }
466
467 fn test_ops(num_params: u32, num_globals: u32, table_size: i32) -> TableOps {
469 TableOps {
470 num_params,
471 num_globals,
472 table_size,
473 ops: vec![
474 TableOp::Null(),
475 TableOp::Drop(),
476 TableOp::Gc(),
477 TableOp::LocalSet(0),
478 TableOp::LocalGet(0),
479 TableOp::GlobalSet(0),
480 TableOp::GlobalGet(0),
481 ],
482 }
483 }
484
485 #[test]
486 fn mutate_table_ops_with_default_mutator() -> mutatis::Result<()> {
487 let _ = env_logger::try_init();
488 use mutatis::Session;
489 use wasmparser::Validator;
490 let mut res = test_ops(5, 5, 5);
491 let mut session = Session::new();
492
493 for _ in 0..1024 {
494 session.mutate(&mut res)?;
495 let wasm = res.to_wasm_binary();
496 let mut validator = Validator::new();
497 let wat = wasmprinter::print_bytes(&wasm).expect("[-] Failed .print_bytes(&wasm).");
498 let result = validator.validate_all(&wasm);
499 log::debug!("{wat}");
500 assert!(
501 result.is_ok(),
502 "\n[-] Invalid wat: {}\n\t\t==== Failed Wat ====\n{}",
503 result.err().expect("[-] Failed .err() in assert macro."),
504 wat
505 );
506 }
507 Ok(())
508 }
509
510 #[test]
511 fn every_op_generated() -> mutatis::Result<()> {
512 let _ = env_logger::try_init();
513 let mut unseen_ops: std::collections::HashSet<_> = OP_NAMES.iter().copied().collect();
514
515 let mut res = empty_test_ops(5, 5, 5);
516 let mut generator = TableOpsMutator;
517 let mut session = mutatis::Session::new();
518
519 'outer: for _ in 0..=1024 {
520 session.mutate_with(&mut generator, &mut res)?;
521 for op in &res.ops {
522 unseen_ops.remove(op.name());
523 if unseen_ops.is_empty() {
524 break 'outer;
525 }
526 }
527 }
528 assert!(unseen_ops.is_empty(), "Failed to generate {unseen_ops:?}");
529 Ok(())
530 }
531
532 #[test]
533 fn test_wat_string() -> mutatis::Result<()> {
534 let _ = env_logger::try_init();
535
536 let mut table_ops = test_ops(2, 2, 5);
537 table_ops.ops.extend([
538 TableOp::Null(),
539 TableOp::Drop(),
540 TableOp::Gc(),
541 TableOp::LocalSet(0),
542 TableOp::LocalGet(0),
543 TableOp::GlobalSet(0),
544 TableOp::GlobalGet(0),
545 TableOp::Null(),
546 TableOp::Drop(),
547 TableOp::Gc(),
548 TableOp::LocalSet(0),
549 TableOp::LocalGet(0),
550 TableOp::GlobalSet(0),
551 TableOp::GlobalGet(0),
552 TableOp::Null(),
553 TableOp::Drop(),
554 ]);
555 let wasm = table_ops.to_wasm_binary();
556 let wat = wasmprinter::print_bytes(&wasm).expect("Failed to convert to WAT");
557 let expected = r#"
558 (module
559 (type (;0;) (func (result externref externref externref)))
560 (type (;1;) (func (param externref externref)))
561 (type (;2;) (func (param externref externref externref)))
562 (type (;3;) (func (result externref externref externref)))
563 (import "" "gc" (func (;0;) (type 0)))
564 (import "" "take_refs" (func (;1;) (type 2)))
565 (import "" "make_refs" (func (;2;) (type 3)))
566 (table (;0;) 5 externref)
567 (global (;0;) (mut externref) ref.null extern)
568 (global (;1;) (mut externref) ref.null extern)
569 (export "run" (func 3))
570 (func (;3;) (type 1) (param externref externref)
571 (local externref)
572 loop ;; label = @1
573 ref.null extern
574 drop
575 call 0
576 local.set 0
577 local.get 0
578 global.set 0
579 global.get 0
580 ref.null extern
581 drop
582 call 0
583 local.set 0
584 local.get 0
585 global.set 0
586 global.get 0
587 ref.null extern
588 drop
589 call 0
590 local.set 0
591 local.get 0
592 global.set 0
593 global.get 0
594 ref.null extern
595 drop
596 br 0 (;@1;)
597 end
598 )
599 )
600 "#;
601
602 let generated = wat.split_whitespace().collect::<Vec<_>>().join(" ");
603 let expected = expected.split_whitespace().collect::<Vec<_>>().join(" ");
604 assert_eq!(generated, expected, "WAT does not match expected");
605
606 Ok(())
607 }
608}