1 //! Generator for exception-handling fuzz test cases. 2 //! 3 //! Generates Wasm modules that exercise `throw`/`try_table`/`catch` in 4 //! various combinations: multiple tags with different signatures, nested 5 //! handler scopes, throws at various call depths, catch vs catch_all, and 6 //! multiple catch clauses per try_table. 7 8 use mutatis::{Candidates, Context, DefaultMutate, Generate, Mutate, Result as MutResult}; 9 use serde::{Deserialize, Serialize}; 10 use std::borrow::Cow; 11 use wasm_encoder::{ 12 BlockType, CodeSection, EntityType, ExportKind, ExportSection, Function, FunctionSection, 13 ImportSection, Instruction, Module, TagKind, TagSection, TagType, TypeSection, ValType, 14 }; 15 16 /// Max number of tags. 17 pub const NUM_TAGS_MAX: u32 = 8; 18 /// Max call-chain depth (number of functions between run and thrower). 19 pub const CALL_DEPTH_MAX: u32 = 6; 20 /// Max number of scenarios (throw+catch combos) per test case. 21 pub const MAX_SCENARIOS: usize = 16; 22 /// Maximum params per tag signature. 23 pub const MAX_TAG_PARAMS: usize = 4; 24 /// Maximum number of decoy catches. 25 pub const MAX_DECOY_CATCHES: usize = 4; 26 27 /// Limits controlling the structure of a generated Wasm module. 28 #[derive(Debug, Default, Clone, Serialize, Deserialize, Mutate)] 29 pub struct ExceptionOpsLimits { 30 /// Number of distinct tags to define. 31 pub(crate) num_tags: u32, 32 /// Depth of the call chain (number of intermediate functions). 33 pub(crate) call_depth: u32, 34 } 35 36 impl ExceptionOpsLimits { fixup(&mut self)37 pub(crate) fn fixup(&mut self) { 38 self.num_tags = self.num_tags.clamp(1, NUM_TAGS_MAX); 39 self.call_depth = self.call_depth.clamp(1, CALL_DEPTH_MAX); 40 } 41 } 42 43 /// A tag signature. 44 #[derive(Debug, Clone, Serialize, Deserialize, Mutate)] 45 pub struct TagSig { 46 pub(crate) params: Vec<SimpleValType>, 47 } 48 49 /// Subset of ValType that we generate for tag params. 50 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] 51 #[allow(missing_docs, reason = "self-describing")] 52 pub enum SimpleValType { 53 I32, 54 I64, 55 F32, 56 F64, 57 } 58 59 impl SimpleValType { to_val_type(self) -> ValType60 fn to_val_type(self) -> ValType { 61 match self { 62 Self::I32 => ValType::I32, 63 Self::I64 => ValType::I64, 64 Self::F32 => ValType::F32, 65 Self::F64 => ValType::F64, 66 } 67 } 68 69 /// A deterministic "interesting" constant for the given type and index, 70 /// used as the thrown payload so the oracle can verify the catch. test_value(self, idx: u32) -> Instruction<'static>71 fn test_value(self, idx: u32) -> Instruction<'static> { 72 match self { 73 Self::I32 => Instruction::I32Const(0x1000_i32.wrapping_add(idx.cast_signed())), 74 Self::I64 => Instruction::I64Const(0x2000_i64.wrapping_add(i64::from(idx))), 75 Self::F32 => Instruction::F32Const(wasm_encoder::Ieee32::new(0x4000_0000 + idx)), 76 Self::F64 => Instruction::F64Const(wasm_encoder::Ieee64::new( 77 0x4000_0000_0000_0000 + u64::from(idx), 78 )), 79 } 80 } 81 } 82 83 /// Which kind of catch clause to use for the handler. 84 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] 85 pub enum CatchKind { 86 /// `(catch $tag $label)`: specific tag, payload on branch. 87 Catch, 88 /// `(catch_all $label)`: any tag, no payload on branch. 89 CatchAll, 90 } 91 92 /// One throw-and-catch scenario. 93 #[derive(Debug, Clone, Serialize, Deserialize, Mutate)] 94 pub struct Scenario { 95 /// Index (into our tag list) of the tag to throw. 96 pub(crate) throw_tag: u32, 97 /// The function depth at which the throw happens (0 = run, call_depth = deepest). 98 pub(crate) throw_depth: u32, 99 /// The function depth at which the handler lives. Must be <= throw_depth. 100 pub(crate) catch_depth: u32, 101 /// What kind of catch clause to use. 102 pub(crate) catch_kind: CatchKind, 103 /// Extra catch clauses (tag indices) to place *before* the real one in the 104 /// try_table, exercising the "skip non-matching" path. These must be 105 /// indices of tags different from `throw_tag`. 106 pub(crate) decoy_catches: Vec<u32>, 107 /// Extra catch clauses (tag indices) to place *after* the real one in the 108 /// try_table, exercising the "clauses past the match" path. These must be 109 /// indices of tags different from `throw_tag`. 110 pub(crate) decoy_catches_after: Vec<u32>, 111 } 112 113 /// A description of a Wasm module that exercises exception throw/catch. 114 #[derive(Debug, Default, Clone, Serialize, Deserialize, Mutate)] 115 pub struct ExceptionOps { 116 pub(crate) limits: ExceptionOpsLimits, 117 pub(crate) tag_sigs: Vec<TagSig>, 118 pub(crate) scenarios: Vec<Scenario>, 119 } 120 121 impl ExceptionOps { 122 /// Encode as a Wasm module. to_wasm_binary(&mut self) -> Vec<u8>123 pub fn to_wasm_binary(&mut self) -> Vec<u8> { 124 self.fixup(); 125 self.encode() 126 } 127 128 /// Fix up the test case to ensure all indices and structures are valid. fixup(&mut self)129 pub fn fixup(&mut self) { 130 self.limits.fixup(); 131 let num_tags = usize::try_from(self.limits.num_tags).unwrap(); 132 133 // Ensure we have exactly num_tags tag signatures. 134 while self.tag_sigs.len() < num_tags { 135 // Default: single i32 param. 136 self.tag_sigs.push(TagSig { 137 params: vec![SimpleValType::I32], 138 }); 139 } 140 self.tag_sigs.truncate(num_tags); 141 142 // Clamp param counts. 143 for sig in &mut self.tag_sigs { 144 sig.params.truncate(MAX_TAG_PARAMS); 145 if sig.params.is_empty() { 146 sig.params.push(SimpleValType::I32); 147 } 148 } 149 150 // Ensure at least one scenario. 151 if self.scenarios.is_empty() { 152 self.scenarios.push(Scenario { 153 throw_tag: 0, 154 throw_depth: self.limits.call_depth, 155 catch_depth: 0, 156 catch_kind: CatchKind::Catch, 157 decoy_catches: vec![], 158 decoy_catches_after: vec![], 159 }); 160 } 161 self.scenarios.truncate(MAX_SCENARIOS); 162 163 let num_tags = self.limits.num_tags; 164 let depth = self.limits.call_depth; 165 for s in &mut self.scenarios { 166 s.throw_tag = s.throw_tag % num_tags; 167 s.throw_depth = s.throw_depth.clamp(1, depth); 168 s.catch_depth = s.catch_depth.clamp(0, s.throw_depth - 1); 169 // Decoy catches: keep only tags different from the real one. 170 s.decoy_catches.retain(|t| *t % num_tags != s.throw_tag); 171 for t in &mut s.decoy_catches { 172 *t = *t % num_tags; 173 } 174 s.decoy_catches.truncate(MAX_DECOY_CATCHES); 175 s.decoy_catches_after 176 .retain(|t| *t % num_tags != s.throw_tag); 177 for t in &mut s.decoy_catches_after { 178 *t = *t % num_tags; 179 } 180 s.decoy_catches_after.truncate(MAX_DECOY_CATCHES); 181 } 182 } 183 184 /// Module layout: 185 /// 186 /// Types: 187 /// 0..num_tags tag signatures: (params) -> () 188 /// num_tags..2*num_tags catch block types: () -> (params) 189 /// 2*num_tags () -> (i32) (run & relay functions) 190 /// 2*num_tags + 1 (i32, i32) -> () (check_i32) 191 /// 2*num_tags + 2 () -> () (thrower functions) 192 /// 193 /// Tags: 0..num_tags (using type indices 0..num_tags) 194 /// Imports: 0: "check_i32" 195 /// Functions: per-scenario call chains + "run" 196 /// Exports: "run" encode(&self) -> Vec<u8>197 fn encode(&self) -> Vec<u8> { 198 let num_tags = self.limits.num_tags; 199 let call_depth = self.limits.call_depth; 200 let num_scenarios = u32::try_from(self.scenarios.len()).unwrap(); 201 202 let mut module = Module::new(); 203 204 let mut types = TypeSection::new(); 205 206 // Type indices 0..num_tags: tag signatures (params -> []) 207 for sig in &self.tag_sigs { 208 let params: Vec<ValType> = sig.params.iter().map(|t| t.to_val_type()).collect(); 209 types.ty().function(params, vec![]); 210 } 211 212 // Type indices num_tags..2*num_tags: catch block types ([] -> params) 213 // These are used as block types for the catch target blocks, since 214 // `catch $tag $label` delivers the tag's param types at the label. 215 let catch_block_type_base = num_tags; 216 for sig in &self.tag_sigs { 217 let results: Vec<ValType> = sig.params.iter().map(|t| t.to_val_type()).collect(); 218 types.ty().function(vec![], results); 219 } 220 221 // Utility function types 222 let fn_type_void_to_i32 = types.len(); 223 types.ty().function(vec![], vec![ValType::I32]); 224 225 let fn_type_check = types.len(); 226 types 227 .ty() 228 .function(vec![ValType::I32, ValType::I32], vec![]); 229 230 let fn_type_void_to_void = types.len(); 231 types.ty().function(vec![], vec![]); 232 233 let mut tags = TagSection::new(); 234 for i in 0..num_tags { 235 tags.tag(TagType { 236 kind: TagKind::Exception, 237 func_type_idx: i, 238 }); 239 } 240 241 let mut imports = ImportSection::new(); 242 let check_func_idx: u32 = 0; 243 imports.import("", "check_i32", EntityType::Function(fn_type_check)); 244 let import_count: u32 = imports.len(); 245 246 // For each scenario: (call_depth + 1) functions at depths 0..=call_depth. 247 // - depth == catch_depth: handler function () -> (i32) 248 // - depth == throw_depth: thrower function () -> () 249 // - other depths < throw_depth: relay function () -> (i32) 250 // - other depths > throw_depth: unreachable () -> () 251 // 252 // Plus one "run" function that calls each scenario's depth-0 function. 253 254 let funcs_per_scenario = call_depth + 1; 255 let run_defined_idx = num_scenarios * funcs_per_scenario; 256 let run_func_idx = import_count + run_defined_idx; 257 258 let mut functions = FunctionSection::new(); 259 let mut code = CodeSection::new(); 260 261 for (si, scenario) in self.scenarios.iter().enumerate() { 262 let scenario_base = import_count + u32::try_from(si).unwrap() * funcs_per_scenario; 263 let tag_sig = &self.tag_sigs[usize::try_from(scenario.throw_tag).unwrap()]; 264 265 for d in 0..=call_depth { 266 if d == scenario.throw_depth { 267 // -- Thrower: pushes payload, throws tag -- 268 functions.function(fn_type_void_to_void); 269 let mut f = Function::new(vec![]); 270 for (pi, param_ty) in tag_sig.params.iter().enumerate() { 271 f.instruction(¶m_ty.test_value( 272 scenario.throw_tag * u32::try_from(MAX_TAG_PARAMS).unwrap() 273 + u32::try_from(pi).unwrap(), 274 )); 275 } 276 f.instruction(&Instruction::Throw(scenario.throw_tag)); 277 f.instruction(&Instruction::End); 278 code.function(&f); 279 } else if d == scenario.catch_depth { 280 // -- Handler: try_table with catch clauses -- 281 functions.function(fn_type_void_to_i32); 282 let mut f = Function::new(vec![]); 283 284 // All decoys (before + after the real catch) share the same 285 // block structure; only the catch-clause ordering differs. 286 let all_decoys: Vec<u32> = scenario 287 .decoy_catches 288 .iter() 289 .chain(scenario.decoy_catches_after.iter()) 290 .copied() 291 .collect(); 292 let num_decoys = all_decoys.len(); 293 let num_before = scenario.decoy_catches.len(); 294 295 // Block nesting (outermost to innermost): 296 // block $result (result i32) 297 // block $catch (result <tag_params> or empty) 298 // block $decoy_0 (result <decoy params>) 299 // ... 300 // block $decoy_{n-1} (result <decoy params>) 301 // try_table ... 302 // 303 // Catch clause labels are relative to the enclosing blocks, 304 // not counting the try_table itself. So from the catch: 305 // label 0 = $decoy_{n-1} (innermost block) 306 // label num_decoys-1 = $decoy_0 307 // label num_decoys = $catch 308 // label num_decoys+1 = $result 309 // 310 // br instructions inside the try_table body do count the 311 // try_table, so they need +1 compared to catch labels. 312 313 let catch_label = u32::try_from(num_decoys).unwrap(); 314 let result_label = u32::try_from(num_decoys + 1).unwrap(); 315 316 // For br inside try_table body, add 1 for the try_table scope 317 let br_result_label = result_label + 1; 318 319 // Build catch clauses: before-decoys, real catch, after-decoys 320 let mut catches: Vec<wasm_encoder::Catch> = Vec::new(); 321 for (di, &decoy_tag) in scenario.decoy_catches.iter().enumerate() { 322 let decoy_label = u32::try_from(num_decoys - 1 - di).unwrap(); 323 catches.push(wasm_encoder::Catch::One { 324 tag: decoy_tag, 325 label: decoy_label, 326 }); 327 } 328 match scenario.catch_kind { 329 CatchKind::Catch => { 330 catches.push(wasm_encoder::Catch::One { 331 tag: scenario.throw_tag, 332 label: catch_label, 333 }); 334 } 335 CatchKind::CatchAll => { 336 catches.push(wasm_encoder::Catch::All { label: catch_label }); 337 } 338 } 339 for (i, &decoy_tag) in scenario.decoy_catches_after.iter().enumerate() { 340 let di = num_before + i; 341 let decoy_label = u32::try_from(num_decoys - 1 - di).unwrap(); 342 catches.push(wasm_encoder::Catch::One { 343 tag: decoy_tag, 344 label: decoy_label, 345 }); 346 } 347 348 // Emit blocks (outermost first) 349 // block $result (result i32) 350 f.instruction(&Instruction::Block(BlockType::Result(ValType::I32))); 351 352 // block $catch 353 match scenario.catch_kind { 354 CatchKind::Catch => { 355 let bt = 356 BlockType::FunctionType(catch_block_type_base + scenario.throw_tag); 357 f.instruction(&Instruction::Block(bt)); 358 } 359 CatchKind::CatchAll => { 360 f.instruction(&Instruction::Block(BlockType::Empty)); 361 } 362 } 363 364 // Decoy blocks (decoy_0 outermost, decoy_{n-1} innermost) 365 for &decoy_tag in &all_decoys { 366 let bt = BlockType::FunctionType(catch_block_type_base + decoy_tag); 367 f.instruction(&Instruction::Block(bt)); 368 } 369 370 // try_table (empty body type -- all exits via branches) 371 f.instruction(&Instruction::TryTable( 372 BlockType::Empty, 373 Cow::Borrowed(&catches), 374 )); 375 376 // Body: call next deeper function 377 let next_func = scenario_base + d + 1; 378 f.instruction(&Instruction::Call(next_func)); 379 380 // Normal completion (no exception): push 0, branch to $result 381 f.instruction(&Instruction::I32Const(0)); 382 f.instruction(&Instruction::Br(br_result_label)); 383 384 f.instruction(&Instruction::End); // end try_table 385 // Normal flow always exits via br above; make fall-through unreachable 386 f.instruction(&Instruction::Unreachable); 387 388 // End decoy blocks (innermost first) — drop all payload, push -1 389 // After ending $decoy_{di}, we're inside $decoy_{di-1} 390 // (or $catch if di==0). Depth to $result = di + 1. 391 for di in (0..num_decoys).rev() { 392 f.instruction(&Instruction::End); // end block $decoy_{di} 393 // Drop the caught payload values 394 let decoy_tag = all_decoys[di]; 395 let decoy_sig = &self.tag_sigs[usize::try_from(decoy_tag).unwrap()]; 396 for _ in &decoy_sig.params { 397 f.instruction(&Instruction::Drop); 398 } 399 // Wrong tag caught -- return -1 400 f.instruction(&Instruction::I32Const(-1)); 401 let depth_to_result = u32::try_from(di).unwrap() + 1; 402 f.instruction(&Instruction::Br(depth_to_result)); 403 } 404 405 // End $catch block -- verify payload and return 1 406 f.instruction(&Instruction::End); // end block $catch 407 match scenario.catch_kind { 408 CatchKind::Catch => { 409 // All tag param values are on the stack. 410 // Verify the first i32 value if present, drop the rest. 411 let nparams = tag_sig.params.len(); 412 // Drop all values from top (last param) down to first 413 for _ in 1..nparams { 414 f.instruction(&Instruction::Drop); 415 } 416 // First param is now on top 417 if tag_sig.params[0] == SimpleValType::I32 { 418 let idx = 419 scenario.throw_tag * u32::try_from(MAX_TAG_PARAMS).unwrap(); 420 let expected = 0x1000_i32.wrapping_add(i32::try_from(idx).unwrap()); 421 f.instruction(&Instruction::I32Const(expected)); 422 f.instruction(&Instruction::Call(check_func_idx)); 423 } else { 424 f.instruction(&Instruction::Drop); 425 } 426 f.instruction(&Instruction::I32Const(1)); 427 } 428 CatchKind::CatchAll => { 429 f.instruction(&Instruction::I32Const(1)); 430 } 431 } 432 433 f.instruction(&Instruction::End); // end block $result 434 f.instruction(&Instruction::End); // end function 435 code.function(&f); 436 } else if d < scenario.throw_depth && d < scenario.catch_depth { 437 // -- Relay above handler: calls next deeper, returns i32 -- 438 functions.function(fn_type_void_to_i32); 439 let mut f = Function::new(vec![]); 440 let next_func = scenario_base + d + 1; 441 f.instruction(&Instruction::Call(next_func)); 442 f.instruction(&Instruction::End); 443 code.function(&f); 444 } else if d < scenario.throw_depth { 445 // -- Relay between handler and thrower: void -- 446 // These functions will never return normally (thrower always throws), 447 // but the validator needs the types to be correct. 448 functions.function(fn_type_void_to_void); 449 let mut f = Function::new(vec![]); 450 let next_func = scenario_base + d + 1; 451 f.instruction(&Instruction::Call(next_func)); 452 f.instruction(&Instruction::End); 453 code.function(&f); 454 } else { 455 // -- Dead code beyond throw depth -- 456 functions.function(fn_type_void_to_void); 457 let mut f = Function::new(vec![]); 458 f.instruction(&Instruction::Unreachable); 459 f.instruction(&Instruction::End); 460 code.function(&f); 461 } 462 } 463 } 464 465 // -- "run" function: calls each scenario entry, sums results -- 466 functions.function(fn_type_void_to_i32); 467 { 468 let mut f = Function::new(vec![(1, ValType::I32)]); 469 for si in 0..num_scenarios { 470 let scenario_entry = import_count + si * funcs_per_scenario; 471 f.instruction(&Instruction::Call(scenario_entry)); 472 f.instruction(&Instruction::LocalGet(0)); 473 f.instruction(&Instruction::I32Add); 474 f.instruction(&Instruction::LocalSet(0)); 475 } 476 f.instruction(&Instruction::LocalGet(0)); 477 f.instruction(&Instruction::End); 478 code.function(&f); 479 } 480 481 let mut exports = ExportSection::new(); 482 exports.export("run", ExportKind::Func, run_func_idx); 483 484 module 485 .section(&types) 486 .section(&imports) 487 .section(&functions) 488 .section(&tags) 489 .section(&exports) 490 .section(&code); 491 492 module.finish() 493 } 494 495 /// Pop the last scenario. Returns true if one was removed. pop(&mut self) -> bool496 pub fn pop(&mut self) -> bool { 497 self.scenarios.pop().is_some() 498 } 499 500 /// Number of scenarios; expected return value from "run" when all 501 /// catches succeed. expected_result(&mut self) -> i32502 pub fn expected_result(&mut self) -> i32 { 503 self.fixup(); 504 i32::try_from(self.scenarios.len()).unwrap() 505 } 506 } 507 508 /// Mutator for unit-variant enums ([`SimpleValType`] and [`CatchKind`]), 509 /// which need manual impls because `#[derive(Mutate)]` doesn't switch 510 /// between variants. 511 #[derive(Debug, Default)] 512 pub struct EnumMutator; 513 514 impl Mutate<SimpleValType> for EnumMutator { mutate(&mut self, c: &mut Candidates<'_>, value: &mut SimpleValType) -> MutResult<()>515 fn mutate(&mut self, c: &mut Candidates<'_>, value: &mut SimpleValType) -> MutResult<()> { 516 c.mutation(|ctx| { 517 let choices = [ 518 SimpleValType::I32, 519 SimpleValType::I64, 520 SimpleValType::F32, 521 SimpleValType::F64, 522 ]; 523 *value = *ctx.rng().choose(&choices).unwrap(); 524 Ok(()) 525 })?; 526 Ok(()) 527 } 528 } 529 530 impl Generate<SimpleValType> for EnumMutator { generate(&mut self, ctx: &mut Context) -> MutResult<SimpleValType>531 fn generate(&mut self, ctx: &mut Context) -> MutResult<SimpleValType> { 532 let choices = [ 533 SimpleValType::I32, 534 SimpleValType::I64, 535 SimpleValType::F32, 536 SimpleValType::F64, 537 ]; 538 Ok(*ctx.rng().choose(&choices).unwrap()) 539 } 540 } 541 542 impl DefaultMutate for SimpleValType { 543 type DefaultMutate = EnumMutator; 544 } 545 546 impl Mutate<CatchKind> for EnumMutator { mutate(&mut self, c: &mut Candidates<'_>, value: &mut CatchKind) -> MutResult<()>547 fn mutate(&mut self, c: &mut Candidates<'_>, value: &mut CatchKind) -> MutResult<()> { 548 c.mutation(|ctx| { 549 let choices = [CatchKind::Catch, CatchKind::CatchAll]; 550 *value = *ctx.rng().choose(&choices).unwrap(); 551 Ok(()) 552 })?; 553 Ok(()) 554 } 555 } 556 557 impl Generate<CatchKind> for EnumMutator { generate(&mut self, ctx: &mut Context) -> MutResult<CatchKind>558 fn generate(&mut self, ctx: &mut Context) -> MutResult<CatchKind> { 559 let choices = [CatchKind::Catch, CatchKind::CatchAll]; 560 Ok(*ctx.rng().choose(&choices).unwrap()) 561 } 562 } 563 564 impl DefaultMutate for CatchKind { 565 type DefaultMutate = EnumMutator; 566 } 567 568 impl Generate<TagSig> for TagSigMutator { generate(&mut self, ctx: &mut Context) -> MutResult<TagSig>569 fn generate(&mut self, ctx: &mut Context) -> MutResult<TagSig> { 570 let count = ctx.rng().gen_index(MAX_TAG_PARAMS).unwrap_or(0) + 1; 571 let params = (0..count) 572 .map(|_| EnumMutator.generate(ctx)) 573 .collect::<MutResult<Vec<_>>>()?; 574 Ok(TagSig { params }) 575 } 576 } 577 578 impl Generate<Scenario> for ScenarioMutator { generate(&mut self, ctx: &mut Context) -> MutResult<Scenario>579 fn generate(&mut self, ctx: &mut Context) -> MutResult<Scenario> { 580 let mut m = mutatis::mutators::u32(); 581 Ok(Scenario { 582 throw_tag: mutatis::Generate::<u32>::generate(&mut m, ctx)?, 583 throw_depth: mutatis::Generate::<u32>::generate(&mut m, ctx)?, 584 catch_depth: mutatis::Generate::<u32>::generate(&mut m, ctx)?, 585 catch_kind: EnumMutator.generate(ctx)?, 586 decoy_catches: vec![], 587 decoy_catches_after: vec![], 588 }) 589 } 590 } 591 592 impl Generate<ExceptionOps> for ExceptionOpsMutator { generate(&mut self, _ctx: &mut Context) -> MutResult<ExceptionOps>593 fn generate(&mut self, _ctx: &mut Context) -> MutResult<ExceptionOps> { 594 let mut ops = ExceptionOps::default(); 595 let mut session = mutatis::Session::new(); 596 for _ in 0..32 { 597 session.mutate(&mut ops)?; 598 } 599 Ok(ops) 600 } 601 } 602 603 #[cfg(test)] 604 mod tests { 605 use super::*; 606 use wasmparser::WasmFeatures; 607 608 #[test] always_produces_valid_wasm()609 fn always_produces_valid_wasm() { 610 mutatis::check::Check::new() 611 .iters(200) 612 .run(|ops: &ExceptionOps| { 613 let mut ops = ops.clone(); 614 let wasm = ops.to_wasm_binary(); 615 let features = WasmFeatures::EXCEPTIONS 616 | WasmFeatures::GC_TYPES 617 | WasmFeatures::REFERENCE_TYPES 618 | WasmFeatures::MULTI_VALUE 619 | WasmFeatures::FLOATS 620 | WasmFeatures::SIMD; 621 let mut validator = wasmparser::Validator::new_with_features(features); 622 validator 623 .validate_all(&wasm) 624 .map(|_| ()) 625 .map_err(|e| format!("{e}\n{ops:#?}")) 626 }) 627 .unwrap(); 628 } 629 } 630