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(&param_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