Coverage Report

Created: 2025-01-09 07:53

/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
}