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