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
13 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
32 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::Null(),
43             GcOp::Drop(),
44             GcOp::Gc(),
45             GcOp::LocalSet(0),
46             GcOp::LocalGet(0),
47             GcOp::GlobalSet(0),
48             GcOp::GlobalGet(0),
49             GcOp::StructNew(0),
50         ],
51         types: Types::new(),
52     };
53     for i in 0..t.limits.max_rec_groups {
54         t.types.insert_rec_group(RecGroupId(i));
55     }
56 
57     if t.limits.max_rec_groups > 0 {
58         let mut rng = StdRng::seed_from_u64(0xC0FFEE);
59         for i in 0..t.limits.max_types {
60             let gid = RecGroupId(rng.gen_range(0..t.limits.max_rec_groups));
61             t.types.insert_empty_struct(TypeId(i), gid);
62         }
63     }
64     t
65 }
66 
67 #[test]
68 fn mutate_gc_ops_with_default_mutator() -> mutatis::Result<()> {
69     let _ = env_logger::try_init();
70     let mut res = test_ops(5, 5, 5);
71 
72     let mut session = mutatis::Session::new();
73 
74     for _ in 0..2048 {
75         session.mutate(&mut res)?;
76         let wasm = res.to_wasm_binary();
77 
78         let feats = wasmparser::WasmFeatures::default();
79         feats.reference_types();
80         feats.gc();
81         let mut validator = wasmparser::Validator::new_with_features(feats);
82 
83         let wat = wasmprinter::print_bytes(&wasm).expect("[-] Failed .print_bytes(&wasm).");
84         let result = validator.validate_all(&wasm);
85         log::debug!("{wat}");
86         assert!(
87             result.is_ok(),
88             "\n[-] Invalid wat: {}\n\t\t==== Failed Wat ====\n{}",
89             result.err().expect("[-] Failed .err() in assert macro."),
90             wat
91         );
92     }
93     Ok(())
94 }
95 
96 #[test]
97 fn struct_new_removed_when_no_types() -> mutatis::Result<()> {
98     let _ = env_logger::try_init();
99 
100     let mut ops = test_ops(0, 0, 0);
101     ops.limits.max_types = 0;
102     ops.ops = vec![GcOp::StructNew(42)];
103 
104     let _ = ops.fixup();
105 
106     assert!(
107         ops.ops.iter().all(|op| !matches!(op, GcOp::StructNew(..))),
108         "StructNew should be removed when there are no types"
109     );
110     Ok(())
111 }
112 
113 #[test]
114 fn local_ops_removed_when_no_params() -> mutatis::Result<()> {
115     let _ = env_logger::try_init();
116 
117     let mut ops = test_ops(0, 0, 0);
118     ops.limits.num_params = 0;
119     ops.ops = vec![GcOp::LocalGet(42), GcOp::LocalSet(99)];
120 
121     ops.fixup();
122 
123     assert!(
124         ops.ops
125             .iter()
126             .all(|op| !matches!(op, GcOp::LocalGet(..) | GcOp::LocalSet(..))),
127         "LocalGet/LocalSet should be removed when there are no params"
128     );
129     Ok(())
130 }
131 
132 #[test]
133 fn global_ops_removed_when_no_globals() -> mutatis::Result<()> {
134     let _ = env_logger::try_init();
135 
136     let mut ops = test_ops(0, 0, 0);
137     ops.limits.num_globals = 0;
138     ops.ops = vec![GcOp::GlobalGet(42), GcOp::GlobalSet(99)];
139 
140     ops.fixup();
141 
142     assert!(
143         ops.ops
144             .iter()
145             .all(|op| !matches!(op, GcOp::GlobalGet(..) | GcOp::GlobalSet(..))),
146         "GlobalGet/GlobalSet should be removed when there are no globals"
147     );
148     Ok(())
149 }
150 
151 #[test]
152 fn every_op_generated() -> mutatis::Result<()> {
153     let _ = env_logger::try_init();
154     let mut unseen_ops: std::collections::HashSet<_> = OP_NAMES.iter().copied().collect();
155 
156     let mut res = empty_test_ops();
157     let mut session = mutatis::Session::new().seed(0xC0FFEE);
158 
159     'outer: for _ in 0..=1024 {
160         session.mutate(&mut res)?;
161         for op in &res.ops {
162             unseen_ops.remove(op.name());
163             if unseen_ops.is_empty() {
164                 break 'outer;
165             }
166         }
167     }
168 
169     assert!(unseen_ops.is_empty(), "Failed to generate {unseen_ops:?}");
170     Ok(())
171 }
172 
173 #[test]
174 fn test_wat_string() -> mutatis::Result<()> {
175     let _ = env_logger::try_init();
176 
177     let mut gc_ops = test_ops(2, 2, 5);
178 
179     let wasm = gc_ops.to_wasm_binary();
180 
181     let actual_wat = wasmprinter::print_bytes(&wasm).expect("Failed to convert to WAT");
182     let actual_wat = actual_wat.trim();
183 
184     let expected_wat = r#"
185 (module
186   (type (;0;) (func (result externref externref externref)))
187   (type (;1;) (func (param externref externref)))
188   (type (;2;) (func (param externref externref externref)))
189   (type (;3;) (func (result externref externref externref)))
190   (type (;4;) (func (param (ref any))))
191   (rec
192     (type (;5;) (struct))
193   )
194   (rec)
195   (rec
196     (type (;6;) (struct))
197   )
198   (rec
199     (type (;7;) (struct))
200     (type (;8;) (struct))
201     (type (;9;) (struct))
202   )
203   (rec
204     (type (;10;) (struct))
205     (type (;11;) (struct))
206   )
207   (rec
208     (type (;12;) (struct))
209     (type (;13;) (struct))
210   )
211   (rec
212     (type (;14;) (struct))
213   )
214   (type (;15;) (func (param (ref 5))))
215   (type (;16;) (func (param (ref 6))))
216   (type (;17;) (func (param (ref 7))))
217   (type (;18;) (func (param (ref 8))))
218   (type (;19;) (func (param (ref 9))))
219   (type (;20;) (func (param (ref 10))))
220   (type (;21;) (func (param (ref 11))))
221   (type (;22;) (func (param (ref 12))))
222   (type (;23;) (func (param (ref 13))))
223   (type (;24;) (func (param (ref 14))))
224   (import "" "gc" (func (;0;) (type 0)))
225   (import "" "take_refs" (func (;1;) (type 2)))
226   (import "" "make_refs" (func (;2;) (type 3)))
227   (import "" "take_struct" (func (;3;) (type 4)))
228   (import "" "take_struct_5" (func (;4;) (type 15)))
229   (import "" "take_struct_6" (func (;5;) (type 16)))
230   (import "" "take_struct_7" (func (;6;) (type 17)))
231   (import "" "take_struct_8" (func (;7;) (type 18)))
232   (import "" "take_struct_9" (func (;8;) (type 19)))
233   (import "" "take_struct_10" (func (;9;) (type 20)))
234   (import "" "take_struct_11" (func (;10;) (type 21)))
235   (import "" "take_struct_12" (func (;11;) (type 22)))
236   (import "" "take_struct_13" (func (;12;) (type 23)))
237   (import "" "take_struct_14" (func (;13;) (type 24)))
238   (table (;0;) 5 externref)
239   (global (;0;) (mut externref) ref.null extern)
240   (global (;1;) (mut externref) ref.null extern)
241   (export "run" (func 14))
242   (func (;14;) (type 1) (param externref externref)
243     (local externref)
244     loop ;; label = @1
245       ref.null extern
246       drop
247       call 0
248       local.set 0
249       local.get 0
250       global.set 0
251       global.get 0
252       struct.new 5
253       call 3
254       drop
255       drop
256       drop
257       br 0 (;@1;)
258     end
259   )
260 )
261     "#;
262     let expected_wat = expected_wat.trim();
263 
264     eprintln!("=== actual ===\n{actual_wat}");
265     eprintln!("=== expected ===\n{expected_wat}");
266     assert_eq!(
267         actual_wat, expected_wat,
268         "actual WAT does not match expected"
269     );
270 
271     Ok(())
272 }
273 
274 #[test]
275 fn emits_empty_rec_groups_and_validates() -> mutatis::Result<()> {
276     let _ = env_logger::try_init();
277 
278     let mut ops = test_ops(5, 5, 5);
279 
280     let wasm = ops.to_wasm_binary();
281 
282     let feats = wasmparser::WasmFeatures::default();
283     feats.reference_types();
284     feats.gc();
285     let mut validator = wasmparser::Validator::new_with_features(feats);
286     assert!(
287         validator.validate_all(&wasm).is_ok(),
288         "GC validation failed"
289     );
290 
291     let wat = wasmprinter::print_bytes(&wasm).expect("to WAT");
292     let recs = wat.matches("(rec").count();
293     let structs = wat.matches("(struct)").count();
294 
295     assert_eq!(recs, 7, "expected 2 (rec) blocks, got {recs}");
296     assert_eq!(structs, 10, "expected no struct types, got {structs}");
297 
298     Ok(())
299 }
300