wasmtime_fuzzing/oracles/
engine.rs

1//! Define the interface for differential evaluation of Wasm functions.
2
3use crate::generators::{CompilerStrategy, Config, DiffValue, DiffValueType};
4use crate::oracles::{diff_wasmi::WasmiEngine, diff_wasmtime::WasmtimeEngine};
5use anyhow::Error;
6use arbitrary::Unstructured;
7use wasmtime::Trap;
8
9/// Returns a function which can be used to build the engine name specified.
10///
11/// `None` is returned if the named engine does not have support compiled into
12/// this crate.
13pub fn build(
14    u: &mut Unstructured<'_>,
15    name: &str,
16    config: &mut Config,
17) -> arbitrary::Result<Option<Box<dyn DiffEngine>>> {
18    let engine: Box<dyn DiffEngine> = match name {
19        "wasmtime" => Box::new(WasmtimeEngine::new(
20            u,
21            config,
22            CompilerStrategy::CraneliftNative,
23        )?),
24        "pulley" => Box::new(WasmtimeEngine::new(
25            u,
26            config,
27            CompilerStrategy::CraneliftPulley,
28        )?),
29        "wasmi" => Box::new(WasmiEngine::new(config)),
30
31        #[cfg(target_arch = "x86_64")]
32        "winch" => Box::new(WasmtimeEngine::new(u, config, CompilerStrategy::Winch)?),
33        #[cfg(not(target_arch = "x86_64"))]
34        "winch" => return Ok(None),
35
36        #[cfg(feature = "fuzz-spec-interpreter")]
37        "spec" => Box::new(crate::oracles::diff_spec::SpecInterpreter::new(config)),
38        #[cfg(not(feature = "fuzz-spec-interpreter"))]
39        "spec" => return Ok(None),
40
41        #[cfg(not(any(windows, target_arch = "s390x", target_arch = "riscv64")))]
42        "v8" => Box::new(crate::oracles::diff_v8::V8Engine::new(config)),
43        #[cfg(any(windows, target_arch = "s390x", target_arch = "riscv64"))]
44        "v8" => return Ok(None),
45
46        _ => panic!("unknown engine {name}"),
47    };
48
49    Ok(Some(engine))
50}
51
52/// Provide a way to instantiate Wasm modules.
53pub trait DiffEngine {
54    /// Return the name of the engine.
55    fn name(&self) -> &'static str;
56
57    /// Create a new instance with the given engine.
58    fn instantiate(&mut self, wasm: &[u8]) -> anyhow::Result<Box<dyn DiffInstance>>;
59
60    /// Tests that the wasmtime-originating `trap` matches the error this engine
61    /// generated.
62    fn assert_error_match(&self, err: &Error, trap: &Trap);
63
64    /// Returns whether the error specified from this engine is
65    /// non-deterministic, like a stack overflow or an attempt to allocate an
66    /// object that is too large (which is non-deterministic because it may
67    /// depend on which collector it was configured with or memory available on
68    /// the system).
69    fn is_non_deterministic_error(&self, err: &Error) -> bool;
70}
71
72/// Provide a way to evaluate Wasm functions--a Wasm instance implemented by a
73/// specific engine (i.e., compiler or interpreter).
74pub trait DiffInstance {
75    /// Return the name of the engine behind this instance.
76    fn name(&self) -> &'static str;
77
78    /// Evaluate an exported function with the given values.
79    ///
80    /// Any error, such as a trap, should be returned through an `Err`. If this
81    /// engine cannot invoke the function signature then `None` should be
82    /// returned and this invocation will be skipped.
83    fn evaluate(
84        &mut self,
85        function_name: &str,
86        arguments: &[DiffValue],
87        results: &[DiffValueType],
88    ) -> anyhow::Result<Option<Vec<DiffValue>>>;
89
90    /// Attempts to return the value of the specified global, returning `None`
91    /// if this engine doesn't support retrieving globals at this time.
92    fn get_global(&mut self, name: &str, ty: DiffValueType) -> Option<DiffValue>;
93
94    /// Same as `get_global` but for memory.
95    fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>>;
96}
97
98/// Initialize any global state associated with runtimes that may be
99/// differentially executed against.
100pub fn setup_engine_runtimes() {
101    #[cfg(feature = "fuzz-spec-interpreter")]
102    crate::oracles::diff_spec::setup_ocaml_runtime();
103}
104
105/// Build a list of allowed values from the given `defaults` using the
106/// `env_list`.
107///
108/// The entries in `defaults` are preserved, in order, and are replaced with
109/// `None` in the returned list if they are disabled.
110///
111/// ```
112/// # use wasmtime_fuzzing::oracles::engine::build_allowed_env_list;
113/// // Passing no `env_list` returns the defaults:
114/// assert_eq!(build_allowed_env_list(None, &["a"]), vec![Some("a")]);
115/// // We can build up a subset of the defaults:
116/// assert_eq!(build_allowed_env_list(Some(vec!["b".to_string()]), &["a","b"]), vec![None, Some("b")]);
117/// // Alternately we can subtract from the defaults:
118/// assert_eq!(build_allowed_env_list(Some(vec!["-a".to_string()]), &["a","b"]), vec![None, Some("b")]);
119/// ```
120/// ```should_panic
121/// # use wasmtime_fuzzing::oracles::engine::build_allowed_env_list;
122/// // We are not allowed to mix set "addition" and "subtraction"; the following
123/// // will panic:
124/// build_allowed_env_list(Some(vec!["-a".to_string(), "b".to_string()]), &["a", "b"]);
125/// ```
126/// ```should_panic
127/// # use wasmtime_fuzzing::oracles::engine::build_allowed_env_list;
128/// // This will also panic if invalid values are used:
129/// build_allowed_env_list(Some(vec!["c".to_string()]), &["a", "b"]);
130/// ```
131pub fn build_allowed_env_list<'a>(
132    env_list: Option<Vec<String>>,
133    defaults: &[&'a str],
134) -> Vec<Option<&'a str>> {
135    if let Some(configured) = &env_list {
136        // Check that the names are either all additions or all subtractions.
137        let subtract_from_defaults = configured.iter().all(|c| c.starts_with("-"));
138        let add_from_defaults = configured.iter().all(|c| !c.starts_with("-"));
139        let start = if subtract_from_defaults { 1 } else { 0 };
140        if !subtract_from_defaults && !add_from_defaults {
141            panic!(
142                "all configured values must either subtract or add from defaults; found mixed values: {:?}",
143                &env_list
144            );
145        }
146
147        // Check that the configured names are valid ones.
148        for c in configured {
149            if !defaults.contains(&&c[start..]) {
150                panic!("invalid environment configuration `{c}`; must be one of: {defaults:?}");
151            }
152        }
153
154        // Select only the allowed names.
155        let mut allowed = Vec::with_capacity(defaults.len());
156        for &d in defaults {
157            let mentioned = configured.iter().any(|c| &c[start..] == d);
158            if (add_from_defaults && mentioned) || (subtract_from_defaults && !mentioned) {
159                allowed.push(Some(d));
160            } else {
161                allowed.push(None);
162            }
163        }
164        allowed
165    } else {
166        defaults.iter().copied().map(Some).collect()
167    }
168}
169
170/// Retrieve a comma-delimited list of values from an environment variable.
171pub fn parse_env_list(env_variable: &str) -> Option<Vec<String>> {
172    std::env::var(env_variable)
173        .ok()
174        .map(|l| l.split(",").map(|s| s.to_owned()).collect())
175}
176
177/// Smoke test an engine with a given config.
178#[cfg(test)]
179pub fn smoke_test_engine<T>(
180    mk_engine: impl Fn(&mut arbitrary::Unstructured<'_>, &mut Config) -> arbitrary::Result<T>,
181) where
182    T: DiffEngine,
183{
184    use rand::prelude::*;
185
186    let mut rng = SmallRng::seed_from_u64(0);
187    let mut buf = vec![0; 2048];
188    let n = 100;
189    for _ in 0..n {
190        rng.fill_bytes(&mut buf);
191        let mut u = Unstructured::new(&buf);
192        let mut config = match u.arbitrary::<Config>() {
193            Ok(config) => config,
194            Err(_) => continue,
195        };
196        // This will ensure that wasmtime, which uses this configuration
197        // settings, can guaranteed instantiate a module.
198        config.set_differential_config();
199
200        let mut engine = match mk_engine(&mut u, &mut config) {
201            Ok(engine) => engine,
202            Err(e) => {
203                println!("skip {e:?}");
204                continue;
205            }
206        };
207
208        let wasm = wat::parse_str(
209            r#"
210                (module
211                    (func (export "add") (param i32 i32) (result i32)
212                        local.get 0
213                        local.get 1
214                        i32.add)
215
216                    (global (export "global") i32 i32.const 1)
217                    (memory (export "memory") 1)
218                )
219            "#,
220        )
221        .unwrap();
222        let mut instance = engine.instantiate(&wasm).unwrap();
223        let results = instance
224            .evaluate(
225                "add",
226                &[DiffValue::I32(1), DiffValue::I32(2)],
227                &[DiffValueType::I32],
228            )
229            .unwrap();
230        assert_eq!(results, Some(vec![DiffValue::I32(3)]));
231
232        if let Some(val) = instance.get_global("global", DiffValueType::I32) {
233            assert_eq!(val, DiffValue::I32(1));
234        }
235
236        if let Some(val) = instance.get_memory("memory", false) {
237            assert_eq!(val.len(), 65536);
238            for i in val.iter() {
239                assert_eq!(*i, 0);
240            }
241        }
242
243        return;
244    }
245
246    panic!("after {n} runs nothing ever ran, something is probably wrong");
247}