Skip to main content

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