1#![cfg_attr(not(feature = "clap"), deny(missing_docs))]
11
12mod error;
13mod info;
14mod module;
15mod mutators;
16
17pub use error::*;
18
19use crate::mutators::{
20    Item, add_function::AddFunctionMutator, add_type::AddTypeMutator,
21    codemotion::CodemotionMutator, custom::AddCustomSectionMutator, custom::CustomSectionMutator,
22    custom::ReorderCustomSectionMutator, function_body_unreachable::FunctionBodyUnreachable,
23    modify_const_exprs::ConstExpressionMutator, modify_data::ModifyDataMutator,
24    peephole::PeepholeMutator, remove_export::RemoveExportMutator, remove_item::RemoveItemMutator,
25    remove_section::RemoveSection, rename_export::RenameExportMutator, snip_function::SnipMutator,
26    start::RemoveStartSection,
27};
28use info::ModuleInfo;
29use mutators::Mutator;
30use rand::{Rng, SeedableRng, rngs::SmallRng};
31use std::sync::Arc;
32
33#[cfg(feature = "clap")]
34use clap::Parser;
35
36#[cfg_attr(
39    not(feature = "clap"),
40    doc = r###"
41A WebAssembly test case mutator.
42
43This is the main entry point into this crate. It provides various methods for
44configuring what kinds of mutations might be applied to the input Wasm. Once
45configured, you can apply a transformation to the input Wasm via the
46[`run`][crate::WasmMutate::run] method.
47
48# Example
49
50```
51# fn _foo() -> anyhow::Result<()> {
52use wasm_mutate::WasmMutate;
53
54let input_wasm = wat::parse_str(r#"
55           (module
56            (func (export "hello") (result i32)
57             (i32.const 1234)
58            )
59           )
60           "#)?;
61
62// Create a `WasmMutate` builder and configure it.
63let mut mutate = WasmMutate::default();
64mutate
65    // Set the RNG seed.
66    .seed(42)
67    // Allow mutations that change the semantics of the Wasm module.
68    .preserve_semantics(false)
69    // Use at most this much "fuel" when trying to mutate the Wasm module before
70    // giving up.
71    .fuel(1_000);
72
73// Run the configured `WasmMutate` to get a sequence of mutations of the input
74// Wasm!
75for mutated_wasm in mutate.run(&input_wasm)? {
76    let mutated_wasm = mutated_wasm?;
77    // Feed `mutated_wasm` into your tests...
78}
79# Ok(())
80# }
81```
82"###
83)]
84#[cfg_attr(feature = "clap", derive(Parser))]
85#[derive(Clone)]
86pub struct WasmMutate<'wasm> {
87    #[cfg_attr(feature = "clap", clap(short, long, default_value = "42"))]
91    seed: u64,
92
93    #[cfg_attr(feature = "clap", clap(long))]
95    preserve_semantics: bool,
96
97    #[cfg_attr(
99        feature = "clap",
100        clap(
101            short,
102            long,
103            default_value = "18446744073709551615", )
105    )]
106    fuel: u64,
107
108    #[cfg_attr(feature = "clap", clap(long))]
111    reduce: bool,
112
113    #[cfg_attr(feature = "clap", clap(skip = None))]
116    raw_mutate_func: Option<Arc<dyn Fn(&mut Vec<u8>, usize) -> Result<()>>>,
117
118    #[cfg_attr(feature = "clap", clap(skip = None))]
119    rng: Option<SmallRng>,
120
121    #[cfg_attr(feature = "clap", clap(skip = None))]
122    info: Option<ModuleInfo<'wasm>>,
123}
124
125impl Default for WasmMutate<'_> {
126    fn default() -> Self {
127        let seed = 3;
128        WasmMutate {
129            seed,
130            preserve_semantics: false,
131            reduce: false,
132            raw_mutate_func: None,
133            fuel: u64::MAX,
134            rng: None,
135            info: None,
136        }
137    }
138}
139
140impl<'wasm> WasmMutate<'wasm> {
141    pub fn seed(&mut self, seed: u64) -> &mut Self {
146        self.seed = seed;
147        self
148    }
149
150    pub fn preserve_semantics(&mut self, preserve_semantics: bool) -> &mut Self {
153        self.preserve_semantics = preserve_semantics;
154        self
155    }
156
157    pub fn fuel(&mut self, fuel: u64) -> &mut Self {
159        self.fuel = fuel;
160        self
161    }
162
163    pub fn reduce(&mut self, reduce: bool) -> &mut Self {
169        self.reduce = reduce;
170        self
171    }
172
173    pub fn raw_mutate_func(
186        &mut self,
187        raw_mutate_func: Option<Arc<dyn Fn(&mut Vec<u8>, usize) -> Result<()>>>,
188    ) -> &mut Self {
189        self.raw_mutate_func = raw_mutate_func;
190        self
191    }
192
193    pub(crate) fn consume_fuel(&mut self, qt: u64) -> Result<()> {
194        if qt > self.fuel {
195            log::info!("Out of fuel");
196            return Err(Error::out_of_fuel());
197        }
198        self.fuel -= qt;
199        Ok(())
200    }
201
202    pub fn run<'a>(
204        &'a mut self,
205        input_wasm: &'wasm [u8],
206    ) -> Result<Box<dyn Iterator<Item = Result<Vec<u8>>> + 'a>> {
207        self.setup(input_wasm)?;
208
209        const MUTATORS: &[&dyn Mutator] = &[
210            &PeepholeMutator::new(2),
211            &RemoveExportMutator,
212            &RenameExportMutator { max_name_size: 100 },
213            &SnipMutator,
214            &CodemotionMutator,
215            &FunctionBodyUnreachable,
216            &AddCustomSectionMutator,
217            &ReorderCustomSectionMutator,
218            &CustomSectionMutator,
219            &AddTypeMutator {
220                max_params: 20,
221                max_results: 20,
222            },
223            &AddFunctionMutator,
224            &RemoveSection::Custom,
225            &RemoveSection::Empty,
226            &ConstExpressionMutator::Global,
227            &ConstExpressionMutator::ElementOffset,
228            &ConstExpressionMutator::ElementFunc,
229            &RemoveItemMutator(Item::Function),
230            &RemoveItemMutator(Item::Global),
231            &RemoveItemMutator(Item::Memory),
232            &RemoveItemMutator(Item::Table),
233            &RemoveItemMutator(Item::Type),
234            &RemoveItemMutator(Item::Data),
235            &RemoveItemMutator(Item::Element),
236            &RemoveItemMutator(Item::Tag),
237            &ModifyDataMutator {
238                max_data_size: 10 << 20, },
240            &RemoveStartSection,
241        ];
242
243        let start = self.rng().random_range(0..MUTATORS.len());
245        for m in MUTATORS.iter().cycle().skip(start).take(MUTATORS.len()) {
246            let can_mutate = m.can_mutate(self);
247            log::trace!("Can `{}` mutate? {}", m.name(), can_mutate);
248            if !can_mutate {
249                continue;
250            }
251            log::debug!("attempting to mutate with `{}`", m.name());
252            match m.mutate(self) {
253                Ok(iter) => {
254                    log::debug!("mutator `{}` succeeded", m.name());
255                    return Ok(Box::new(iter.into_iter().map(|r| r.map(|m| m.finish()))));
256                }
257                Err(e) => {
258                    log::debug!("mutator `{}` failed: {}", m.name(), e);
259                    return Err(e);
260                }
261            }
262        }
263
264        Err(Error::no_mutations_applicable())
265    }
266
267    fn setup(&mut self, input_wasm: &'wasm [u8]) -> Result<()> {
268        self.info = Some(ModuleInfo::new(input_wasm)?);
269        self.rng = Some(SmallRng::seed_from_u64(self.seed));
270        Ok(())
271    }
272
273    pub(crate) fn rng(&mut self) -> &mut SmallRng {
274        self.rng.as_mut().unwrap()
275    }
276
277    pub(crate) fn info(&self) -> &ModuleInfo<'wasm> {
278        self.info.as_ref().unwrap()
279    }
280
281    fn raw_mutate(&mut self, data: &mut Vec<u8>, max_size: usize) -> Result<()> {
282        if let Some(mutate) = &self.raw_mutate_func {
284            return mutate(data, max_size);
285        }
286
287        let a = self.rng().random_range(0..=data.len());
293        let b = self.rng().random_range(0..=data.len());
294        let start = a.min(b);
295        let end = a.max(b);
296
297        let max_size = if self.reduce || self.rng().random() {
301            0
302        } else {
303            max_size
304        };
305        let len = self
306            .rng()
307            .random_range(0..=end - start + max_size.saturating_sub(data.len()));
308
309        data.splice(start..end, self.rng().random_iter().take(len));
312
313        Ok(())
314    }
315}
316
317#[cfg(test)]
318pub(crate) fn validate(bytes: &[u8]) {
319    use wasmparser::WasmFeatures;
320
321    let mut validator = wasmparser::Validator::new_with_features(
322        WasmFeatures::default() | WasmFeatures::MEMORY64 | WasmFeatures::MULTI_MEMORY,
323    );
324    let err = match validator.validate_all(bytes) {
325        Ok(_) => return,
326        Err(e) => e,
327    };
328    drop(std::fs::write("test.wasm", &bytes));
329    if let Ok(text) = wasmprinter::print_bytes(bytes) {
330        drop(std::fs::write("test.wat", &text));
331    }
332
333    panic!("wasm failed to validate: {err} (written to test.wasm)");
334}