1 use crate::generators::gc_ops::{
2     limits::GcOpsLimits,
3     ops::{GcOp, GcOps, OP_NAMES},
4     types::{RecGroupId, TypeId, Types},
5 };
6 use mutatis;
7 use rand::rngs::StdRng;
8 use rand::{Rng, SeedableRng};
9 use wasmparser;
10 use wasmprinter;
11 
12 /// Creates empty GcOps
empty_test_ops() -> GcOps13 fn empty_test_ops() -> GcOps {
14     let mut t = GcOps {
15         limits: GcOpsLimits {
16             num_params: 5,
17             num_globals: 5,
18             table_size: 5,
19             max_rec_groups: 5,
20             max_types: 5,
21         },
22         ops: vec![],
23         types: Types::new(),
24     };
25     for i in 0..t.limits.max_rec_groups {
26         t.types.insert_rec_group(RecGroupId(i));
27     }
28     t
29 }
30 
31 /// Creates GcOps with all default opcodes
test_ops(num_params: u32, num_globals: u32, table_size: u32) -> GcOps32 fn test_ops(num_params: u32, num_globals: u32, table_size: u32) -> GcOps {
33     let mut t = GcOps {
34         limits: GcOpsLimits {
35             num_params,
36             num_globals,
37             table_size,
38             max_rec_groups: 7,
39             max_types: 10,
40         },
41         ops: vec![
42             GcOp::NullExtern,
43             GcOp::Drop,
44             GcOp::Gc,
45             GcOp::LocalSet { local_index: 0 },
46             GcOp::LocalGet { local_index: 0 },
47             GcOp::GlobalSet { global_index: 0 },
48             GcOp::GlobalGet { global_index: 0 },
49             GcOp::StructNew { type_index: 0 },
50         ],
51         types: Types::new(),
52     };
53 
54     for i in 0..t.limits.max_rec_groups {
55         t.types.insert_rec_group(RecGroupId(i));
56     }
57 
58     let mut rng = StdRng::seed_from_u64(0xC0FFEE);
59     if t.limits.max_rec_groups > 0 {
60         for i in 0..t.limits.max_types {
61             let gid = RecGroupId(rng.gen_range(0..t.limits.max_rec_groups));
62             let is_final = false;
63             let supertype = None;
64             t.types
65                 .insert_empty_struct(TypeId(i), gid, is_final, supertype);
66         }
67     }
68 
69     t
70 }
71 
72 #[test]
mutate_gc_ops_with_default_mutator() -> mutatis::Result<()>73 fn mutate_gc_ops_with_default_mutator() -> mutatis::Result<()> {
74     let _ = env_logger::try_init();
75 
76     let mut features = wasmparser::WasmFeatures::default();
77     features.insert(wasmparser::WasmFeatures::REFERENCE_TYPES);
78     features.insert(wasmparser::WasmFeatures::FUNCTION_REFERENCES);
79     features.insert(wasmparser::WasmFeatures::GC_TYPES);
80     features.insert(wasmparser::WasmFeatures::GC);
81 
82     let mut ops = test_ops(5, 5, 5);
83 
84     let mut session = mutatis::Session::new();
85     for _ in 0..2048 {
86         session.mutate(&mut ops)?;
87 
88         let wasm = ops.to_wasm_binary();
89         println!("wasm: {}", wasmprinter::print_bytes(&wasm).unwrap());
90         crate::oracles::log_wasm(&wasm);
91 
92         let mut validator = wasmparser::Validator::new_with_features(features);
93         if let Err(e) = validator.validate_all(&wasm) {
94             let mut config = wasmprinter::Config::new();
95             config.print_offsets(true);
96             config.print_operand_stack(true);
97             let mut wat = String::new();
98             let wat = match config.print(&wasm, &mut wasmprinter::PrintFmtWrite(&mut wat)) {
99                 Ok(()) => wat,
100                 Err(e) => format!("<failed to disassemble Wasm binary to WAT: {e}>"),
101             };
102             panic!(
103                 "Emitted Wasm binary is not valid!\n\n\
104                  === Validation Error ===\n\n\
105                  {e}\n\n\
106                  === GcOps ===\n\n\
107                  {ops:#?}\n\n\
108                  === Wat ===\n\n\
109                  {wat}"
110             );
111         }
112     }
113     Ok(())
114 }
115 
116 #[test]
struct_new_removed_when_no_types() -> mutatis::Result<()>117 fn struct_new_removed_when_no_types() -> mutatis::Result<()> {
118     let _ = env_logger::try_init();
119 
120     let mut ops = test_ops(0, 0, 0);
121     ops.limits.max_types = 0;
122     ops.ops = vec![GcOp::StructNew { type_index: 42 }];
123 
124     ops.fixup();
125     assert!(
126         ops.ops
127             .iter()
128             .all(|op| !matches!(op, GcOp::StructNew { .. })),
129         "StructNew should be removed when there are no types"
130     );
131     Ok(())
132 }
133 
134 #[test]
local_ops_removed_when_no_params() -> mutatis::Result<()>135 fn local_ops_removed_when_no_params() -> mutatis::Result<()> {
136     let _ = env_logger::try_init();
137 
138     let mut ops = test_ops(0, 0, 0);
139     ops.limits.num_params = 0;
140     ops.ops = vec![
141         GcOp::LocalGet { local_index: 42 },
142         GcOp::LocalSet { local_index: 99 },
143     ];
144 
145     ops.fixup();
146     assert!(
147         ops.ops
148             .iter()
149             .all(|op| !matches!(op, GcOp::LocalGet { .. } | GcOp::LocalSet { .. })),
150         "LocalGet/LocalSet should be removed when there are no params"
151     );
152     Ok(())
153 }
154 
155 #[test]
global_ops_removed_when_no_globals() -> mutatis::Result<()>156 fn global_ops_removed_when_no_globals() -> mutatis::Result<()> {
157     let _ = env_logger::try_init();
158 
159     let mut ops = test_ops(0, 0, 0);
160     ops.limits.num_globals = 0;
161     ops.ops = vec![
162         GcOp::GlobalGet { global_index: 42 },
163         GcOp::GlobalSet { global_index: 99 },
164     ];
165 
166     ops.fixup();
167     assert!(
168         ops.ops
169             .iter()
170             .all(|op| !matches!(op, GcOp::GlobalGet { .. } | GcOp::GlobalSet { .. })),
171         "GlobalGet/GlobalSet should be removed when there are no globals"
172     );
173     Ok(())
174 }
175 
176 #[test]
every_op_generated() -> mutatis::Result<()>177 fn every_op_generated() -> mutatis::Result<()> {
178     let _ = env_logger::try_init();
179     let mut unseen_ops: std::collections::HashSet<_> = OP_NAMES.iter().copied().collect();
180 
181     let mut res = empty_test_ops();
182     let mut session = mutatis::Session::new().seed(0xC0FFEE);
183 
184     'outer: for _ in 0..=1024 {
185         session.mutate(&mut res)?;
186         for op in &res.ops {
187             unseen_ops.remove(op.name());
188             if unseen_ops.is_empty() {
189                 break 'outer;
190             }
191         }
192     }
193 
194     assert!(unseen_ops.is_empty(), "Failed to generate {unseen_ops:?}");
195     Ok(())
196 }
197 
198 #[test]
emits_rec_groups_and_validates() -> mutatis::Result<()>199 fn emits_rec_groups_and_validates() -> mutatis::Result<()> {
200     let _ = env_logger::try_init();
201 
202     let mut ops = test_ops(5, 5, 5);
203 
204     let wasm = ops.to_wasm_binary();
205 
206     let feats = wasmparser::WasmFeatures::default();
207     feats.reference_types();
208     feats.gc();
209     let mut validator = wasmparser::Validator::new_with_features(feats);
210     assert!(
211         validator.validate_all(&wasm).is_ok(),
212         "GC validation failed"
213     );
214 
215     let wat = wasmprinter::print_bytes(&wasm).expect("to WAT");
216     let recs = wat.matches("(rec").count();
217     let structs = wat.matches("(struct)").count();
218 
219     assert_eq!(
220         recs,
221         ops.types.rec_groups.len(),
222         "one (rec) block per rec group"
223     );
224     assert_eq!(
225         structs,
226         ops.types.type_defs.len(),
227         "one (struct) per struct type in type_defs"
228     );
229 
230     Ok(())
231 }
232 
233 #[test]
fixup_check_types_and_indexes() -> mutatis::Result<()>234 fn fixup_check_types_and_indexes() -> mutatis::Result<()> {
235     let _ = env_logger::try_init();
236 
237     let mut ops = test_ops(5, 5, 5);
238 
239     // These `GcOp`s do not have their operands satisfied, and their results are
240     // not the operands of the next op, so `fixup` will need to deal with
241     // that. Additionally, their immediates are out-of-bounds of their
242     // respective index spaces, which `fixup` will also need to address.
243     ops.ops = vec![
244         GcOp::TakeTypedStructCall {
245             type_index: ops.limits.max_types + 7,
246         },
247         GcOp::GlobalSet {
248             global_index: ops.limits.num_globals * 2,
249         },
250         GcOp::StructNew {
251             type_index: ops.limits.max_types + 9,
252         },
253         GcOp::LocalSet {
254             local_index: ops.limits.num_params * 5,
255         },
256     ];
257 
258     // Call `fixup` to insert missing types, rewrite the immediates such that
259     // they are within their bounds, insert missing operands, and drop unused
260     // results.
261     ops.fixup();
262 
263     // Check that we got the expected `GcOp` sequence after `fixup`:
264     assert_eq!(
265         ops.ops,
266         [
267             // Inserted by `fixup` to satisfy `TakeTypedStructCall`'s operands.
268             GcOp::StructNew { type_index: 7 },
269             // The `type_index` is now valid.
270             GcOp::TakeTypedStructCall { type_index: 7 },
271             // Inserted by `fixup` to satisfy `GlobalSet`'s operands.
272             GcOp::NullExtern,
273             // The `global_index` is now valid.
274             GcOp::GlobalSet { global_index: 0 },
275             // The `type_index` is now valid.
276             GcOp::StructNew { type_index: 9 },
277             // Inserted by `fixup` to satisfy `LocalSet`'s operands.
278             GcOp::NullExtern,
279             // The `local_index` is now valid.
280             GcOp::LocalSet { local_index: 0 },
281             // Inserted by `fixup` to make sure the operand stack is empty at
282             // the end of the block.
283             GcOp::Drop,
284         ]
285     );
286 
287     // Verify that we generate a valid Wasm binary after calling `fixup`.
288     let wasm = ops.to_wasm_binary();
289     let wat = wasmprinter::print_bytes(&wasm).unwrap();
290     log::debug!("{wat}");
291     let feats = wasmparser::WasmFeatures::default();
292     feats.reference_types();
293     feats.gc();
294     let mut validator = wasmparser::Validator::new_with_features(feats);
295     assert!(
296         validator.validate_all(&wasm).is_ok(),
297         "GC validation should pass after fixup"
298     );
299 
300     Ok(())
301 }
302 
303 #[test]
sort_types_by_supertype_orders_supertype_before_subtype_across_rec_groups()304 fn sort_types_by_supertype_orders_supertype_before_subtype_across_rec_groups() {
305     let mut types = Types::new();
306     let ga = RecGroupId(0);
307     let gb = RecGroupId(1);
308     let gc = RecGroupId(2);
309     let gd = RecGroupId(3);
310     types.insert_rec_group(ga);
311     types.insert_rec_group(gb);
312     types.insert_rec_group(gc);
313     types.insert_rec_group(gd);
314 
315     let a = TypeId(0);
316     let b = TypeId(1);
317     let c = TypeId(2);
318     let d = TypeId(3);
319 
320     // Cross-rec-group chain: C <: A <: B <: D.
321     types.insert_empty_struct(a, ga, false, Some(b)); // A <: B
322     types.insert_empty_struct(b, gb, false, Some(d)); // B <: D
323     types.insert_empty_struct(c, gc, false, Some(a)); // C <: A
324     types.insert_empty_struct(d, gd, false, None); // D
325 
326     let mut sorted = Vec::new();
327     types.sort_types_topo(&mut sorted);
328 
329     // Rec-group boundaries do not change topological ordering by supertype.
330     assert_eq!(
331         sorted,
332         [TypeId(3), TypeId(1), TypeId(0), TypeId(2)],
333         "topo order: supertype before subtype across rec groups"
334     );
335 }
336 
337 #[test]
fixup_preserves_subtyping_within_same_rec_group()338 fn fixup_preserves_subtyping_within_same_rec_group() {
339     let _ = env_logger::try_init();
340 
341     let mut types = Types::new();
342     let g = RecGroupId(0);
343     types.insert_rec_group(g);
344 
345     let super_ty = TypeId(0);
346     let sub_ty = TypeId(1);
347 
348     // Both types are in the same rec group.
349     // The second subtypes the first.
350     types.insert_empty_struct(super_ty, g, false, None);
351     types.insert_empty_struct(sub_ty, g, false, Some(super_ty));
352 
353     let limits = GcOpsLimits {
354         num_params: 0,
355         num_globals: 0,
356         table_size: 0,
357         max_rec_groups: 10,
358         max_types: 10,
359     };
360 
361     types.fixup(&limits);
362 
363     assert_eq!(types.rec_group_of(super_ty), Some(g));
364     assert_eq!(types.rec_group_of(sub_ty), Some(g));
365     assert_eq!(
366         types.type_defs.get(&sub_ty).unwrap().supertype,
367         Some(super_ty)
368     );
369 }
370 
371 #[test]
fixup_breaks_one_edge_in_multi_rec_group_type_cycle()372 fn fixup_breaks_one_edge_in_multi_rec_group_type_cycle() {
373     let _ = env_logger::try_init();
374 
375     let mut types = Types::new();
376 
377     let g_a = RecGroupId(0);
378     let g_bc = RecGroupId(1);
379     let g_d = RecGroupId(2);
380 
381     types.insert_rec_group(g_a);
382     types.insert_rec_group(g_bc);
383     types.insert_rec_group(g_d);
384 
385     let a = TypeId(0);
386     let b = TypeId(1);
387     let c = TypeId(2);
388     let d = TypeId(3);
389 
390     // Rec(a)
391     types.insert_empty_struct(a, g_a, false, Some(d));
392 
393     // Rec(b, c)
394     types.insert_empty_struct(b, g_bc, false, None);
395     types.insert_empty_struct(c, g_bc, false, Some(a));
396 
397     // Rec(d)
398     types.insert_empty_struct(d, g_d, false, Some(c));
399 
400     let limits = GcOpsLimits {
401         num_params: 0,
402         num_globals: 0,
403         table_size: 0,
404         max_rec_groups: 10,
405         max_types: 10,
406     };
407 
408     types.fixup(&limits);
409 
410     let a_super = types.type_defs.get(&a).unwrap().supertype;
411     let c_super = types.type_defs.get(&c).unwrap().supertype;
412     let d_super = types.type_defs.get(&d).unwrap().supertype;
413 
414     let cleared = [a_super, c_super, d_super]
415         .into_iter()
416         .filter(|st| st.is_none())
417         .count();
418 
419     assert!(
420         cleared == 1,
421         "fixup should clear exactly one edge to break the cycle"
422     );
423 }
424 
425 #[test]
sort_rec_groups_topo_orders_dependencies_first()426 fn sort_rec_groups_topo_orders_dependencies_first() {
427     let mut types = Types::new();
428 
429     let g0 = RecGroupId(0);
430     let g1 = RecGroupId(1);
431     let g2 = RecGroupId(2);
432     let g3 = RecGroupId(3);
433 
434     types.insert_rec_group(g0);
435     types.insert_rec_group(g1);
436     types.insert_rec_group(g2);
437     types.insert_rec_group(g3);
438 
439     let a = TypeId(0);
440     let b = TypeId(1);
441     let c = TypeId(2);
442     let d = TypeId(3);
443     let e = TypeId(4);
444     let f = TypeId(5);
445 
446     types.insert_empty_struct(a, g0, false, Some(b)); // g0 -> g1
447     types.insert_empty_struct(b, g1, false, Some(c)); // g1 -> g2
448     types.insert_empty_struct(c, g2, false, Some(d)); // g2 ->g3
449     types.insert_empty_struct(d, g3, false, None);
450     types.insert_empty_struct(e, g0, false, None);
451     types.insert_empty_struct(f, g2, false, None);
452 
453     let mut sorted = Vec::new();
454     types.sort_rec_groups_topo(&mut sorted);
455 
456     // g3 has no deps, g2 depends on g3, g1 on g2, g0 on g1.
457     assert_eq!(
458         sorted,
459         [RecGroupId(3), RecGroupId(2), RecGroupId(1), RecGroupId(0)],
460         "topo order: depended-on groups before dependent groups"
461     );
462 }
463 
464 #[test]
break_rec_group_cycles()465 fn break_rec_group_cycles() {
466     let mut types = Types::new();
467 
468     let g0 = RecGroupId(0);
469     let g1 = RecGroupId(1);
470     let g2 = RecGroupId(2);
471     let g3 = RecGroupId(3);
472 
473     types.insert_rec_group(g0);
474     types.insert_rec_group(g1);
475     types.insert_rec_group(g2);
476     types.insert_rec_group(g3);
477 
478     let a0 = TypeId(0);
479     let a1 = TypeId(1);
480     let b0 = TypeId(2);
481     let b1 = TypeId(3);
482     let c0 = TypeId(4);
483     let c1 = TypeId(5);
484     let c2 = TypeId(6);
485     let d0 = TypeId(7);
486     let d1 = TypeId(8);
487 
488     // Before: t
489     //
490     //    ----------------------------------
491     //    |          outer cycle           │
492     //    v                                │
493     //  +----+       +----+       +----+   │
494     //  | g0 |------>| g1 |------>| g2 |---
495     //  +----+       +----+       +----+
496     //                 ^            │
497     //                 │  inner     │
498     //                 │  cycle     v
499     //                 │          +----+
500     //                 -----------| g3 |
501     //                            +----+
502     //
503     // After: back edges dropped, clean chain
504     //
505     //  +----+       +----+       +----+       +----+
506     //  | g0 |------>| g1 |------>| g2 |------>| g3 |
507     //  +----+       +----+       +----+       +----+
508 
509     types.insert_empty_struct(a0, g0, false, Some(b0)); // g0 -> g1
510     types.insert_empty_struct(a1, g0, false, None);
511 
512     types.insert_empty_struct(b0, g1, false, None);
513     types.insert_empty_struct(b1, g1, false, Some(c0)); // g1 -> g2
514 
515     types.insert_empty_struct(c0, g2, false, None);
516     types.insert_empty_struct(c1, g2, false, Some(a1)); // g2 -> g0 (outer back edge)
517     types.insert_empty_struct(c2, g2, false, Some(d0)); // g2 -> g3
518 
519     types.insert_empty_struct(d0, g3, false, None);
520     types.insert_empty_struct(d1, g3, false, Some(b0)); // g3 -> g1 (inner back edge)
521 
522     // Type graph is acyclic — breaking supertype cycles changes nothing.
523     types.break_supertype_cycles();
524     assert_eq!(types.type_defs.get(&a0).unwrap().supertype, Some(b0));
525     assert_eq!(types.type_defs.get(&b1).unwrap().supertype, Some(c0));
526     assert_eq!(types.type_defs.get(&c1).unwrap().supertype, Some(a1));
527     assert_eq!(types.type_defs.get(&c2).unwrap().supertype, Some(d0));
528     assert_eq!(types.type_defs.get(&d1).unwrap().supertype, Some(b0));
529 
530     assert_eq!(types.rec_groups.len(), 4);
531 
532     types.break_rec_group_cycles();
533 
534     // All four groups preserved.
535     assert_eq!(types.rec_groups.len(), 4);
536     assert!(types.rec_groups.contains_key(&g0));
537     assert!(types.rec_groups.contains_key(&g1));
538     assert!(types.rec_groups.contains_key(&g2));
539     assert!(types.rec_groups.contains_key(&g3));
540 
541     // Back edge (g2->g0): c1's supertype cleared.
542     assert_eq!(types.type_defs.get(&c1).unwrap().supertype, None);
543 
544     // Back edge (g3->g1): d1's supertype cleared.
545     assert_eq!(types.type_defs.get(&d1).unwrap().supertype, None);
546 
547     // All other cross-group supertypes preserved.
548     assert_eq!(types.type_defs.get(&a0).unwrap().supertype, Some(b0));
549     assert_eq!(types.type_defs.get(&b1).unwrap().supertype, Some(c0));
550     assert_eq!(types.type_defs.get(&c2).unwrap().supertype, Some(d0));
551 
552     // Result is a clean chain: g0 -> g1 -> g2 -> g3
553     let mut topo = Vec::new();
554     types.sort_rec_groups_topo(&mut topo);
555     assert_eq!(topo.len(), 4);
556     assert_eq!(topo, vec![g3, g2, g1, g0]);
557 }
558