1 //! Operations for the `gc` operations.
2 
3 use crate::generators::gc_ops::{
4     limits::GcOpsLimits,
5     types::{CompositeType, RecGroupId, StructType, TypeId, Types},
6 };
7 use mutatis::{Context, Generate, mutators as m};
8 use serde::{Deserialize, Serialize};
9 use smallvec::SmallVec;
10 use std::collections::BTreeMap;
11 use wasm_encoder::{
12     CodeSection, ConstExpr, EntityType, ExportKind, ExportSection, Function, FunctionSection,
13     GlobalSection, ImportSection, Instruction, Module, RefType, TableSection, TableType,
14     TypeSection, ValType,
15 };
16 
17 /// A description of a Wasm module that makes a series of `externref` table
18 /// operations.
19 #[derive(Debug, Default, Serialize, Deserialize)]
20 pub struct GcOps {
21     pub(crate) limits: GcOpsLimits,
22     pub(crate) ops: Vec<GcOp>,
23     pub(crate) types: Types,
24 }
25 
26 impl GcOps {
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(&mut self) -> Vec<u8> {
39         self.fixup();
40 
41         let mut module = Module::new();
42 
43         // Encode the types for all functions that we are using.
44         let mut types = TypeSection::new();
45 
46         // 0: "gc"
47         types.ty().function(
48             vec![],
49             // Return a bunch of stuff from `gc` so that we exercise GCing when
50             // there is return pointer space allocated on the stack. This is
51             // especially important because the x64 backend currently
52             // dynamically adjusts the stack pointer for each call that uses
53             // return pointers rather than statically allocating space in the
54             // stack frame.
55             vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
56         );
57 
58         // 1: "run"
59         let mut params: Vec<ValType> = Vec::with_capacity(self.limits.num_params as usize);
60         for _i in 0..self.limits.num_params {
61             params.push(ValType::EXTERNREF);
62         }
63         let results = vec![];
64         types.ty().function(params, results);
65 
66         // 2: `take_refs`
67         types.ty().function(
68             vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
69             vec![],
70         );
71 
72         // 3: `make_refs`
73         types.ty().function(
74             vec![],
75             vec![ValType::EXTERNREF, ValType::EXTERNREF, ValType::EXTERNREF],
76         );
77 
78         // 4: `take_struct`
79         types.ty().function(
80             vec![ValType::Ref(RefType {
81                 nullable: false,
82                 heap_type: wasm_encoder::HeapType::ANY,
83             })],
84             vec![],
85         );
86 
87         let struct_type_base: u32 = types.len();
88 
89         let mut rec_groups: BTreeMap<RecGroupId, Vec<TypeId>> = self
90             .types
91             .rec_groups
92             .iter()
93             .copied()
94             .map(|id| (id, Vec::new()))
95             .collect();
96 
97         for (id, ty) in self.types.type_defs.iter() {
98             rec_groups.entry(ty.rec_group).or_default().push(id.clone());
99         }
100 
101         let encode_ty_id = |ty_id: &TypeId| -> wasm_encoder::SubType {
102             let def = &self.types.type_defs[ty_id];
103             match &def.composite_type {
104                 CompositeType::Struct(StructType {}) => wasm_encoder::SubType {
105                     is_final: true,
106                     supertype_idx: None,
107                     composite_type: wasm_encoder::CompositeType {
108                         inner: wasm_encoder::CompositeInnerType::Struct(wasm_encoder::StructType {
109                             fields: Box::new([]),
110                         }),
111                         shared: false,
112                         describes: None,
113                         descriptor: None,
114                     },
115                 },
116             }
117         };
118 
119         let mut struct_count = 0;
120 
121         for type_ids in rec_groups.values() {
122             let members: Vec<wasm_encoder::SubType> = type_ids.iter().map(encode_ty_id).collect();
123             types.ty().rec(members);
124             struct_count += type_ids.len() as u32;
125         }
126 
127         let typed_fn_type_base: u32 = struct_type_base + struct_count;
128 
129         for i in 0..struct_count {
130             let concrete = struct_type_base + i;
131             types.ty().function(
132                 vec![ValType::Ref(RefType {
133                     nullable: false,
134                     heap_type: wasm_encoder::HeapType::Concrete(concrete),
135                 })],
136                 vec![],
137             );
138         }
139 
140         // Import the GC function.
141         let mut imports = ImportSection::new();
142         imports.import("", "gc", EntityType::Function(0));
143         imports.import("", "take_refs", EntityType::Function(2));
144         imports.import("", "make_refs", EntityType::Function(3));
145         imports.import("", "take_struct", EntityType::Function(4));
146 
147         // For each of our concrete struct types, define a function
148         // import that takes an argument of that concrete type.
149         let typed_first_func_index: u32 = imports.len();
150 
151         for i in 0..struct_count {
152             let ty_idx = typed_fn_type_base + i;
153             let name = format!("take_struct_{}", struct_type_base + i);
154             imports.import("", &name, EntityType::Function(ty_idx));
155         }
156 
157         // Define our table.
158         let mut tables = TableSection::new();
159         tables.table(TableType {
160             element_type: RefType::EXTERNREF,
161             minimum: u64::from(self.limits.table_size),
162             maximum: None,
163             table64: false,
164             shared: false,
165         });
166         // Define our globals.
167         let mut globals = GlobalSection::new();
168         for _ in 0..self.limits.num_globals {
169             globals.global(
170                 wasm_encoder::GlobalType {
171                     val_type: wasm_encoder::ValType::EXTERNREF,
172                     mutable: true,
173                     shared: false,
174                 },
175                 &ConstExpr::ref_null(wasm_encoder::HeapType::EXTERN),
176             );
177         }
178 
179         // Define the "run" function export.
180         let mut functions = FunctionSection::new();
181         let mut exports = ExportSection::new();
182 
183         let run_defined_idx = functions.len();
184         functions.function(1);
185         let run_func_index = imports.len() + run_defined_idx;
186         exports.export("run", ExportKind::Func, run_func_index);
187 
188         // Give ourselves one scratch local that we can use in various `GcOp`
189         // implementations.
190         let local_decls: Vec<(u32, ValType)> = vec![(1, ValType::EXTERNREF)];
191 
192         let mut func = Function::new(local_decls);
193         func.instruction(&Instruction::Loop(wasm_encoder::BlockType::Empty));
194         for op in &self.ops {
195             op.insert(
196                 &mut func,
197                 self.limits.num_params,
198                 struct_type_base,
199                 typed_first_func_index,
200             );
201         }
202         func.instruction(&Instruction::Br(0));
203         func.instruction(&Instruction::End);
204         func.instruction(&Instruction::End);
205 
206         let mut code = CodeSection::new();
207         code.function(&func);
208 
209         module
210             .section(&types)
211             .section(&imports)
212             .section(&functions)
213             .section(&tables)
214             .section(&globals)
215             .section(&exports)
216             .section(&code);
217 
218         module.finish()
219     }
220 
221     /// Computes the abstract stack depth after executing all operations
222     pub fn abstract_stack_depth(&self, index: usize) -> usize {
223         debug_assert!(index <= self.ops.len());
224         let mut stack: usize = 0;
225         for op in self.ops.iter().take(index) {
226             let pop = op.operands_len();
227             let push = op.results_len();
228             stack = stack.saturating_sub(pop);
229             stack += push;
230         }
231         stack
232     }
233 
234     /// Fixes this test case such that it becomes valid.
235     ///
236     /// This is necessary because a random mutation (e.g. removing an op in the
237     /// middle of our sequence) might have made it so that subsequent ops won't
238     /// have their expected operand types on the Wasm stack
239     /// anymore. Furthermore, because we serialize and deserialize test cases,
240     /// and libFuzzer will occasionally mutate those serialized bytes directly,
241     /// rather than use one of our custom mutations, we have no guarantee that
242     /// pre-mutation test cases are even valid! Therefore, we always call this
243     /// method before translating this "AST"-style representation into a raw
244     /// Wasm binary.
245     pub fn fixup(&mut self) {
246         self.limits.fixup();
247         self.types.fixup(&self.limits);
248 
249         let mut new_ops = Vec::with_capacity(self.ops.len());
250         let mut stack = 0;
251 
252         for mut op in self.ops.iter().copied() {
253             if self.limits.max_types == 0
254                 && matches!(
255                     op,
256                     GcOp::StructNew(..) | GcOp::TakeStructCall(..) | GcOp::TakeTypedStructCall(..)
257                 )
258             {
259                 continue;
260             }
261             if self.limits.num_params == 0 && matches!(op, GcOp::LocalGet(..) | GcOp::LocalSet(..))
262             {
263                 continue;
264             }
265             if self.limits.num_globals == 0
266                 && matches!(op, GcOp::GlobalGet(..) | GcOp::GlobalSet(..))
267             {
268                 continue;
269             }
270 
271             op.fixup(&self.limits);
272 
273             let mut temp = SmallVec::<[_; 4]>::new();
274 
275             while stack < op.operands_len() {
276                 temp.push(GcOp::Null());
277                 stack += 1;
278             }
279 
280             temp.push(op);
281             stack = stack - op.operands_len() + op.results_len();
282 
283             new_ops.extend(temp);
284         }
285 
286         // Insert drops to balance the final stack state
287         for _ in 0..stack {
288             new_ops.push(GcOp::Drop());
289         }
290 
291         self.ops = new_ops;
292     }
293 
294     /// Attempts to remove the last opcode from the sequence.
295     ///
296     /// Returns `true` if an opcode was successfully removed, or `false` if the
297     /// list was already empty.
298     pub fn pop(&mut self) -> bool {
299         self.ops.pop().is_some()
300     }
301 }
302 
303 macro_rules! define_gc_ops {
304     (
305         $(
306             $op:ident $( ( $($limit_var:ident : $limit:expr => $ty:ty),* ) )? : $params:expr => $results:expr ,
307         )*
308     ) => {
309         /// The operations for the `gc` operations.
310         #[derive(Copy, Clone, Debug, Serialize, Deserialize)]
311         pub(crate) enum GcOp {
312             $(
313                 $op ( $( $($ty),* )? ),
314             )*
315         }
316 
317         /// Names of the operations for testing purposes.
318         #[cfg(test)]
319         pub const OP_NAMES: &'static[&'static str] = &[
320             $(
321                 stringify!($op),
322             )*
323         ];
324 
325         impl GcOp {
326             #[cfg(test)]
327             pub fn name(&self) -> &'static str  {
328                 match self {
329                     $(
330                         Self::$op (..) => stringify!($op),
331                     )*
332                 }
333             }
334 
335             pub fn operands_len(&self) -> usize {
336                 match self {
337                     $(
338                         Self::$op (..) => $params,
339                     )*
340                 }
341             }
342 
343             pub fn results_len(&self) -> usize {
344                 match self {
345                     $(
346                         Self::$op (..) => $results,
347                     )*
348                 }
349             }
350         }
351 
352         $(
353             #[allow(non_snake_case, reason = "macro-generated code")]
354             fn $op(
355                 _ctx: &mut mutatis::Context,
356                 _limits: &GcOpsLimits,
357                 stack: usize,
358             ) -> mutatis::Result<(GcOp, usize)> {
359                 #[allow(unused_comparisons, reason = "macro-generated code")]
360                 {
361                     debug_assert!(stack >= $params);
362                 }
363 
364                 let op = GcOp::$op(
365                     $($({
366                         let limit_fn = $limit as fn(&GcOpsLimits) -> $ty;
367                         let limit = (limit_fn)(_limits);
368                         debug_assert!(limit > 0);
369                         m::range(0..=limit - 1).generate(_ctx)?
370                     }),*)?
371                 );
372                 let new_stack = stack - $params + $results;
373                 Ok((op, new_stack))
374             }
375         )*
376 
377         impl GcOp {
378             fn fixup(&mut self, limits: &GcOpsLimits) {
379                 match self {
380                     $(
381                         Self::$op( $( $( $limit_var ),* )? ) => {
382                             $( $(
383                                 let limit_fn = $limit as fn(&GcOpsLimits) -> $ty;
384                                 let limit = (limit_fn)(limits);
385                                 debug_assert!(limit > 0);
386                                 *$limit_var = *$limit_var % limit;
387                             )* )?
388                         }
389                     )*
390                 }
391             }
392 
393             pub(crate) fn generate(
394                 ctx: &mut mutatis::Context,
395                 ops: &GcOps,
396                 stack: usize,
397             ) -> mutatis::Result<(GcOp, usize)> {
398                 let mut valid_choices: Vec<
399                     fn(&mut Context, &GcOpsLimits, usize) -> mutatis::Result<(GcOp, usize)>
400                 > = vec![];
401                 $(
402                     #[allow(unused_comparisons, reason = "macro-generated code")]
403                     if stack >= $params $($(
404                         && {
405                             let limit_fn = $limit as fn(&GcOpsLimits) -> $ty;
406                             let limit = (limit_fn)(&ops.limits);
407                             limit > 0
408                         }
409                     )*)? {
410                         valid_choices.push($op);
411                     }
412                 )*
413 
414                 let f = *ctx.rng()
415                     .choose(&valid_choices)
416                     .expect("should always have a valid op choice");
417 
418                 (f)(ctx, &ops.limits, stack)
419             }
420         }
421     };
422 }
423 
424 define_gc_ops! {
425     Gc : 0 => 3,
426 
427     MakeRefs : 0 => 3,
428     TakeRefs : 3 => 0,
429 
430     // Add one to make sure that out of bounds table accesses are possible, but still rare.
431     TableGet(elem_index: |ops| ops.table_size + 1 => u32) : 0 => 1,
432     TableSet(elem_index: |ops| ops.table_size + 1 => u32) : 1 => 0,
433 
434     GlobalGet(global_index: |ops| ops.num_globals => u32) : 0 => 1,
435     GlobalSet(global_index: |ops| ops.num_globals => u32) : 1 => 0,
436 
437     LocalGet(local_index: |ops| ops.num_params => u32) : 0 => 1,
438     LocalSet(local_index: |ops| ops.num_params => u32) : 1 => 0,
439 
440     StructNew(type_index: |ops| ops.max_types => u32) : 0 => 0,
441     TakeStructCall(type_index: |ops| ops.max_types => u32) : 1 => 0,
442     TakeTypedStructCall(type_index: |ops| ops.max_types => u32) : 1 => 0,
443 
444     Drop : 1 => 0,
445 
446     Null : 0 => 1,
447 }
448 
449 impl GcOp {
450     fn insert(
451         self,
452         func: &mut Function,
453         scratch_local: u32,
454         struct_type_base: u32,
455         typed_first_func_index: u32,
456     ) {
457         let gc_func_idx = 0;
458         let take_refs_func_idx = 1;
459         let make_refs_func_idx = 2;
460         let take_structref_idx = 3;
461 
462         match self {
463             Self::Gc() => {
464                 func.instruction(&Instruction::Call(gc_func_idx));
465             }
466             Self::MakeRefs() => {
467                 func.instruction(&Instruction::Call(make_refs_func_idx));
468             }
469             Self::TakeRefs() => {
470                 func.instruction(&Instruction::Call(take_refs_func_idx));
471             }
472             Self::TableGet(x) => {
473                 func.instruction(&Instruction::I32Const(x.cast_signed()));
474                 func.instruction(&Instruction::TableGet(0));
475             }
476             Self::TableSet(x) => {
477                 func.instruction(&Instruction::LocalSet(scratch_local));
478                 func.instruction(&Instruction::I32Const(x.cast_signed()));
479                 func.instruction(&Instruction::LocalGet(scratch_local));
480                 func.instruction(&Instruction::TableSet(0));
481             }
482             Self::GlobalGet(x) => {
483                 func.instruction(&Instruction::GlobalGet(x));
484             }
485             Self::GlobalSet(x) => {
486                 func.instruction(&Instruction::GlobalSet(x));
487             }
488             Self::LocalGet(x) => {
489                 func.instruction(&Instruction::LocalGet(x));
490             }
491             Self::LocalSet(x) => {
492                 func.instruction(&Instruction::LocalSet(x));
493             }
494             Self::Drop() => {
495                 func.instruction(&Instruction::Drop);
496             }
497             Self::Null() => {
498                 func.instruction(&Instruction::RefNull(wasm_encoder::HeapType::EXTERN));
499             }
500             Self::StructNew(x) => {
501                 func.instruction(&Instruction::StructNew(x + struct_type_base));
502                 func.instruction(&Instruction::Call(take_structref_idx));
503             }
504             Self::TakeStructCall(x) => {
505                 func.instruction(&Instruction::StructNew(x + struct_type_base));
506                 func.instruction(&Instruction::Call(take_structref_idx));
507             }
508             Self::TakeTypedStructCall(x) => {
509                 let s = struct_type_base + x;
510                 let f = typed_first_func_index + x;
511                 func.instruction(&Instruction::StructNew(s));
512                 func.instruction(&Instruction::Call(f));
513             }
514         }
515     }
516 }
517