wasmtime_fuzzing/generators/
exception_ops.rs1use mutatis::{Candidates, Context, DefaultMutate, Generate, Mutate, Result as MutResult};
9use serde::{Deserialize, Serialize};
10use std::borrow::Cow;
11use wasm_encoder::{
12 BlockType, CodeSection, EntityType, ExportKind, ExportSection, Function, FunctionSection,
13 ImportSection, Instruction, Module, TagKind, TagSection, TagType, TypeSection, ValType,
14};
15
16pub const NUM_TAGS_MAX: u32 = 8;
18pub const CALL_DEPTH_MAX: u32 = 6;
20pub const MAX_SCENARIOS: usize = 16;
22pub const MAX_TAG_PARAMS: usize = 4;
24pub const MAX_DECOY_CATCHES: usize = 4;
26
27#[derive(Debug, Default, Clone, Serialize, Deserialize, Mutate)]
29pub struct ExceptionOpsLimits {
30 pub(crate) num_tags: u32,
32 pub(crate) call_depth: u32,
34}
35
36impl ExceptionOpsLimits {
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#[derive(Debug, Clone, Serialize, Deserialize, Mutate)]
45pub struct TagSig {
46 pub(crate) params: Vec<SimpleValType>,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
51#[allow(missing_docs, reason = "self-describing")]
52pub enum SimpleValType {
53 I32,
54 I64,
55 F32,
56 F64,
57}
58
59impl SimpleValType {
60 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
85pub enum CatchKind {
86 Catch,
88 CatchAll,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize, Mutate)]
94pub struct Scenario {
95 pub(crate) throw_tag: u32,
97 pub(crate) throw_depth: u32,
99 pub(crate) catch_depth: u32,
101 pub(crate) catch_kind: CatchKind,
103 pub(crate) decoy_catches: Vec<u32>,
107 pub(crate) decoy_catches_after: Vec<u32>,
111}
112
113#[derive(Debug, Default, Clone, Serialize, Deserialize, Mutate)]
115pub struct ExceptionOps {
116 pub(crate) limits: ExceptionOpsLimits,
117 pub(crate) tag_sigs: Vec<TagSig>,
118 pub(crate) scenarios: Vec<Scenario>,
119}
120
121impl ExceptionOps {
122 pub fn to_wasm_binary(&mut self) -> Vec<u8> {
124 self.fixup();
125 self.encode()
126 }
127
128 pub fn fixup(&mut self) {
130 self.limits.fixup();
131 let num_tags = usize::try_from(self.limits.num_tags).unwrap();
132
133 while self.tag_sigs.len() < num_tags {
135 self.tag_sigs.push(TagSig {
137 params: vec![SimpleValType::I32],
138 });
139 }
140 self.tag_sigs.truncate(num_tags);
141
142 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 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 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 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 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 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 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 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 functions.function(fn_type_void_to_void);
269 let mut f = Function::new(vec![]);
270 for (pi, param_ty) in tag_sig.params.iter().enumerate() {
271 f.instruction(¶m_ty.test_value(
272 scenario.throw_tag * u32::try_from(MAX_TAG_PARAMS).unwrap()
273 + u32::try_from(pi).unwrap(),
274 ));
275 }
276 f.instruction(&Instruction::Throw(scenario.throw_tag));
277 f.instruction(&Instruction::End);
278 code.function(&f);
279 } else if d == scenario.catch_depth {
280 functions.function(fn_type_void_to_i32);
282 let mut f = Function::new(vec![]);
283
284 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 let catch_label = u32::try_from(num_decoys).unwrap();
314 let result_label = u32::try_from(num_decoys + 1).unwrap();
315
316 let br_result_label = result_label + 1;
318
319 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 f.instruction(&Instruction::Block(BlockType::Result(ValType::I32)));
351
352 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 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 f.instruction(&Instruction::TryTable(
372 BlockType::Empty,
373 Cow::Borrowed(&catches),
374 ));
375
376 let next_func = scenario_base + d + 1;
378 f.instruction(&Instruction::Call(next_func));
379
380 f.instruction(&Instruction::I32Const(0));
382 f.instruction(&Instruction::Br(br_result_label));
383
384 f.instruction(&Instruction::End); f.instruction(&Instruction::Unreachable);
387
388 for di in (0..num_decoys).rev() {
392 f.instruction(&Instruction::End); 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 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 f.instruction(&Instruction::End); match scenario.catch_kind {
408 CatchKind::Catch => {
409 let nparams = tag_sig.params.len();
412 for _ in 1..nparams {
414 f.instruction(&Instruction::Drop);
415 }
416 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); f.instruction(&Instruction::End); code.function(&f);
436 } else if d < scenario.throw_depth && d < scenario.catch_depth {
437 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 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 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 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 pub fn pop(&mut self) -> bool {
497 self.scenarios.pop().is_some()
498 }
499
500 pub fn expected_result(&mut self) -> i32 {
503 self.fixup();
504 i32::try_from(self.scenarios.len()).unwrap()
505 }
506}
507
508#[derive(Debug, Default)]
512pub struct EnumMutator;
513
514impl Mutate<SimpleValType> for EnumMutator {
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
530impl Generate<SimpleValType> for EnumMutator {
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
542impl DefaultMutate for SimpleValType {
543 type DefaultMutate = EnumMutator;
544}
545
546impl Mutate<CatchKind> for EnumMutator {
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
557impl Generate<CatchKind> for EnumMutator {
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
564impl DefaultMutate for CatchKind {
565 type DefaultMutate = EnumMutator;
566}
567
568#[cfg(test)]
569mod tests {
570 use super::*;
571 use wasmparser::WasmFeatures;
572
573 #[test]
574 fn always_produces_valid_wasm() {
575 mutatis::check::Check::new()
576 .iters(200)
577 .run(|ops: &ExceptionOps| {
578 let mut ops = ops.clone();
579 let wasm = ops.to_wasm_binary();
580 let features = WasmFeatures::EXCEPTIONS
581 | WasmFeatures::GC_TYPES
582 | WasmFeatures::REFERENCE_TYPES
583 | WasmFeatures::MULTI_VALUE
584 | WasmFeatures::FLOATS
585 | WasmFeatures::SIMD;
586 let mut validator = wasmparser::Validator::new_with_features(features);
587 validator
588 .validate_all(&wasm)
589 .map(|_| ())
590 .map_err(|e| format!("{e}\n{ops:#?}"))
591 })
592 .unwrap();
593 }
594}