1 #![no_main]
2 
3 use libfuzzer_sys::arbitrary::{Arbitrary, Unstructured};
4 use libfuzzer_sys::{fuzz_mutator, fuzz_target, fuzzer_mutate};
5 use mutatis::Session;
6 use postcard::{from_bytes, to_slice};
7 use rand::{Rng, SeedableRng};
8 use wasmtime_fuzzing::generators::ExceptionOps;
9 use wasmtime_fuzzing::oracles::exception_ops;
10 
11 fuzz_target!(|data: &[u8]| {
12     let Ok((seed, ops)) = postcard::from_bytes::<(u64, ExceptionOps)>(data) else {
13         return;
14     };
15 
16     let mut buf = [0u8; 1024];
17     let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
18     rng.fill(&mut buf);
19 
20     let u = Unstructured::new(&buf);
21     let Ok(config) = wasmtime_fuzzing::generators::Config::arbitrary_take_rest(u) else {
22         return;
23     };
24 
25     let _ = exception_ops(config, ops);
26 });
27 
28 fuzz_mutator!(|data: &mut [u8], size: usize, max_size: usize, seed: u32| {
29     let _ = env_logger::try_init();
30 
31     // With probability of about 1/8, use default mutator
32     if seed & 7 == 0 {
33         return fuzzer_mutate(data, size, max_size);
34     }
35 
36     // Try to decode using postcard; fallback to default input on failure
37     let mut tuple: (u64, ExceptionOps) = from_bytes(&data[..size]).ok().unwrap_or_default();
38 
39     let mut session = Session::new().seed(seed.into()).shrink(max_size < size);
40 
41     if session.mutate(&mut tuple).is_ok() {
42         loop {
43             if let Ok(encoded) = to_slice(&tuple, data) {
44                 return encoded.len();
45             }
46 
47             // Attempt to shrink scenarios if encoding fails (e.g., buffer too small)
48             if tuple.1.pop() {
49                 continue;
50             }
51 
52             break;
53         }
54     }
55 
56     // Fallback to default libfuzzer mutator
57     fuzzer_mutate(data, size, max_size)
58 });
59