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