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}