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