wasmtime_fuzzing/oracles/
diff_spec.rs

1//! Evaluate an exported Wasm function using the WebAssembly specification
2//! reference interpreter.
3
4use crate::generators::{Config, DiffValue, DiffValueType};
5use crate::oracles::engine::{DiffEngine, DiffInstance};
6use anyhow::{Error, Result, anyhow};
7use wasm_spec_interpreter::SpecValue;
8use wasmtime::Trap;
9
10/// A wrapper for `wasm-spec-interpreter` as a [`DiffEngine`].
11pub struct SpecInterpreter;
12
13impl SpecInterpreter {
14    pub(crate) fn new(config: &mut Config) -> Self {
15        let config = &mut config.module_config.config;
16
17        config.min_memories = config.min_memories.min(1);
18        config.max_memories = config.max_memories.min(1);
19        config.min_tables = config.min_tables.min(1);
20        config.max_tables = config.max_tables.min(1);
21
22        config.memory64_enabled = false;
23        config.threads_enabled = false;
24        config.bulk_memory_enabled = false;
25        config.reference_types_enabled = false;
26        config.tail_call_enabled = false;
27        config.relaxed_simd_enabled = false;
28        config.custom_page_sizes_enabled = false;
29        config.wide_arithmetic_enabled = false;
30        config.extended_const_enabled = false;
31        config.exceptions_enabled = false;
32
33        Self
34    }
35}
36
37impl DiffEngine for SpecInterpreter {
38    fn name(&self) -> &'static str {
39        "spec"
40    }
41
42    fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {
43        let instance = wasm_spec_interpreter::instantiate(wasm)
44            .map_err(|e| anyhow!("failed to instantiate in spec interpreter: {}", e))?;
45        Ok(Box::new(SpecInstance { instance }))
46    }
47
48    fn assert_error_match(&self, err: &Error, trap: &Trap) {
49        // TODO: implement this for the spec interpreter
50        let _ = (trap, err);
51    }
52
53    fn is_non_deterministic_error(&self, err: &Error) -> bool {
54        err.to_string().contains("(Isabelle) call stack exhausted")
55    }
56}
57
58struct SpecInstance {
59    instance: wasm_spec_interpreter::SpecInstance,
60}
61
62impl DiffInstance for SpecInstance {
63    fn name(&self) -> &'static str {
64        "spec"
65    }
66
67    fn evaluate(
68        &mut self,
69        function_name: &str,
70        arguments: &[DiffValue],
71        _results: &[DiffValueType],
72    ) -> Result<Option<Vec<DiffValue>>> {
73        let arguments = arguments.iter().map(SpecValue::from).collect();
74        match wasm_spec_interpreter::interpret(&self.instance, function_name, Some(arguments)) {
75            Ok(results) => Ok(Some(results.into_iter().map(SpecValue::into).collect())),
76            Err(err) => Err(anyhow!(err)),
77        }
78    }
79
80    fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> {
81        use wasm_spec_interpreter::{SpecExport::Global, export};
82        if let Ok(Global(g)) = export(&self.instance, name) {
83            Some(g.into())
84        } else {
85            panic!("expected an exported global value at name `{name}`")
86        }
87    }
88
89    fn get_memory(&mut self, name: &str, _shared: bool) -> Option<Vec<u8>> {
90        use wasm_spec_interpreter::{SpecExport::Memory, export};
91        if let Ok(Memory(m)) = export(&self.instance, name) {
92            Some(m)
93        } else {
94            panic!("expected an exported memory at name `{name}`")
95        }
96    }
97}
98
99impl From<&DiffValue> for SpecValue {
100    fn from(v: &DiffValue) -> Self {
101        match *v {
102            DiffValue::I32(n) => SpecValue::I32(n),
103            DiffValue::I64(n) => SpecValue::I64(n),
104            DiffValue::F32(n) => SpecValue::F32(n as i32),
105            DiffValue::F64(n) => SpecValue::F64(n as i64),
106            DiffValue::V128(n) => SpecValue::V128(n.to_le_bytes().to_vec()),
107            DiffValue::FuncRef { .. }
108            | DiffValue::ExternRef { .. }
109            | DiffValue::AnyRef { .. }
110            | DiffValue::ExnRef { .. }
111            | DiffValue::ContRef { .. } => {
112                unimplemented!()
113            }
114        }
115    }
116}
117
118impl From<SpecValue> for DiffValue {
119    fn from(spec: SpecValue) -> DiffValue {
120        match spec {
121            SpecValue::I32(n) => DiffValue::I32(n),
122            SpecValue::I64(n) => DiffValue::I64(n),
123            SpecValue::F32(n) => DiffValue::F32(n as u32),
124            SpecValue::F64(n) => DiffValue::F64(n as u64),
125            SpecValue::V128(n) => {
126                assert_eq!(n.len(), 16);
127                DiffValue::V128(u128::from_le_bytes(n.as_slice().try_into().unwrap()))
128            }
129        }
130    }
131}
132
133/// Set up the OCaml runtime for triggering its signal handler configuration.
134///
135/// Because both the OCaml runtime and Wasmtime set up signal handlers, we must
136/// carefully decide when to instantiate them; this function allows us to
137/// control when. Wasmtime uses these signal handlers for catching various
138/// WebAssembly failures. On certain OSes (e.g. Linux `x86_64`), the signal
139/// handlers interfere, observable as an uncaught `SIGSEGV`--not even caught by
140/// libFuzzer.
141///
142/// This failure can be mitigated by always running Wasmtime second in
143/// differential fuzzing. In some cases, however, this is not possible because
144/// which engine will execute first is unknown. This function can be explicitly
145/// executed first, e.g., during global initialization, to avoid this issue.
146pub fn setup_ocaml_runtime() {
147    wasm_spec_interpreter::setup_ocaml_runtime();
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    #[test]
155    fn smoke() {
156        if !wasm_spec_interpreter::support_compiled_in() {
157            return;
158        }
159        crate::oracles::engine::smoke_test_engine(|_, config| Ok(SpecInterpreter::new(config)))
160    }
161}