1 //! Evaluate an exported Wasm function using the WebAssembly specification
2 //! reference interpreter.
3 
4 use crate::generators::{DiffValue, DiffValueType, ModuleConfig};
5 use crate::oracles::engine::{DiffEngine, DiffInstance};
6 use anyhow::{anyhow, bail, Error, Result};
7 use wasm_spec_interpreter::Value;
8 use wasmtime::Trap;
9 
10 /// A wrapper for `wasm-spec-interpreter` as a [`DiffEngine`].
11 pub struct SpecInterpreter;
12 
13 impl SpecInterpreter {
14     /// Build a new [`SpecInterpreter`] but only if the configuration does not
15     /// rely on features that the current bindings (i.e.,
16     /// `wasm-spec-interpreter`) do not support.
17     pub fn new(config: &ModuleConfig) -> Result<Self> {
18         if config.config.reference_types_enabled {
19             bail!("the spec interpreter bindings do not support reference types")
20         }
21         // TODO: right now the interpreter bindings only execute the first
22         // function in the module so if there's possibly more than one function
23         // it's not possible to run the other function. This should be fixed
24         // with improvements to the ocaml bindings to the interpreter.
25         if config.config.max_funcs > 1 {
26             bail!("the spec interpreter bindings can only support one function for now")
27         }
28 
29         // TODO: right now the instantiation step for the interpreter does
30         // nothing and the evaluation step performs an instantiation followed by
31         // an execution. This means that instantiations which fail in other
32         // engines will "succeed" in the interpreter because the error is
33         // delayed to the execution. This should be fixed by making
34         // instantiation a first-class primitive in our interpreter bindings.
35         if config.config.max_tables > 0 {
36             bail!("the spec interpreter bindings do not fail as they should with out-of-bounds table accesses")
37         }
38 
39         if config.config.memory64_enabled {
40             bail!("memory64 not implemented in spec interpreter");
41         }
42 
43         if config.config.threads_enabled {
44             bail!("spec interpreter does not support the threading proposal");
45         }
46         Ok(Self)
47     }
48 }
49 
50 impl DiffEngine for SpecInterpreter {
51     fn name(&self) -> &'static str {
52         "spec"
53     }
54 
55     fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {
56         // TODO: ideally we would avoid copying the module bytes here.
57         Ok(Box::new(SpecInstance {
58             wasm: wasm.to_vec(),
59         }))
60     }
61 
62     fn assert_error_match(&self, trap: &Trap, err: Error) {
63         // TODO: implement this for the spec interpreter
64         drop((trap, err));
65     }
66 }
67 
68 struct SpecInstance {
69     wasm: Vec<u8>,
70 }
71 
72 impl DiffInstance for SpecInstance {
73     fn name(&self) -> &'static str {
74         "spec"
75     }
76 
77     fn evaluate(
78         &mut self,
79         _function_name: &str,
80         arguments: &[DiffValue],
81         _results: &[DiffValueType],
82     ) -> Result<Option<Vec<DiffValue>>> {
83         // The spec interpreter needs some work before it can fully support this
84         // interface:
85         //  - TODO adapt `wasm-spec-interpreter` to use function name to select
86         //    function to run
87         //  - TODO adapt `wasm-spec-interpreter` to expose an "instance" with
88         //    so we can hash memory, globals, etc.
89         let arguments = arguments.iter().map(Value::from).collect();
90         match wasm_spec_interpreter::interpret(&self.wasm, Some(arguments)) {
91             Ok(results) => Ok(Some(results.into_iter().map(Value::into).collect())),
92             Err(err) => Err(anyhow!(err)),
93         }
94     }
95 
96     fn get_global(&mut self, _name: &str, _ty: DiffValueType) -> Option<DiffValue> {
97         // TODO: should implement this
98         None
99     }
100 
101     fn get_memory(&mut self, _name: &str, _shared: bool) -> Option<Vec<u8>> {
102         // TODO: should implement this
103         None
104     }
105 }
106 
107 impl From<&DiffValue> for Value {
108     fn from(v: &DiffValue) -> Self {
109         match *v {
110             DiffValue::I32(n) => Value::I32(n),
111             DiffValue::I64(n) => Value::I64(n),
112             DiffValue::F32(n) => Value::F32(n as i32),
113             DiffValue::F64(n) => Value::F64(n as i64),
114             DiffValue::V128(n) => Value::V128(n.to_le_bytes().to_vec()),
115             DiffValue::FuncRef { .. } | DiffValue::ExternRef { .. } => unimplemented!(),
116         }
117     }
118 }
119 
120 impl Into<DiffValue> for Value {
121     fn into(self) -> DiffValue {
122         match self {
123             Value::I32(n) => DiffValue::I32(n),
124             Value::I64(n) => DiffValue::I64(n),
125             Value::F32(n) => DiffValue::F32(n as u32),
126             Value::F64(n) => DiffValue::F64(n as u64),
127             Value::V128(n) => {
128                 assert_eq!(n.len(), 16);
129                 DiffValue::V128(u128::from_le_bytes(n.as_slice().try_into().unwrap()))
130             }
131         }
132     }
133 }
134 
135 /// Set up the OCaml runtime for triggering its signal handler configuration.
136 ///
137 /// Because both the OCaml runtime and Wasmtime set up signal handlers, we must
138 /// carefully decide when to instantiate them; this function allows us to
139 /// control when. Wasmtime uses these signal handlers for catching various
140 /// WebAssembly failures. On certain OSes (e.g. Linux `x86_64`), the signal
141 /// handlers interfere, observable as an uncaught `SIGSEGV`--not even caught by
142 /// libFuzzer.
143 ///
144 /// This failure can be mitigated by always running Wasmtime second in
145 /// differential fuzzing. In some cases, however, this is not possible because
146 /// which engine will execute first is unknown. This function can be explicitly
147 /// executed first, e.g., during global initialization, to avoid this issue.
148 pub fn setup_ocaml_runtime() {
149     wasm_spec_interpreter::setup_ocaml_runtime();
150 }
151 
152 #[test]
153 fn smoke() {
154     if !wasm_spec_interpreter::support_compiled_in() {
155         return;
156     }
157     crate::oracles::engine::smoke_test_engine(|config| SpecInterpreter::new(&config.module_config))
158 }
159