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 add_function::AddFunctionMutator, add_type::AddTypeMutator, codemotion::CodemotionMutator,
21 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 Item,
27};
28use info::ModuleInfo;
29use mutators::Mutator;
30use rand::{rngs::SmallRng, Rng, SeedableRng};
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 ];
241
242 let start = self.rng().gen_range(0..MUTATORS.len());
244 for m in MUTATORS.iter().cycle().skip(start).take(MUTATORS.len()) {
245 let can_mutate = m.can_mutate(self);
246 log::trace!("Can `{}` mutate? {}", m.name(), can_mutate);
247 if !can_mutate {
248 continue;
249 }
250 log::debug!("attempting to mutate with `{}`", m.name());
251 match m.mutate(self) {
252 Ok(iter) => {
253 log::debug!("mutator `{}` succeeded", m.name());
254 return Ok(Box::new(iter.into_iter().map(|r| r.map(|m| m.finish()))));
255 }
256 Err(e) => {
257 log::debug!("mutator `{}` failed: {}", m.name(), e);
258 return Err(e);
259 }
260 }
261 }
262
263 Err(Error::no_mutations_applicable())
264 }
265
266 fn setup(&mut self, input_wasm: &'wasm [u8]) -> Result<()> {
267 self.info = Some(ModuleInfo::new(input_wasm)?);
268 self.rng = Some(SmallRng::seed_from_u64(self.seed));
269 Ok(())
270 }
271
272 pub(crate) fn rng(&mut self) -> &mut SmallRng {
273 self.rng.as_mut().unwrap()
274 }
275
276 pub(crate) fn info(&self) -> &ModuleInfo<'wasm> {
277 self.info.as_ref().unwrap()
278 }
279
280 fn raw_mutate(&mut self, data: &mut Vec<u8>, max_size: usize) -> Result<()> {
281 if let Some(mutate) = &self.raw_mutate_func {
283 return mutate(data, max_size);
284 }
285
286 let a = self.rng().gen_range(0..=data.len());
292 let b = self.rng().gen_range(0..=data.len());
293 let start = a.min(b);
294 let end = a.max(b);
295
296 let max_size = if self.reduce || self.rng().r#gen() {
300 0
301 } else {
302 max_size
303 };
304 let len = self
305 .rng()
306 .gen_range(0..=end - start + max_size.saturating_sub(data.len()));
307
308 data.splice(
311 start..end,
312 self.rng()
313 .sample_iter(rand::distributions::Standard)
314 .take(len),
315 );
316
317 Ok(())
318 }
319}
320
321#[cfg(test)]
322pub(crate) fn validate(bytes: &[u8]) {
323 use wasmparser::WasmFeatures;
324
325 let mut validator = wasmparser::Validator::new_with_features(
326 WasmFeatures::default() | WasmFeatures::MEMORY64 | WasmFeatures::MULTI_MEMORY,
327 );
328 let err = match validator.validate_all(bytes) {
329 Ok(_) => return,
330 Err(e) => e,
331 };
332 drop(std::fs::write("test.wasm", &bytes));
333 if let Ok(text) = wasmprinter::print_bytes(bytes) {
334 drop(std::fs::write("test.wat", &text));
335 }
336
337 panic!("wasm failed to validate: {err} (written to test.wasm)");
338}