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