From 9f84c44d35bc6b6223f9fdb7a81def279861ec00 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 9 Dec 2021 11:46:23 -0800 Subject: [PATCH 1/7] wasm-mutate: Actually run `RemoveCustomSection` mutator --- crates/wasm-mutate/src/lib.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/wasm-mutate/src/lib.rs b/crates/wasm-mutate/src/lib.rs index b4a083746a..a39c2bde51 100644 --- a/crates/wasm-mutate/src/lib.rs +++ b/crates/wasm-mutate/src/lib.rs @@ -12,10 +12,10 @@ mod info; mod module; mod mutators; use crate::mutators::{ - codemotion::CodemotionMutator, data::RemoveDataSegment, elems::RemoveElemSegment, - function_body_unreachable::FunctionBodyUnreachable, peephole::PeepholeMutator, - remove_export::RemoveExportMutator, rename_export::RenameExportMutator, - snip_function::SnipMutator, + codemotion::CodemotionMutator, custom::RemoveCustomSection, data::RemoveDataSegment, + elems::RemoveElemSegment, function_body_unreachable::FunctionBodyUnreachable, + peephole::PeepholeMutator, remove_export::RemoveExportMutator, + rename_export::RenameExportMutator, snip_function::SnipMutator, }; use info::ModuleInfo; use mutators::Mutator; @@ -274,6 +274,7 @@ impl<'wasm> WasmMutate<'wasm> { FunctionBodyUnreachable, RemoveElemSegment, RemoveDataSegment, + RemoveCustomSection, ) ); From 3ede7650410e4ad62c58a43d85a0c4af531e953a Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 9 Dec 2021 12:44:31 -0800 Subject: [PATCH 2/7] wasm-mutate: Add more docs for the `Mutator` trait --- crates/wasm-mutate/src/mutators.rs | 60 ++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/crates/wasm-mutate/src/mutators.rs b/crates/wasm-mutate/src/mutators.rs index 22bb5e3036..90392e136d 100644 --- a/crates/wasm-mutate/src/mutators.rs +++ b/crates/wasm-mutate/src/mutators.rs @@ -31,19 +31,57 @@ pub mod rename_export; pub mod snip_function; pub mod start; +use std::borrow::Cow; + use super::Result; use crate::WasmMutate; use wasm_encoder::Module; use wasmparser::Operator; -/// This trait needs to be implemented for all mutators -/// -/// Take a look to the implementation of the -/// [RenameExportMutator][super::RenameExportMutator] implementation for a reference +/// A mutation that can be applied to a Wasm module to produce a new, mutated +/// Wasm module. pub trait Mutator { - /// Method where the mutation happpens + /// Can this `Mutator` *probably* be applied to the the given Wasm and + /// configuration? + /// + /// When checking Wasm applicability, these checks should be implemented as + /// quick, incomplete checks. For example, a mutator that removes a single + /// function at a time should *not* build the whole call graph to determine + /// if there are any functions that are dead code. Instead, it should just + /// check that the Wasm contains at least one function. (It can always + /// return an `Err` from the `mutate` method later, but we want to delay + /// expensive computations until if/when we've committed to applying a given + /// mutation). + /// + /// As an example of configuration checking, if a mutator adds new functions + /// to the Wasm, increasing the Wasm's size, it should check whether the + /// `WasmMutate` has been configured to only perform size-reducing + /// mutations, and if so return `false` here. + fn can_mutate(&self, config: &WasmMutate) -> bool; + + /// Run this mutation. + /// + /// Rather than returning a single, mutated module, we allow for mutators to + /// return many. /// - /// * `config` instance of WasmMutate + /// Mutators that return `Ok(iterator)` should always return an iterator + /// with at least one item. Mutators should never return an empty iterator, + /// instead they should return `Err(...)`. + /// + /// The iterators returned from this method must be *lazy*. Mutators should + /// *not* return `Ok(Box::new(vec![...].into_iter()))`. Only one mutation + /// should ever be computed at a time. Mutator implementations might find + /// `std::iter::from_fn` helpful. + /// + /// When should a mutator return a single item via `std::iter::once` versus + /// an iterator with many items? When the mutator builds up a bunch of state + /// that was expensive to build and can be reused, it should return an + /// iterator with many items that reuse that state. For example, the + /// peephole mutator's e-graph is expensive to build and should be reused. + /// If, however, the mutator doesn't build up state or its state is cheap to + /// recompute, then the mutator should return a single-item iterator with + /// `std::iter::once`, to give the fuzzer a chance to choose a new kind of + /// mutation. fn mutate<'a>( self, config: &'a mut WasmMutate, @@ -51,12 +89,10 @@ pub trait Mutator { where Self: Copy; - /// Returns if this mutator can be applied with the info and the byte range - /// in which it can be applied - fn can_mutate(&self, config: &WasmMutate) -> bool; - - /// Provides the name of the mutator, mostly used for debugging purposes - fn name(&self) -> String { + /// What is this mutator's name? + /// + /// This is only used for debugging and logging purposes. + fn name(&self) -> Cow<'static, str> { return std::any::type_name::().into(); } } From 15c9c5b3087719f683b30d1ef0699e39893b4339 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 9 Dec 2021 13:53:02 -0800 Subject: [PATCH 3/7] wasm-mutate: Clean up logging a little bit --- crates/wasm-mutate/src/lib.rs | 8 +- crates/wasm-mutate/src/mutators/codemotion.rs | 8 +- .../src/mutators/function_body_unreachable.rs | 7 +- crates/wasm-mutate/src/mutators/peephole.rs | 87 +++++++-------- .../wasm-mutate/src/mutators/remove_export.rs | 67 ++++++------ .../wasm-mutate/src/mutators/snip_function.rs | 100 +++++++++--------- crates/wasm-mutate/tests/tests.rs | 26 ++--- 7 files changed, 153 insertions(+), 150 deletions(-) diff --git a/crates/wasm-mutate/src/lib.rs b/crates/wasm-mutate/src/lib.rs index a39c2bde51..894589d82c 100644 --- a/crates/wasm-mutate/src/lib.rs +++ b/crates/wasm-mutate/src/lib.rs @@ -45,7 +45,7 @@ macro_rules! define_mutators { return Ok(Box::new(iter.into_iter().map(|r| r.map(|m| m.finish())))) } Err(e) => { - log::info!("mutator {} failed: {}; will try again", m.name(), e); + log::debug!("mutator {} failed: {}; will try again", m.name(), e); return Err(e); } } @@ -60,7 +60,7 @@ macro_rules! define_mutators { return Ok(Box::new(iter.into_iter().map(|r| r.map(|m| m.finish())))) } Err(e) => { - log::info!("mutator {} failed: {}; will try again", m.name(), e); + log::debug!("mutator {} failed: {}; will try again", m.name(), e); return Err(e); } } @@ -76,7 +76,7 @@ macro_rules! define_mutators { return Ok(Box::new(iter.into_iter().map(|r| r.map(|m| m.finish())))) } Err(e) => { - log::info!("mutator {} failed: {}; will try again", m.name(), e); + log::debug!("mutator {} failed: {}; will try again", m.name(), e); return Err(e); } } @@ -241,7 +241,7 @@ impl<'wasm> WasmMutate<'wasm> { pub(crate) fn consume_fuel(&self, qt: u64) -> Result<()> { if qt > self.fuel.get() { - log::info!("Resource limits reached!"); + log::info!("Out of fuel"); return Err(crate::Error::NoMutationsApplicable); // Replace by a TimeoutError type } self.fuel.set(self.fuel.get() - qt); diff --git a/crates/wasm-mutate/src/mutators/codemotion.rs b/crates/wasm-mutate/src/mutators/codemotion.rs index 2b2a5896ce..89ec81c6b3 100644 --- a/crates/wasm-mutate/src/mutators/codemotion.rs +++ b/crates/wasm-mutate/src/mutators/codemotion.rs @@ -35,12 +35,6 @@ use rand::{prelude::SliceRandom, Rng}; use wasm_encoder::{CodeSection, Function, Module, ValType}; use wasmparser::{CodeSectionReader, FunctionBody}; -// Hack to show debug messages in tests -#[cfg(not(test))] -use log::debug; -#[cfg(test)] -use std::println as debug; - /// Code motion meta mutator, it groups all code motion mutators and select a /// valid random one when an input Wasm binary is passed to it. #[derive(Clone, Copy)] @@ -156,7 +150,7 @@ impl Mutator for CodemotionMutator { for fidx in 0..config.info().function_count { let reader = sectionreader.read()?; if fidx == function_to_mutate { - debug!("Mutating function idx {:?}", fidx); + log::trace!("Mutating function {}", fidx); codes.function(&newfunc); } else { codes.raw(&code_section.data[reader.range().start..reader.range().end]); diff --git a/crates/wasm-mutate/src/mutators/function_body_unreachable.rs b/crates/wasm-mutate/src/mutators/function_body_unreachable.rs index b61df3ef66..e98a09c9ca 100644 --- a/crates/wasm-mutate/src/mutators/function_body_unreachable.rs +++ b/crates/wasm-mutate/src/mutators/function_body_unreachable.rs @@ -18,16 +18,20 @@ impl Mutator for FunctionBodyUnreachable { config: &mut WasmMutate<'a>, ) -> Result> + 'a>> { let mut codes = CodeSection::new(); + let code_section = config.info().get_code_section(); let mut reader = CodeSectionReader::new(code_section.data, 0)?; + let count = reader.get_count(); let function_to_mutate = config.rng().gen_range(0, count); + (0..count) .map(|i| { config.consume_fuel(1)?; + let f = reader.read().unwrap(); if i == function_to_mutate { - log::debug!("Changing function idx {:?}", i); + log::trace!("Mutating function {}", i); let locals = vec![]; let mut f = Function::new(locals); f.instruction(&Instruction::Unreachable); @@ -40,6 +44,7 @@ impl Mutator for FunctionBodyUnreachable { Ok(()) }) .collect::>>()?; + Ok(Box::new(std::iter::once(Ok(config .info() .replace_section(config.info().code.unwrap(), &codes))))) diff --git a/crates/wasm-mutate/src/mutators/peephole.rs b/crates/wasm-mutate/src/mutators/peephole.rs index c18e15f563..8ddbd691c1 100644 --- a/crates/wasm-mutate/src/mutators/peephole.rs +++ b/crates/wasm-mutate/src/mutators/peephole.rs @@ -14,39 +14,36 @@ //! of the top config `preserve_semantics`. //! //! # Example +//! //! ```ignore //! rules.extend(rewrite!("strength-reduction"; "(i32.shl ?x 1_i32)" <=> "(i32.mul ?x 2_i32)")); //! ``` //! -use crate::module::PrimitiveTypeInfo; -use crate::mutators::peephole::eggsy::analysis::PeepholeMutationAnalysis; -use crate::mutators::peephole::eggsy::encoder::expr2wasm::ResourceRequest; -use crate::mutators::peephole::eggsy::encoder::Encoder; -use crate::mutators::peephole::eggsy::expr_enumerator::lazy_expand_aux; -use crate::mutators::peephole::eggsy::lang::Lang; +pub mod dfg; +pub mod eggsy; +pub mod rules; + +use self::{ + dfg::DFGBuilder, + eggsy::{ + analysis::PeepholeMutationAnalysis, + encoder::{expr2wasm::ResourceRequest, Encoder}, + expr_enumerator::lazy_expand_aux, + lang::Lang, + }, +}; +use super::{Mutator, OperatorAndByteOffset}; +use crate::{ + module::{map_type, PrimitiveTypeInfo}, + ModuleInfo, Result, WasmMutate, +}; use egg::{Rewrite, Runner}; use rand::{prelude::SmallRng, Rng}; -use std::convert::TryFrom; +use std::{borrow::Cow, convert::TryFrom, fmt::Debug}; use wasm_encoder::{CodeSection, Function, GlobalSection, Instruction, Module, ValType}; use wasmparser::{CodeSectionReader, FunctionBody, GlobalSectionReader, LocalsReader}; -// Hack to show debug messages in tests -#[cfg(not(test))] -use log::debug; -#[cfg(test)] -use std::println as debug; - -use crate::{module::map_type, ModuleInfo, Result, WasmMutate}; - -use self::dfg::DFGBuilder; - -use super::{Mutator, OperatorAndByteOffset}; - -pub mod dfg; -pub mod eggsy; -pub mod rules; - /// This mutator applies a random peephole transformation to the input Wasm module #[derive(Clone, Copy)] pub struct PeepholeMutator { @@ -135,9 +132,11 @@ impl PeepholeMutator { let operatorscount = operators.len(); let mut opcode_to_mutate = config.rng().gen_range(0, operatorscount); - debug!( - "Function idx {} with {} operators. Selecting {}", - function_to_mutate, operatorscount, opcode_to_mutate + log::trace!( + "Selecting operator {}/{} from function {}", + opcode_to_mutate, + operatorscount, + function_to_mutate, ); let locals = self.get_func_locals( config.info(), @@ -156,8 +155,8 @@ impl PeepholeMutator { let basicblock = match basicblock { None => { - debug!( - "Basic block cannot be constructed for {:?}", + log::trace!( + "Basic block cannot be constructed for opcode {:?}", &operators[opcode_to_mutate] ); opcode_to_mutate = (opcode_to_mutate + 1) % operatorscount; @@ -170,7 +169,7 @@ impl PeepholeMutator { let minidfg = match minidfg { None => { - debug!("DFG cannot be constructed for {:?}", opcode_to_mutate); + log::trace!("DFG cannot be constructed for opcode {}", opcode_to_mutate); opcode_to_mutate = (opcode_to_mutate + 1) % operatorscount; count += 1; @@ -189,17 +188,19 @@ impl PeepholeMutator { let start = minidfg.get_expr(opcode_to_mutate); if !minidfg.is_subtree_consistent_from_root() { - debug!("{} is not consistent", start); + log::trace!("{} is not consistent", start); opcode_to_mutate = (opcode_to_mutate + 1) % operatorscount; count += 1; continue; }; - debug!( - "Trying to mutate \n{} at {} in fidx {}", - start.pretty(30), + log::trace!( + "Trying to mutate\n\ + {}\n\ + at opcode {} in function {}", + start.pretty(30).trim(), opcode_to_mutate, - function_to_mutate + function_to_mutate, ); let analysis = PeepholeMutationAnalysis::new( @@ -227,10 +228,11 @@ impl PeepholeMutator { continue; }; - debug!( - "Egraph built, nodes count {}", + log::trace!( + "Egraph built, nodes count = {}", egraph.total_number_of_nodes() ); + // At this point we spent some resource calculating basic block, // and constructing the egraph config.consume_fuel(1)?; @@ -302,12 +304,11 @@ impl PeepholeMutator { start = current_pos; } } + if needed_resources.len() > 0 { - debug!( - "Adding out of function resources allocation for {} resources", - needed_resources.len() - ); + log::trace!("Adding {} additional resources", needed_resources.len()); } + for resource in &needed_resources { match resource { ResourceRequest::Global { @@ -430,12 +431,12 @@ impl Mutator for PeepholeMutator { } } -use std::fmt::Debug; impl Debug for Box { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("Code mutator").finish() } } + pub(crate) trait CodeMutator { fn mutate( &self, @@ -457,8 +458,8 @@ pub(crate) trait CodeMutator { ) -> Result; /// Provides the name of the mutator, mostly used for debugging purposes - fn name(&self) -> String { - return format!("{:?}", std::any::type_name::()); + fn name(&self) -> Cow<'static, str> { + std::any::type_name::().into() } } diff --git a/crates/wasm-mutate/src/mutators/remove_export.rs b/crates/wasm-mutate/src/mutators/remove_export.rs index f8806665d5..05aaeef3f1 100644 --- a/crates/wasm-mutate/src/mutators/remove_export.rs +++ b/crates/wasm-mutate/src/mutators/remove_export.rs @@ -21,41 +21,40 @@ impl Mutator for RemoveExportMutator { let max_exports = reader.get_count() as u64; let skip_at = config.rng().gen_range(0, max_exports); - (0..max_exports) - .map(|i| { - config.consume_fuel(1)?; - let export = reader.read().unwrap(); - if skip_at != i { - // otherwise bypass - match export.kind { - wasmparser::ExternalKind::Function => { - exports.export(export.field, Export::Function(export.index)); - } - wasmparser::ExternalKind::Table => { - exports.export(export.field, Export::Table(export.index)); - } - wasmparser::ExternalKind::Memory => { - exports.export(export.field, Export::Memory(export.index)); - } - wasmparser::ExternalKind::Global => { - exports.export(export.field, Export::Global(export.index)); - } - wasmparser::ExternalKind::Module => { - exports.export(export.field, Export::Module(export.index)); - } - wasmparser::ExternalKind::Instance => { - exports.export(export.field, Export::Instance(export.index)); - } - _ => { - panic!("Unknown export {:?}", export) - } - } - } else { - log::debug!("Removing export {:?} idx {:?}", export, skip_at); + for i in 0..max_exports { + config.consume_fuel(1)?; + let export = reader.read().unwrap(); + + if skip_at == i { + log::trace!("Removing export {:?} at index {}", export, skip_at); + continue; + } + + match export.kind { + wasmparser::ExternalKind::Function => { + exports.export(export.field, Export::Function(export.index)); + } + wasmparser::ExternalKind::Table => { + exports.export(export.field, Export::Table(export.index)); + } + wasmparser::ExternalKind::Memory => { + exports.export(export.field, Export::Memory(export.index)); + } + wasmparser::ExternalKind::Global => { + exports.export(export.field, Export::Global(export.index)); } - Ok(()) - }) - .collect::>>()?; + wasmparser::ExternalKind::Module => { + exports.export(export.field, Export::Module(export.index)); + } + wasmparser::ExternalKind::Instance => { + exports.export(export.field, Export::Instance(export.index)); + } + _ => { + panic!("Unknown export {:?}", export) + } + } + } + Ok(Box::new(std::iter::once(Ok(config .info() .replace_section( diff --git a/crates/wasm-mutate/src/mutators/snip_function.rs b/crates/wasm-mutate/src/mutators/snip_function.rs index b8a7f3f4a9..048c0fb6e3 100644 --- a/crates/wasm-mutate/src/mutators/snip_function.rs +++ b/crates/wasm-mutate/src/mutators/snip_function.rs @@ -26,59 +26,61 @@ impl Mutator for SnipMutator { (function_to_mutate + config.info().imported_functions_count) as usize, ); - (0..count) - .map(|i| { - config.consume_fuel(1)?; - let f = reader.read().unwrap(); - if i == function_to_mutate { - log::debug!("Snip function idx {:?}", function_to_mutate); - let locals = vec![]; - let mut f = Function::new(locals); + for i in 0..count { + config.consume_fuel(1)?; + let f = reader.read().unwrap(); - match ftype { - TypeInfo::Func(t) => { - t.returns.iter().for_each(|primitive| match primitive { - PrimitiveTypeInfo::I32 => { - f.instruction(&Instruction::I32Const(0)); - } - PrimitiveTypeInfo::I64 => { - f.instruction(&Instruction::I64Const(0)); - } - PrimitiveTypeInfo::F32 => { - f.instruction(&Instruction::F32Const(0.0)); - } - PrimitiveTypeInfo::F64 => { - f.instruction(&Instruction::F64Const(0.0)); - } - PrimitiveTypeInfo::V128 => { - f.instruction(&Instruction::V128Const(0)); - } - PrimitiveTypeInfo::FuncRef => { - f.instruction(&Instruction::RefNull(ValType::FuncRef)); - } - PrimitiveTypeInfo::ExternRef => { - f.instruction(&Instruction::RefNull(ValType::ExternRef)); - } - PrimitiveTypeInfo::ExnRef => { - // TODO: not supported in `wasm-encoder` yet. - f.instruction(&Instruction::Unreachable); - } - PrimitiveTypeInfo::Empty | PrimitiveTypeInfo::Func => { - unreachable!() - } - }); - } - }; + if i != function_to_mutate { + codes.raw(&code_section.data[f.range().start..f.range().end]); + continue; + } + + log::trace!("Snipping function {}", function_to_mutate); - f.instruction(&Instruction::End); + let locals = vec![]; + let mut f = Function::new(locals); - codes.function(&f); - } else { - codes.raw(&code_section.data[f.range().start..f.range().end]); + match ftype { + TypeInfo::Func(t) => { + for primitive in t.returns.iter() { + match primitive { + PrimitiveTypeInfo::I32 => { + f.instruction(&Instruction::I32Const(0)); + } + PrimitiveTypeInfo::I64 => { + f.instruction(&Instruction::I64Const(0)); + } + PrimitiveTypeInfo::F32 => { + f.instruction(&Instruction::F32Const(0.0)); + } + PrimitiveTypeInfo::F64 => { + f.instruction(&Instruction::F64Const(0.0)); + } + PrimitiveTypeInfo::V128 => { + f.instruction(&Instruction::V128Const(0)); + } + PrimitiveTypeInfo::FuncRef => { + f.instruction(&Instruction::RefNull(ValType::FuncRef)); + } + PrimitiveTypeInfo::ExternRef => { + f.instruction(&Instruction::RefNull(ValType::ExternRef)); + } + PrimitiveTypeInfo::ExnRef => { + // TODO: not supported in `wasm-encoder` yet. + f.instruction(&Instruction::Unreachable); + } + PrimitiveTypeInfo::Empty | PrimitiveTypeInfo::Func => { + unreachable!() + } + } + } } - Ok(()) - }) - .collect::>>()?; + } + + f.instruction(&Instruction::End); + codes.function(&f); + } + Ok(Box::new(std::iter::once(Ok(config .info() .replace_section(config.info().code.unwrap(), &codes))))) diff --git a/crates/wasm-mutate/tests/tests.rs b/crates/wasm-mutate/tests/tests.rs index 8e834998dd..bdf7c2b631 100644 --- a/crates/wasm-mutate/tests/tests.rs +++ b/crates/wasm-mutate/tests/tests.rs @@ -1,7 +1,6 @@ use wasm_mutate::WasmMutate; use wasmparser::Validator; -// Copied from wasm-smith fn validate(validator: &mut Validator, bytes: &[u8]) { let err = match validator.validate_all(bytes) { Ok(()) => return, @@ -12,31 +11,34 @@ fn validate(validator: &mut Validator, bytes: &[u8]) { drop(std::fs::write("test.wat", &text)); } - panic!("wasm failed to validate {:?}", err); + panic!("Wasm failed to validate: {:?}", err); } #[test] fn integration_test() { let _ = env_logger::try_init(); + let wat = r#" - (module - - (func (export "exported_func") - nop - i32.const 42 - if - i32.const 98 - drop - end + (module + (func (export "exported_func") + nop + i32.const 42 + if + i32.const 98 + drop + end + ) ) - ) "#; let original = &wat::parse_str(wat).unwrap(); + let mut mutator = WasmMutate::default(); mutator.fuel(1000); mutator.seed(0); + // seed is zero, which means first mutator let start = std::time::Instant::now(); + let it = mutator.run(original).unwrap(); let mut count = 0; for mutated in it.take(100) { From 16eb497e956e32d28ab65bd03e9d3d988fd99eee Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 9 Dec 2021 15:32:45 -0800 Subject: [PATCH 4/7] wasm-mutate: Clean up the `Error` type and handling --- .../src/bin/wasm-mutate-stats.rs | 4 +- crates/wasm-mutate/src/error.rs | 96 ++++++++++++++----- crates/wasm-mutate/src/lib.rs | 12 ++- crates/wasm-mutate/src/module.rs | 68 ++++++------- crates/wasm-mutate/src/mutators/codemotion.rs | 4 +- .../mutators/codemotion/ir/parse_context.rs | 13 +-- crates/wasm-mutate/src/mutators/data.rs | 9 +- crates/wasm-mutate/src/mutators/elems.rs | 12 +-- crates/wasm-mutate/src/mutators/peephole.rs | 9 +- .../src/mutators/peephole/eggsy/analysis.rs | 3 +- .../peephole/eggsy/encoder/expr2wasm.rs | 36 +++---- fuzz/fuzz_targets/mutate.rs | 88 ++++++++--------- src/bin/wasm-mutate.rs | 85 +++++++--------- 13 files changed, 228 insertions(+), 211 deletions(-) diff --git a/crates/wasm-mutate-stats/src/bin/wasm-mutate-stats.rs b/crates/wasm-mutate-stats/src/bin/wasm-mutate-stats.rs index 07f2b36195..346bc2c84a 100644 --- a/crates/wasm-mutate-stats/src/bin/wasm-mutate-stats.rs +++ b/crates/wasm-mutate-stats/src/bin/wasm-mutate-stats.rs @@ -500,8 +500,8 @@ impl State { // First stage, generate and return the mutated let it = match wasmmutate.run(&wasmcp) { Ok(it) => it, - Err(e) => match e { - wasm_mutate::Error::NoMutationsApplicable => { + Err(e) => match e.kind() { + wasm_mutate::ErrorKind::NoMutationsApplicable => { Box::new(std::iter::once(Ok(wasm.clone()))) } _ => { diff --git a/crates/wasm-mutate/src/error.rs b/crates/wasm-mutate/src/error.rs index 3eba9162d8..6f94bc6191 100644 --- a/crates/wasm-mutate/src/error.rs +++ b/crates/wasm-mutate/src/error.rs @@ -1,31 +1,83 @@ -use wasm_encoder::ValType; -use wasmparser::{GlobalType, Type}; - /// An error encountered when choosing or applying a Wasm mutation. #[derive(thiserror::Error, Debug)] -pub enum Error { - /// The input Wasm module did not parse or validate okay. - #[error("Failed to parse or validate the input Wasm module.")] +#[error(transparent)] +pub struct Error { + kind: Box, +} + +impl Error { + /// Construct a new `Error` from an `ErrorKind`. + pub fn new(kind: ErrorKind) -> Self { + kind.into() + } + + /// Construct a new parse error. + pub fn parse(err: wasmparser::BinaryReaderError) -> Self { + err.into() + } + + /// Construct a "no mutations applicable" error. + pub fn no_mutations_applicable() -> Self { + ErrorKind::NoMutationsApplicable.into() + } + + /// Construct an "out of fuel" error. + pub fn out_of_fuel() -> Self { + ErrorKind::OutOfFuel.into() + } + + /// Construct an unsupported error. + pub fn unsupported(msg: impl Into) -> Self { + ErrorKind::Unsupported(msg.into()).into() + } + + /// Construct another kind of `Error`. + pub fn other(err: impl Into) -> Self { + ErrorKind::Other(err.into()).into() + } + + /// Get the kind of error that this is. + pub fn kind(&self) -> &ErrorKind { + &*self.kind + } +} + +impl From for Error { + fn from(kind: ErrorKind) -> Self { + Error { + kind: Box::new(kind), + } + } +} + +impl From for Error { + fn from(e: wasmparser::BinaryReaderError) -> Self { + ErrorKind::Parse(e).into() + } +} + +/// The kind of error. +#[derive(thiserror::Error, Debug)] +pub enum ErrorKind { + /// Failed to parse the input Wasm module. + #[error("Failed to parse the input Wasm module.")] Parse(#[from] wasmparser::BinaryReaderError), + /// None of the available mutators are applicable to the input Wasm module - #[error("There are not applicable mutations for this module.")] + #[error("There are not applicable mutations for the input Wasm module.")] NoMutationsApplicable, - /// There is a type/operator that wasm-mutate cannot process - #[error("Unsupported mapping.")] - UnsupportedType(EitherType), - /// Ast parsing error for code motion mutators - #[error("Invalid Ast parsing for code motion")] - InvalidAstOperation(String), -} -#[derive(Debug)] -pub enum EitherType { - Type(Type), - TypeDef(String), - Operator(String), - ValType(ValType), - EggError(String), - GlobalType(GlobalType), + /// Ran out of fuel before a mutation could be applied succesfully. + #[error("Out of fuel")] + OutOfFuel, + + /// The Wasm is using an unsupported feature. + #[error("Unsupported: {0}")] + Unsupported(String), + + /// Another error. + #[error("{0}")] + Other(String), } /// A `Result` type that is either `Ok(T)` or `Err(wasm_mutate::Error)`. diff --git a/crates/wasm-mutate/src/lib.rs b/crates/wasm-mutate/src/lib.rs index 894589d82c..9b5eec9767 100644 --- a/crates/wasm-mutate/src/lib.rs +++ b/crates/wasm-mutate/src/lib.rs @@ -6,11 +6,16 @@ //! Wasm parser, validator, compiler, or any other Wasm-consuming //! tool. `wasm-mutate` can serve as a custom mutator for mutation-based //! fuzzing. + #![cfg_attr(not(feature = "structopt"), deny(missing_docs))] + mod error; mod info; mod module; mod mutators; + +pub use error::*; + use crate::mutators::{ codemotion::CodemotionMutator, custom::RemoveCustomSection, data::RemoveDataSegment, elems::RemoveElemSegment, function_body_unreachable::FunctionBodyUnreachable, @@ -19,15 +24,12 @@ use crate::mutators::{ }; use info::ModuleInfo; use mutators::Mutator; - use rand::{rngs::SmallRng, Rng, SeedableRng}; use std::{cell::Cell, sync::Arc}; #[cfg(feature = "structopt")] use structopt::StructOpt; -pub use error::{Error, Result}; - macro_rules! define_mutators { (@count) => {0}; (@count $first: expr , $( $tail: expr ,) *) => { 1 + define_mutators!(@count $($tail , )*) }; @@ -242,7 +244,7 @@ impl<'wasm> WasmMutate<'wasm> { pub(crate) fn consume_fuel(&self, qt: u64) -> Result<()> { if qt > self.fuel.get() { log::info!("Out of fuel"); - return Err(crate::Error::NoMutationsApplicable); // Replace by a TimeoutError type + return Err(Error::out_of_fuel()); } self.fuel.set(self.fuel.get() - qt); Ok(()) @@ -278,7 +280,7 @@ impl<'wasm> WasmMutate<'wasm> { ) ); - Err(crate::Error::NoMutationsApplicable) + Err(Error::no_mutations_applicable()) } pub(crate) fn rng(&mut self) -> &mut SmallRng { diff --git a/crates/wasm-mutate/src/module.rs b/crates/wasm-mutate/src/module.rs index 4c8a14130d..f9a19271e8 100644 --- a/crates/wasm-mutate/src/module.rs +++ b/crates/wasm-mutate/src/module.rs @@ -1,10 +1,8 @@ +use crate::{Error, Result}; use std::convert::TryFrom; - use wasm_encoder::{BlockType, ValType}; use wasmparser::{Type, TypeDef, TypeOrFuncType}; -use crate::error::EitherType; - #[derive(Debug, Clone, PartialEq)] pub enum PrimitiveTypeInfo { I32, @@ -31,51 +29,46 @@ pub enum TypeInfo { // TODO: module linking support will require instance and module types. } -impl TryFrom for PrimitiveTypeInfo { - type Error = super::Error; - - fn try_from(value: Type) -> Result { +impl From for PrimitiveTypeInfo { + fn from(value: Type) -> Self { match value { - Type::I32 => Ok(PrimitiveTypeInfo::I32), - Type::I64 => Ok(PrimitiveTypeInfo::I64), - Type::F32 => Ok(PrimitiveTypeInfo::F32), - Type::F64 => Ok(PrimitiveTypeInfo::F64), - Type::V128 => Ok(PrimitiveTypeInfo::V128), - Type::FuncRef => Ok(PrimitiveTypeInfo::FuncRef), - Type::ExternRef => Ok(PrimitiveTypeInfo::ExternRef), - Type::EmptyBlockType => Ok(PrimitiveTypeInfo::Empty), - Type::ExnRef => Ok(PrimitiveTypeInfo::ExnRef), - Type::Func => Ok(PrimitiveTypeInfo::Func), + Type::I32 => PrimitiveTypeInfo::I32, + Type::I64 => PrimitiveTypeInfo::I64, + Type::F32 => PrimitiveTypeInfo::F32, + Type::F64 => PrimitiveTypeInfo::F64, + Type::V128 => PrimitiveTypeInfo::V128, + Type::FuncRef => PrimitiveTypeInfo::FuncRef, + Type::ExternRef => PrimitiveTypeInfo::ExternRef, + Type::EmptyBlockType => PrimitiveTypeInfo::Empty, + Type::ExnRef => PrimitiveTypeInfo::ExnRef, + Type::Func => PrimitiveTypeInfo::Func, } } } impl TryFrom> for TypeInfo { - type Error = super::Error; + type Error = Error; - fn try_from(value: TypeDef<'_>) -> Result { + fn try_from(value: TypeDef<'_>) -> Result { match value { TypeDef::Func(ft) => Ok(TypeInfo::Func(FuncInfo { params: ft .params .iter() - .map(|&t| PrimitiveTypeInfo::try_from(t)) - .collect::, _>>()?, + .map(|&t| PrimitiveTypeInfo::from(t)) + .collect(), returns: ft .returns .iter() - .map(|&t| PrimitiveTypeInfo::try_from(t)) - .collect::, _>>()?, + .map(|&t| PrimitiveTypeInfo::from(t)) + .collect(), })), - _ => Err(super::Error::UnsupportedType(EitherType::TypeDef(format!( - "{:?}", - value - )))), + _ => Err(Error::unsupported(format!("{:?}", value))), } } } -pub fn map_type(tpe: Type) -> super::Result { +pub fn map_type(tpe: Type) -> Result { match tpe { Type::I32 => Ok(ValType::I32), Type::I64 => Ok(ValType::I64), @@ -84,20 +77,17 @@ pub fn map_type(tpe: Type) -> super::Result { Type::V128 => Ok(ValType::V128), Type::FuncRef => Ok(ValType::FuncRef), Type::ExternRef => Ok(ValType::ExternRef), - _ => Err(super::Error::UnsupportedType(EitherType::Type(tpe))), + _ => Err(Error::unsupported(format!( + "{:?} is not supported in `wasm-encoder`", + tpe + ))), } } -pub fn map_block_type(ty: TypeOrFuncType) -> super::Result { +pub fn map_block_type(ty: TypeOrFuncType) -> Result { match ty { - TypeOrFuncType::Type(ty) => match ty { - Type::I32 => Ok(BlockType::Result(ValType::I32)), - Type::I64 => Ok(BlockType::Result(ValType::I64)), - Type::F32 => Ok(BlockType::Result(ValType::F32)), - Type::F64 => Ok(BlockType::Result(ValType::F64)), - Type::EmptyBlockType => Ok(BlockType::Empty), - _ => Err(super::Error::NoMutationsApplicable), - }, - TypeOrFuncType::FuncType(_) => Err(super::Error::NoMutationsApplicable), + TypeOrFuncType::FuncType(f) => Ok(BlockType::FunctionType(f)), + TypeOrFuncType::Type(Type::EmptyBlockType) => Ok(BlockType::Empty), + TypeOrFuncType::Type(ty) => Ok(BlockType::Result(map_type(ty)?)), } } diff --git a/crates/wasm-mutate/src/mutators/codemotion.rs b/crates/wasm-mutate/src/mutators/codemotion.rs index 89ec81c6b3..a404bcbc00 100644 --- a/crates/wasm-mutate/src/mutators/codemotion.rs +++ b/crates/wasm-mutate/src/mutators/codemotion.rs @@ -110,7 +110,7 @@ impl CodemotionMutator { } } - Err(Error::NoMutationsApplicable) + Err(Error::no_mutations_applicable()) } } /// Trait to be implemented by all code motion mutators @@ -177,6 +177,8 @@ mod tests { use rand::{rngs::SmallRng, SeedableRng}; fn test_motion_mutator(original: &str, expected: &str, seed: u64) { + let _ = env_logger::try_init(); + let mut wasmmutate = WasmMutate::default(); let original = &wat::parse_str(original).unwrap(); diff --git a/crates/wasm-mutate/src/mutators/codemotion/ir/parse_context.rs b/crates/wasm-mutate/src/mutators/codemotion/ir/parse_context.rs index 7983b32d97..f7eb0d01af 100644 --- a/crates/wasm-mutate/src/mutators/codemotion/ir/parse_context.rs +++ b/crates/wasm-mutate/src/mutators/codemotion/ir/parse_context.rs @@ -1,3 +1,4 @@ +use crate::{Error, Result}; use wasmparser::{Range, TypeOrFuncType}; #[derive(Debug, Default)] @@ -124,15 +125,13 @@ impl ParseContext { /// Pops nodes previously parsed, set them as the current parsing /// and then returns a copy of the poped value - pub fn pop_state(&mut self) -> crate::Result> { + pub fn pop_state(&mut self) -> Result> { match self.stack.pop() { Some(new_state) => { self.current_parsing = new_state.clone(); Ok(new_state) } - None => Err(crate::Error::InvalidAstOperation( - "Parsing should not be empty".to_string(), - )), + None => Err(Error::other("`pop_state` on an empty stack")), } } @@ -166,12 +165,10 @@ impl ParseContext { } /// Pop frame from the current parsing - pub fn pop_frame(&mut self) -> crate::Result<(State, Option, usize)> { + pub fn pop_frame(&mut self) -> Result<(State, Option, usize)> { match self.frames.pop() { Some(e) => Ok(e), - None => Err(crate::Error::InvalidAstOperation( - "Missing parent frame".to_string(), - )), + None => Err(Error::other("`pop_frame` on an empty frame stack")), } } diff --git a/crates/wasm-mutate/src/mutators/data.rs b/crates/wasm-mutate/src/mutators/data.rs index a1e68ee77c..4955a0a89a 100644 --- a/crates/wasm-mutate/src/mutators/data.rs +++ b/crates/wasm-mutate/src/mutators/data.rs @@ -1,6 +1,7 @@ //! Mutators that add, edit, or remove data segments. use super::Mutator; +use crate::Error; use rand::Rng; /// A mutator to remove data segments. @@ -32,10 +33,8 @@ impl Mutator for RemoveDataSegment { if let wasmparser::DataKind::Passive = seg.kind { // TODO: to support passive segments, we'll need to keep track // of segment renumberings and then fixup the code section. - return Err(crate::Error::UnsupportedType( - crate::error::EitherType::Operator( - "Can't remove data segments when some are passive".into(), - ), + return Err(Error::unsupported( + "Can't remove data segments when some are passive", )); } @@ -60,7 +59,7 @@ mod tests { use super::*; #[test] - fn test_remove_elem_segment() { + fn test_remove_data_segment() { crate::mutators::match_mutation( r#" (module diff --git a/crates/wasm-mutate/src/mutators/elems.rs b/crates/wasm-mutate/src/mutators/elems.rs index 65ffc25f19..013027383c 100644 --- a/crates/wasm-mutate/src/mutators/elems.rs +++ b/crates/wasm-mutate/src/mutators/elems.rs @@ -1,12 +1,10 @@ //! Mutators that operate on the elements section. +use super::Mutator; +use crate::{Error, Result}; use rand::Rng; use wasm_encoder::Module; -use crate::Result; - -use super::Mutator; - /// A mutator that removes element segments. #[derive(Clone, Copy)] pub struct RemoveElemSegment; @@ -36,10 +34,8 @@ impl Mutator for RemoveElemSegment { if let wasmparser::ElementKind::Passive = elem.kind { // TODO: to support passive segments, we'll need to keep track // of segment renumberings and then fixup the code section. - return Err(crate::Error::UnsupportedType( - crate::error::EitherType::Operator( - "Can't remove element segments when some are passive".into(), - ), + return Err(Error::unsupported( + "Can't remove element segments when some are passive", )); } diff --git a/crates/wasm-mutate/src/mutators/peephole.rs b/crates/wasm-mutate/src/mutators/peephole.rs index 8ddbd691c1..4cb6e14f81 100644 --- a/crates/wasm-mutate/src/mutators/peephole.rs +++ b/crates/wasm-mutate/src/mutators/peephole.rs @@ -36,11 +36,11 @@ use self::{ use super::{Mutator, OperatorAndByteOffset}; use crate::{ module::{map_type, PrimitiveTypeInfo}, - ModuleInfo, Result, WasmMutate, + Error, ModuleInfo, Result, WasmMutate, }; use egg::{Rewrite, Runner}; use rand::{prelude::SmallRng, Rng}; -use std::{borrow::Cow, convert::TryFrom, fmt::Debug}; +use std::{borrow::Cow, fmt::Debug}; use wasm_encoder::{CodeSection, Function, GlobalSection, Instruction, Module, ValType}; use wasmparser::{CodeSectionReader, FunctionBody, GlobalSectionReader, LocalsReader}; @@ -76,7 +76,7 @@ impl PeepholeMutator { } for _ in 0..localsreader.get_count() { let (count, ty) = localsreader.read()?; - let tymapped = PrimitiveTypeInfo::try_from(ty)?; + let tymapped = PrimitiveTypeInfo::from(ty); for _ in 0..count { all_locals.push(tymapped.clone()); } @@ -121,8 +121,9 @@ impl PeepholeMutator { loop { if visited_functions == function_count { - return Err(crate::Error::NoMutationsApplicable); + return Err(Error::no_mutations_applicable()); } + let reader = readers[function_to_mutate as usize]; let operatorreader = reader.get_operators_reader()?; let mut localsreader = reader.get_locals_reader()?; diff --git a/crates/wasm-mutate/src/mutators/peephole/eggsy/analysis.rs b/crates/wasm-mutate/src/mutators/peephole/eggsy/analysis.rs index 9c06769981..97e3d43acd 100644 --- a/crates/wasm-mutate/src/mutators/peephole/eggsy/analysis.rs +++ b/crates/wasm-mutate/src/mutators/peephole/eggsy/analysis.rs @@ -1,6 +1,7 @@ use crate::{ module::{PrimitiveTypeInfo, TypeInfo}, mutators::peephole::{eggsy::Lang, EG}, + Error, }; use egg::{Analysis, EGraph, Id}; @@ -105,7 +106,7 @@ impl PeepholeMutationAnalysis { } if ty.returns.len() > 1 { - return Err(crate::Error::NoMutationsApplicable); + return Err(Error::no_mutations_applicable()); } Ok(ty.returns[0].clone()) diff --git a/crates/wasm-mutate/src/mutators/peephole/eggsy/encoder/expr2wasm.rs b/crates/wasm-mutate/src/mutators/peephole/eggsy/encoder/expr2wasm.rs index 9a984f41af..41ced45047 100644 --- a/crates/wasm-mutate/src/mutators/peephole/eggsy/encoder/expr2wasm.rs +++ b/crates/wasm-mutate/src/mutators/peephole/eggsy/encoder/expr2wasm.rs @@ -1,15 +1,13 @@ -//! Helper to encode [Lang] expressions to Wasm -use std::num::Wrapping; - -use crate::error::EitherType; - -use crate::WasmMutate; +//! Helper to encode [Lang] expressions to Wasm. -use crate::module::PrimitiveTypeInfo; -use crate::mutators::peephole::eggsy::encoder::TraversalEvent; -use crate::mutators::peephole::{Lang, EG}; +use crate::{ + module::PrimitiveTypeInfo, + mutators::peephole::{eggsy::encoder::TraversalEvent, Lang, EG}, + Error, Result, WasmMutate, +}; use egg::{Id, RecExpr}; use rand::Rng; +use std::num::Wrapping; use wasm_encoder::{Function, Instruction, MemArg}; /// Some custom nodes might need special resource allocation outside the @@ -41,7 +39,7 @@ pub fn expr2wasm( expr: &RecExpr, newfunc: &mut Function, _egraph: &EG, -) -> crate::Result> { +) -> Result> { let nodes = expr.as_ref(); // The last node is the root. let root = Id::from(nodes.len() - 1); @@ -663,12 +661,10 @@ pub fn expr2wasm( newfunc.instruction(&Instruction::I32Add); } _ => { - return Err(crate::Error::UnsupportedType(EitherType::EggError( - format!( - "The current eterm cannot be unfolded {:?}.\n expr {}", - child, expr - ), - ))) + return Err(Error::other(format!( + "The current eterm cannot be unfolded {:?}.\n expr {}", + child, expr + ))); } } } @@ -686,11 +682,9 @@ pub fn expr2wasm( newfunc.instruction(&Instruction::I64Add); } _ => { - return Err(crate::Error::UnsupportedType(EitherType::EggError( - format!( - "The current eterm cannot be unfolded {:?}.\n expr {}", - child, expr - ), + return Err(Error::other(format!( + "The current eterm cannot be unfolded {:?}.\n expr {}", + child, expr ))) } } diff --git a/fuzz/fuzz_targets/mutate.rs b/fuzz/fuzz_targets/mutate.rs index 56396b027e..3da9c03015 100755 --- a/fuzz/fuzz_targets/mutate.rs +++ b/fuzz/fuzz_targets/mutate.rs @@ -42,67 +42,69 @@ fuzz_target!(|bytes: &[u8]| { ); } - // Mutate the Wasm with `wasm-mutate`. We always preserve semantics. + // Mutate the Wasm with `wasm-mutate`. Assert that each mutation is still + // valid Wasm. + let mut wasm_mutate = wasm_mutate::WasmMutate::default(); wasm_mutate.seed(seed); wasm_mutate.fuel(300); - wasm_mutate.preserve_semantics(true); + wasm_mutate.preserve_semantics( + // If we are going to check that we get the same evaluated results + // before and after mutation, then we need to preserve semantics. + cfg!(feature = "wasmtime"), + ); - let mutated_wasm_iterator = wasm_mutate.run(&wasm); - let mut found = false; + let iterator = match wasm_mutate.run(&wasm) { + Ok(iterator) => iterator, + Err(e) => match e.kind() { + wasm_mutate::ErrorKind::NoMutationsApplicable => return, + _ => panic!("Unexpected mutation failure: {}", e), + }, + }; let mut features = WasmFeatures::default(); - features.simd = config.simd_enabled; features.relaxed_simd = config.relaxed_simd_enabled; features.reference_types = config.reference_types_enabled; features.module_linking = config.module_linking_enabled; features.bulk_memory = config.bulk_memory_enabled; - match mutated_wasm_iterator { - Ok(mut iterator) => { - while let Some(mutated_wasm) = iterator.next() { - match mutated_wasm { - Ok(mutated_wasm) => { - // Increase ony once for the same input Wasm - if !found { - NUM_SUCCESSFUL_MUTATIONS.fetch_add(1, Ordering::Relaxed); - found = true; - } + for (i, mutated_wasm) in iterator.take(100).enumerate() { + let mutated_wasm = match mutated_wasm { + Ok(w) => w, + Err(e) => match e.kind() { + wasm_mutate::ErrorKind::NoMutationsApplicable => continue, + _ => panic!("Unexpected mutation failure: {}", e), + }, + }; - let mut validator = wasmparser::Validator::new(); - validator.wasm_features(features); + // Increase ony once for the same input Wasm. + if i == 0 { + NUM_SUCCESSFUL_MUTATIONS.fetch_add(1, Ordering::Relaxed); + } - let validation_result = validator.validate_all(&mutated_wasm); + let mut validator = wasmparser::Validator::new(); + validator.wasm_features(features); - log::debug!("validation result = {:?}", validation_result); + let validation_result = validator.validate_all(&mutated_wasm); - if log::log_enabled!(log::Level::Debug) { - std::fs::write("mutated.wasm", &mutated_wasm) - .expect("should write `mutated.wasm` okay"); - if let Ok(mutated_wat) = wasmprinter::print_bytes(&mutated_wasm) { - std::fs::write("mutated.wat", &mutated_wat) - .expect("should write `mutated.wat` okay"); - } - } - assert!( - validation_result.is_ok(), - "`wasm-mutate` should always produce a valid Wasm file" - ); - #[cfg(feature = "wasmtime")] - eval::assert_same_evaluation(&wasm, &mutated_wasm); - } - Err(e) => match e { - wasm_mutate::Error::NoMutationsApplicable => continue, - e => panic!("Unexpected mutation failure: {}", e), - }, - } + log::debug!("validation result = {:?}", validation_result); + + if log::log_enabled!(log::Level::Debug) { + std::fs::write("mutated.wasm", &mutated_wasm) + .expect("should write `mutated.wasm` okay"); + if let Ok(mutated_wat) = wasmprinter::print_bytes(&mutated_wasm) { + std::fs::write("mutated.wat", &mutated_wat) + .expect("should write `mutated.wat` okay"); } } - Err(e) => match e { - wasm_mutate::Error::NoMutationsApplicable => return, - e => panic!("Unexpected mutation failure: {}", e), - }, + assert!( + validation_result.is_ok(), + "`wasm-mutate` should always produce a valid Wasm file" + ); + + #[cfg(feature = "wasmtime")] + eval::assert_same_evaluation(&wasm, &mutated_wasm); } }); diff --git a/src/bin/wasm-mutate.rs b/src/bin/wasm-mutate.rs index 1d96096ff5..34825412e9 100644 --- a/src/bin/wasm-mutate.rs +++ b/src/bin/wasm-mutate.rs @@ -3,6 +3,7 @@ use std::fs; use std::io::{stdin, stdout, Read, Write}; use std::path::PathBuf; use structopt::StructOpt; +use wasm_mutate::{ErrorKind, Result}; /// A WebAssembly test case mutator. /// @@ -25,16 +26,18 @@ use structopt::StructOpt; /// /// * 1: An unexpected failure occurred. /// -/// * 2: Failed to mutate the Wasm module with the given seed and mutation +/// * 2: Failed to parse or validate the input Wasm module. +/// +/// * 3: Failed to mutate the Wasm module with the given seed and mutation /// constraints. (Happens rarely with reasonable constraints; try again /// with a new seed or loosen your constraints.) /// -/// * 3: The input Wasm module uses a Wasm feature or proposal that is not yet -/// supported by `wasm-mutate`. +/// * 4: Ran out of fuel before successfully applying a mutation. /// -/// * 4: The input is not a valid Wasm module. +/// * 5: The input Wasm module uses a Wasm feature or proposal that is not yet +/// supported by `wasm-mutate`. /// -/// * 5: Constructing the Ast for code motion mutations fails +/// * 6: Other error. #[derive(StructOpt)] struct Options<'wasm> { /// The input WebAssembly binary that will be mutated. @@ -91,62 +94,40 @@ fn main() -> anyhow::Result<()> { input .read_to_end(&mut input_wasm) .with_context(|| format!("failed to read '{}'", input_name))?; - let it = opts.wasm_mutate.run(&input_wasm); - let mut output_wasm = match it { - Ok(w) => w, - Err(e) => { - let code = match &e { - wasm_mutate::Error::NoMutationsApplicable => 2, - wasm_mutate::Error::UnsupportedType(_) => 3, - wasm_mutate::Error::Parse(_) => 4, - wasm_mutate::Error::InvalidAstOperation(_) => 5, - }; - eprintln!("{}", e); - std::process::exit(code); + + let mut output_wasms = unwrap_wasm_mutate_result(opts.wasm_mutate.run(&input_wasm)).take(100); + let wasm = loop { + if let Some(res) = output_wasms.next() { + match res { + Err(e) if matches!(e.kind(), ErrorKind::NoMutationsApplicable) => { + // Try the next mutation. + continue; + } + _ => break unwrap_wasm_mutate_result(res), + } } }; - let mut first_good = Err(wasm_mutate::Error::NoMutationsApplicable); + output + .write_all(&wasm) + .with_context(|| format!("failed to write to '{}'", output_name))?; - while let Some(w) = output_wasm.next() { - match w { - Ok(w) => { - first_good = Ok(w); - break; - } - Err(e) => { - let code = match &e { - wasm_mutate::Error::NoMutationsApplicable => { - // Continue to the next one if its is type no mutation - // otherwrise, raise an error - continue; - } - wasm_mutate::Error::UnsupportedType(_) => 3, - wasm_mutate::Error::Parse(_) => 4, - wasm_mutate::Error::InvalidAstOperation(_) => 5, - }; - eprintln!("{}", e); - std::process::exit(code); - } - } - } + Ok(()) +} - match first_good { - Ok(w) => { - output - .write_all(&w) - .with_context(|| format!("failed to write to '{}'", output_name))?; - } +fn unwrap_wasm_mutate_result(result: Result) -> T { + match result { + Ok(x) => x, Err(e) => { - let code = match &e { - wasm_mutate::Error::NoMutationsApplicable => 2, - wasm_mutate::Error::UnsupportedType(_) => 3, - wasm_mutate::Error::Parse(_) => 4, - wasm_mutate::Error::InvalidAstOperation(_) => 5, + let code = match e.kind() { + ErrorKind::Parse(_) => 2, + ErrorKind::NoMutationsApplicable => 3, + ErrorKind::OutOfFuel => 4, + ErrorKind::Unsupported(_) => 5, + ErrorKind::Other(_) => 6, }; eprintln!("{}", e); std::process::exit(code); } } - Ok(()) } From 16f801e9a69bae323d930e2de404f8bbff8f7579 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 9 Dec 2021 15:34:44 -0800 Subject: [PATCH 5/7] Add `wasm-shrink` and `wasm-mutate` to the README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9b641fc5c8..c883b94e80 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ look at the sub-crates: * [**`wast`**](crates/wast) - like `wat`, except provides an AST * [**`wasmprinter`**](crates/wasmprinter) - prints WebAssembly binaries in their string form +* [**`wasm-mutate`**](crates/wasm-mutate) - a WebAssembly test case mutator +* [**`wasm-shrink`**](crates/wasm-shrink) - a WebAssembly test case shrinker * [**`wasm-smith`**](crates/wasm-smith) - a WebAssembly test case generator * [**`wasm-encoder`**](crates/wasm-encoder) - a crate to generate a binary WebAssembly module From b9b19f1cd863c7dad35e86c261b8a5ded071dc70 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 9 Dec 2021 15:39:32 -0800 Subject: [PATCH 6/7] fuzz: Ignore all errors, not just "no mutations applicable" --- fuzz/fuzz_targets/mutate.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fuzz/fuzz_targets/mutate.rs b/fuzz/fuzz_targets/mutate.rs index 3da9c03015..a9e0b06b35 100755 --- a/fuzz/fuzz_targets/mutate.rs +++ b/fuzz/fuzz_targets/mutate.rs @@ -56,10 +56,10 @@ fuzz_target!(|bytes: &[u8]| { let iterator = match wasm_mutate.run(&wasm) { Ok(iterator) => iterator, - Err(e) => match e.kind() { - wasm_mutate::ErrorKind::NoMutationsApplicable => return, - _ => panic!("Unexpected mutation failure: {}", e), - }, + Err(e) => { + log::warn!("Failed to mutate the Wasm: {}", e); + return; + } }; let mut features = WasmFeatures::default(); From 30938c325c307d6058c61c501fef65774f11f5ad Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 9 Dec 2021 16:33:21 -0800 Subject: [PATCH 7/7] wasm-mutate: Fix rules that used `1_i64` inside `i32.sh{l,r_u}` expressions --- crates/wasm-mutate/src/mutators/peephole.rs | 2 ++ crates/wasm-mutate/src/mutators/peephole/rules.rs | 6 +++--- fuzz/fuzz_targets/mutate.rs | 2 ++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/wasm-mutate/src/mutators/peephole.rs b/crates/wasm-mutate/src/mutators/peephole.rs index 4cb6e14f81..b5464d2324 100644 --- a/crates/wasm-mutate/src/mutators/peephole.rs +++ b/crates/wasm-mutate/src/mutators/peephole.rs @@ -254,6 +254,8 @@ impl PeepholeMutator { let iterator = iterator .filter(move |expr| !expr.to_string().eq(&startcmp.to_string())) .map(move |expr| { + log::trace!("Yielding expression:\n{}", expr.pretty(60)); + let mut newfunc = self.copy_locals(reader)?; let needed_resources = Encoder::build_function( config, diff --git a/crates/wasm-mutate/src/mutators/peephole/rules.rs b/crates/wasm-mutate/src/mutators/peephole/rules.rs index e1faf04f68..f24025e6ff 100644 --- a/crates/wasm-mutate/src/mutators/peephole/rules.rs +++ b/crates/wasm-mutate/src/mutators/peephole/rules.rs @@ -489,7 +489,7 @@ impl PeepholeMutator { // `x <=> x << 1` rules.extend(rewrite!( "i32.shl-1"; - "?x" <=> "(i32.shl ?x 1_i64)" + "?x" <=> "(i32.shl ?x 1_i32)" if self.is_type("?x", PrimitiveTypeInfo::I32) )); rules.extend(rewrite!( @@ -501,7 +501,7 @@ impl PeepholeMutator { // `x <=> x >> 1` rules.extend(rewrite!( "i32.shr_u-1"; - "?x" <=> "(i32.shr_u ?x 1_i64)" + "?x" <=> "(i32.shr_u ?x 1_i32)" if self.is_type("?x", PrimitiveTypeInfo::I32) )); rules.extend(rewrite!( @@ -511,7 +511,7 @@ impl PeepholeMutator { )); rules.extend(rewrite!( "i32.shr_s-1"; - "?x" <=> "(i32.shr_s ?x 1_i64)" + "?x" <=> "(i32.shr_s ?x 1_i32)" if self.is_type("?x", PrimitiveTypeInfo::I32) )); rules.extend(rewrite!( diff --git a/fuzz/fuzz_targets/mutate.rs b/fuzz/fuzz_targets/mutate.rs index a9e0b06b35..147b5f307a 100755 --- a/fuzz/fuzz_targets/mutate.rs +++ b/fuzz/fuzz_targets/mutate.rs @@ -91,9 +91,11 @@ fuzz_target!(|bytes: &[u8]| { log::debug!("validation result = {:?}", validation_result); if log::log_enabled!(log::Level::Debug) { + log::debug!("writing mutated Wasm to `mutated.wasm`"); std::fs::write("mutated.wasm", &mutated_wasm) .expect("should write `mutated.wasm` okay"); if let Ok(mutated_wat) = wasmprinter::print_bytes(&mutated_wasm) { + log::debug!("writing mutated WAT to `mutated.wat`"); std::fs::write("mutated.wat", &mutated_wat) .expect("should write `mutated.wat` okay"); }