#![cfg_attr(not(feature = "clap"), deny(missing_docs))]
mod error;
mod info;
mod module;
mod mutators;
pub use error::*;
use crate::mutators::{
add_function::AddFunctionMutator, add_type::AddTypeMutator, codemotion::CodemotionMutator,
custom::AddCustomSectionMutator, custom::CustomSectionMutator,
custom::ReorderCustomSectionMutator, function_body_unreachable::FunctionBodyUnreachable,
modify_const_exprs::ConstExpressionMutator, modify_data::ModifyDataMutator,
peephole::PeepholeMutator, remove_export::RemoveExportMutator, remove_item::RemoveItemMutator,
remove_section::RemoveSection, rename_export::RenameExportMutator, snip_function::SnipMutator,
Item,
};
use info::ModuleInfo;
use mutators::Mutator;
use rand::{rngs::SmallRng, Rng, SeedableRng};
use std::sync::Arc;
#[cfg(feature = "clap")]
use clap::Parser;
#[cfg_attr(
not(feature = "clap"),
doc = r###"
A WebAssembly test case mutator.
This is the main entry point into this crate. It provides various methods for
configuring what kinds of mutations might be applied to the input Wasm. Once
configured, you can apply a transformation to the input Wasm via the
[`run`][crate::WasmMutate::run] method.
# Example
```
# fn _foo() -> anyhow::Result<()> {
use wasm_mutate::WasmMutate;
let input_wasm = wat::parse_str(r#"
(module
(func (export "hello") (result i32)
(i32.const 1234)
)
)
"#)?;
// Create a `WasmMutate` builder and configure it.
let mut mutate = WasmMutate::default();
mutate
// Set the RNG seed.
.seed(42)
// Allow mutations that change the semantics of the Wasm module.
.preserve_semantics(false)
// Use at most this much "fuel" when trying to mutate the Wasm module before
// giving up.
.fuel(1_000);
// Run the configured `WasmMutate` to get a sequence of mutations of the input
// Wasm!
for mutated_wasm in mutate.run(&input_wasm)? {
let mutated_wasm = mutated_wasm?;
// Feed `mutated_wasm` into your tests...
}
# Ok(())
# }
```
"###
)]
#[cfg_attr(feature = "clap", derive(Parser))]
#[derive(Clone)]
pub struct WasmMutate<'wasm> {
#[cfg_attr(feature = "clap", clap(short, long, default_value = "42"))]
seed: u64,
#[cfg_attr(feature = "clap", clap(long))]
preserve_semantics: bool,
#[cfg_attr(
feature = "clap",
clap(
short,
long,
default_value = "18446744073709551615", )
)]
fuel: u64,
#[cfg_attr(feature = "clap", clap(long))]
reduce: bool,
#[cfg_attr(feature = "clap", clap(skip = None))]
raw_mutate_func: Option<Arc<dyn Fn(&mut Vec<u8>, usize) -> Result<()>>>,
#[cfg_attr(feature = "clap", clap(skip = None))]
rng: Option<SmallRng>,
#[cfg_attr(feature = "clap", clap(skip = None))]
info: Option<ModuleInfo<'wasm>>,
}
impl Default for WasmMutate<'_> {
fn default() -> Self {
let seed = 3;
WasmMutate {
seed,
preserve_semantics: false,
reduce: false,
raw_mutate_func: None,
fuel: u64::MAX,
rng: None,
info: None,
}
}
}
impl<'wasm> WasmMutate<'wasm> {
pub fn seed(&mut self, seed: u64) -> &mut Self {
self.seed = seed;
self
}
pub fn preserve_semantics(&mut self, preserve_semantics: bool) -> &mut Self {
self.preserve_semantics = preserve_semantics;
self
}
pub fn fuel(&mut self, fuel: u64) -> &mut Self {
self.fuel = fuel;
self
}
pub fn reduce(&mut self, reduce: bool) -> &mut Self {
self.reduce = reduce;
self
}
pub fn raw_mutate_func(
&mut self,
raw_mutate_func: Option<Arc<dyn Fn(&mut Vec<u8>, usize) -> Result<()>>>,
) -> &mut Self {
self.raw_mutate_func = raw_mutate_func;
self
}
pub(crate) fn consume_fuel(&mut self, qt: u64) -> Result<()> {
if qt > self.fuel {
log::info!("Out of fuel");
return Err(Error::out_of_fuel());
}
self.fuel -= qt;
Ok(())
}
pub fn run<'a>(
&'a mut self,
input_wasm: &'wasm [u8],
) -> Result<Box<dyn Iterator<Item = Result<Vec<u8>>> + 'a>> {
self.setup(input_wasm)?;
const MUTATORS: &[&dyn Mutator] = &[
&PeepholeMutator::new(2),
&RemoveExportMutator,
&RenameExportMutator { max_name_size: 100 },
&SnipMutator,
&CodemotionMutator,
&FunctionBodyUnreachable,
&AddCustomSectionMutator,
&ReorderCustomSectionMutator,
&CustomSectionMutator,
&AddTypeMutator {
max_params: 20,
max_results: 20,
},
&AddFunctionMutator,
&RemoveSection::Custom,
&RemoveSection::Empty,
&ConstExpressionMutator::Global,
&ConstExpressionMutator::ElementOffset,
&ConstExpressionMutator::ElementFunc,
&RemoveItemMutator(Item::Function),
&RemoveItemMutator(Item::Global),
&RemoveItemMutator(Item::Memory),
&RemoveItemMutator(Item::Table),
&RemoveItemMutator(Item::Type),
&RemoveItemMutator(Item::Data),
&RemoveItemMutator(Item::Element),
&RemoveItemMutator(Item::Tag),
&ModifyDataMutator {
max_data_size: 10 << 20, },
];
let start = self.rng().gen_range(0..MUTATORS.len());
for m in MUTATORS.iter().cycle().skip(start).take(MUTATORS.len()) {
let can_mutate = m.can_mutate(self);
log::trace!("Can `{}` mutate? {}", m.name(), can_mutate);
if !can_mutate {
continue;
}
log::debug!("attempting to mutate with `{}`", m.name());
match m.mutate(self) {
Ok(iter) => {
log::debug!("mutator `{}` succeeded", m.name());
return Ok(Box::new(iter.into_iter().map(|r| r.map(|m| m.finish()))));
}
Err(e) => {
log::debug!("mutator `{}` failed: {}", m.name(), e);
return Err(e);
}
}
}
Err(Error::no_mutations_applicable())
}
fn setup(&mut self, input_wasm: &'wasm [u8]) -> Result<()> {
self.info = Some(ModuleInfo::new(input_wasm)?);
self.rng = Some(SmallRng::seed_from_u64(self.seed));
Ok(())
}
pub(crate) fn rng(&mut self) -> &mut SmallRng {
self.rng.as_mut().unwrap()
}
pub(crate) fn info(&self) -> &ModuleInfo<'wasm> {
self.info.as_ref().unwrap()
}
fn raw_mutate(&mut self, data: &mut Vec<u8>, max_size: usize) -> Result<()> {
if let Some(mutate) = &self.raw_mutate_func {
return mutate(data, max_size);
}
let a = self.rng().gen_range(0..=data.len());
let b = self.rng().gen_range(0..=data.len());
let start = a.min(b);
let end = a.max(b);
let max_size = if self.reduce || self.rng().gen() {
0
} else {
max_size
};
let len = self
.rng()
.gen_range(0..=end - start + max_size.saturating_sub(data.len()));
data.splice(
start..end,
self.rng()
.sample_iter(rand::distributions::Standard)
.take(len),
);
Ok(())
}
}
#[cfg(test)]
pub(crate) fn validate(bytes: &[u8]) {
use wasmparser::WasmFeatures;
let mut validator = wasmparser::Validator::new_with_features(
WasmFeatures::default() | WasmFeatures::MEMORY64 | WasmFeatures::MULTI_MEMORY,
);
let err = match validator.validate_all(bytes) {
Ok(_) => return,
Err(e) => e,
};
drop(std::fs::write("test.wasm", &bytes));
if let Ok(text) = wasmprinter::print_bytes(bytes) {
drop(std::fs::write("test.wat", &text));
}
panic!("wasm failed to validate: {} (written to test.wasm)", err);
}