/src/wasm-tools/crates/wasm-mutate/src/mutators.rs
Line | Count | Source (jump to first uncovered line) |
1 | | //! Mutator trait |
2 | | //! |
3 | | //! `wasm-mutate` in build on top of three main group or mutators: |
4 | | //! **top wasm module struct mutators**, [**code |
5 | | //! motion mutators**][super::CodemotionMutator] and [**peephole |
6 | | //! mutators**][super::PeepholeMutator]. |
7 | | //! |
8 | | //! The former type is meant to change(mutate) the top structure of the input Wasm |
9 | | //! binary, like for example, [by renaming the name of an exported function][super::RenameExportMutator]. |
10 | | //! |
11 | | //! The later two types make changes deeper on the code of the input Wasm binary, |
12 | | //! specifycally at the code section level of the binary. The code motion mutator |
13 | | //! parses the code and provides an AST which is transformed in a semantically |
14 | | //! equivalent way. We provide two concrete implementations using this type of |
15 | | //! mutator: [`LoopUnrollMutator`] and [`IfComplementMutator`]. |
16 | | //! |
17 | | //! The last group of mutators are the [**peephole |
18 | | //! mutators**][super::PeepholeMutator]. When it comes to the input Wasm binary code section, it |
19 | | //! iterates through the defined functions, and then each instruction of the |
20 | | //! functions is processed in order to construct an equivalent piece of code. |
21 | | //! |
22 | | //! [`LoopUnrollMutator`]: crate::mutators::codemotion::loop_unrolling::LoopUnrollMutator |
23 | | //! [`IfComplementMutator`]: crate::mutators::codemotion::if_complement::IfComplementMutator |
24 | | |
25 | | pub mod add_function; |
26 | | pub mod add_type; |
27 | | pub mod codemotion; |
28 | | pub mod custom; |
29 | | pub mod function_body_unreachable; |
30 | | pub mod modify_const_exprs; |
31 | | pub mod modify_data; |
32 | | pub mod peephole; |
33 | | pub mod remove_export; |
34 | | pub mod remove_item; |
35 | | pub mod remove_section; |
36 | | pub mod rename_export; |
37 | | pub mod snip_function; |
38 | | pub mod start; |
39 | | |
40 | | mod translate; |
41 | | pub use self::translate::Item; |
42 | | |
43 | | use std::borrow::Cow; |
44 | | |
45 | | use super::Result; |
46 | | use crate::WasmMutate; |
47 | | use wasm_encoder::Module; |
48 | | use wasmparser::Operator; |
49 | | |
50 | | /// A mutation that can be applied to a Wasm module to produce a new, mutated |
51 | | /// Wasm module. |
52 | | pub trait Mutator { |
53 | | /// Can this `Mutator` *probably* be applied to the the given Wasm and |
54 | | /// configuration? |
55 | | /// |
56 | | /// When checking Wasm applicability, these checks should be implemented as |
57 | | /// quick, incomplete checks. For example, a mutator that removes a single |
58 | | /// function at a time should *not* build the whole call graph to determine |
59 | | /// if there are any functions that are dead code. Instead, it should just |
60 | | /// check that the Wasm contains at least one function. (It can always |
61 | | /// return an `Err` from the `mutate` method later, but we want to delay |
62 | | /// expensive computations until if/when we've committed to applying a given |
63 | | /// mutation). |
64 | | /// |
65 | | /// As an example of configuration checking, if a mutator adds new functions |
66 | | /// to the Wasm, increasing the Wasm's size, it should check whether the |
67 | | /// `WasmMutate` has been configured to only perform size-reducing |
68 | | /// mutations, and if so return `false` here. |
69 | | fn can_mutate(&self, config: &WasmMutate) -> bool; |
70 | | |
71 | | /// Run this mutation. |
72 | | /// |
73 | | /// Rather than returning a single, mutated module, we allow for mutators to |
74 | | /// return many. |
75 | | /// |
76 | | /// Mutators that return `Ok(iterator)` should always return an iterator |
77 | | /// with at least one item. Mutators should never return an empty iterator, |
78 | | /// instead they should return `Err(...)`. |
79 | | /// |
80 | | /// The iterators returned from this method must be *lazy*. Mutators should |
81 | | /// *not* return `Ok(Box::new(vec![...].into_iter()))`. Only one mutation |
82 | | /// should ever be computed at a time. Mutator implementations might find |
83 | | /// `std::iter::from_fn` helpful. |
84 | | /// |
85 | | /// When should a mutator return a single item via `std::iter::once` versus |
86 | | /// an iterator with many items? When the mutator builds up a bunch of state |
87 | | /// that was expensive to build and can be reused, it should return an |
88 | | /// iterator with many items that reuse that state. For example, the |
89 | | /// peephole mutator's e-graph is expensive to build and should be reused. |
90 | | /// If, however, the mutator doesn't build up state or its state is cheap to |
91 | | /// recompute, then the mutator should return a single-item iterator with |
92 | | /// `std::iter::once`, to give the fuzzer a chance to choose a new kind of |
93 | | /// mutation. |
94 | | fn mutate<'a>( |
95 | | &self, |
96 | | config: &'a mut WasmMutate, |
97 | | ) -> Result<Box<dyn Iterator<Item = Result<Module>> + 'a>>; |
98 | | |
99 | | /// What is this mutator's name? |
100 | | /// |
101 | | /// This is only used for debugging and logging purposes. |
102 | 0 | fn name(&self) -> Cow<'static, str> { |
103 | 0 | return std::any::type_name::<Self>().into(); |
104 | 0 | } Unexecuted instantiation: <wasm_mutate::mutators::custom::CustomSectionMutator as wasm_mutate::mutators::Mutator>::name Unexecuted instantiation: <wasm_mutate::mutators::custom::AddCustomSectionMutator as wasm_mutate::mutators::Mutator>::name Unexecuted instantiation: <wasm_mutate::mutators::custom::ReorderCustomSectionMutator as wasm_mutate::mutators::Mutator>::name Unexecuted instantiation: <wasm_mutate::mutators::remove_item::RemoveItemMutator as wasm_mutate::mutators::Mutator>::name Unexecuted instantiation: <wasm_mutate::mutators::codemotion::CodemotionMutator as wasm_mutate::mutators::Mutator>::name Unexecuted instantiation: <wasm_mutate::mutators::add_function::AddFunctionMutator as wasm_mutate::mutators::Mutator>::name Unexecuted instantiation: <wasm_mutate::mutators::modify_data::ModifyDataMutator as wasm_mutate::mutators::Mutator>::name Unexecuted instantiation: <wasm_mutate::mutators::snip_function::SnipMutator as wasm_mutate::mutators::Mutator>::name Unexecuted instantiation: <wasm_mutate::mutators::peephole::PeepholeMutator as wasm_mutate::mutators::Mutator>::name Unexecuted instantiation: <wasm_mutate::mutators::modify_const_exprs::ConstExpressionMutator as wasm_mutate::mutators::Mutator>::name Unexecuted instantiation: <wasm_mutate::mutators::rename_export::RenameExportMutator as wasm_mutate::mutators::Mutator>::name Unexecuted instantiation: <wasm_mutate::mutators::remove_export::RemoveExportMutator as wasm_mutate::mutators::Mutator>::name Unexecuted instantiation: <wasm_mutate::mutators::add_type::AddTypeMutator as wasm_mutate::mutators::Mutator>::name Unexecuted instantiation: <wasm_mutate::mutators::remove_section::RemoveSection as wasm_mutate::mutators::Mutator>::name Unexecuted instantiation: <wasm_mutate::mutators::function_body_unreachable::FunctionBodyUnreachable as wasm_mutate::mutators::Mutator>::name |
105 | | } |
106 | | |
107 | | /// Type helper to wrap operator and the byte offset in the code section of a Wasm module |
108 | | pub type OperatorAndByteOffset<'a> = (Operator<'a>, usize); |
109 | | |
110 | | #[cfg(test)] |
111 | | fn match_mutation<T>(original: &str, mutator: T, expected: &str) |
112 | | where |
113 | | T: Mutator + Clone, |
114 | | { |
115 | | WasmMutate::default().match_mutation(original, mutator, expected) |
116 | | } |
117 | | |
118 | | impl WasmMutate<'_> { |
119 | | #[cfg(test)] |
120 | | fn match_mutation<T>(&mut self, original: &str, mutator: T, expected: &str) |
121 | | where |
122 | | T: Mutator + Clone, |
123 | | { |
124 | | use crate::ErrorKind; |
125 | | |
126 | | drop(env_logger::try_init()); |
127 | | |
128 | | let original = &wat::parse_str(original).unwrap(); |
129 | | |
130 | | let expected = &wat::parse_str(expected).unwrap(); |
131 | | let expected_text = wasmprinter::print_bytes(expected).unwrap(); |
132 | | |
133 | | let mut config = self.clone(); |
134 | | config.setup(&original).unwrap(); |
135 | | |
136 | | let can_mutate = mutator.can_mutate(&config); |
137 | | |
138 | | assert!(can_mutate); |
139 | | |
140 | | let attempts = 2000; |
141 | | let mut last_mutation = None; |
142 | | |
143 | | for _ in 0..attempts { |
144 | | let mutation = match mutator |
145 | | .clone() |
146 | | .mutate(&mut config) |
147 | | .map(|mut mutation| mutation.next()) |
148 | | { |
149 | | Ok(Some(mutation)) => mutation.unwrap(), |
150 | | Ok(None) => continue, |
151 | | Err(e) if matches!(e.kind(), ErrorKind::NoMutationsApplicable) => continue, |
152 | | Err(e) => panic!("mutation error: {}", e), |
153 | | }; |
154 | | |
155 | | let mutation_bytes = mutation.finish(); |
156 | | |
157 | | crate::validate(&mutation_bytes); |
158 | | |
159 | | // If it fails, it is probably an invalid |
160 | | // reformatting expected |
161 | | let text = wasmprinter::print_bytes(mutation_bytes).unwrap(); |
162 | | if text.trim() == expected_text.trim() { |
163 | | return; |
164 | | } |
165 | | log::debug!("skipping mutation {}", text); |
166 | | last_mutation = Some(text); |
167 | | } |
168 | | |
169 | | match last_mutation { |
170 | | Some(mutation) => { |
171 | | panic!( |
172 | | "after {} attempts the last mutation:\n{:?}\n\n\ |
173 | | did not match the expected mutation\n{:?}", |
174 | | attempts, mutation, expected_text |
175 | | ); |
176 | | } |
177 | | None => { |
178 | | panic!( |
179 | | "never found any applicable mutations after {} attempts", |
180 | | attempts |
181 | | ); |
182 | | } |
183 | | } |
184 | | } |
185 | | } |