1 //! Interpret WebAssembly modules using the OCaml spec interpreter.
2 //!
3 //! ```
4 //! # use wasm_spec_interpreter::{SpecValue, interpret, instantiate};
5 //! let module = wat::parse_file("tests/add.wat").unwrap();
6 //! let instance = instantiate(&module).unwrap();
7 //! let parameters = vec![SpecValue::I32(42), SpecValue::I32(1)];
8 //! let results = interpret(&instance, "add", Some(parameters)).unwrap();
9 //! assert_eq!(results, &[SpecValue::I32(43)]);
10 //! ```
11 //!
12 //! ### Warning
13 //!
14 //! The OCaml runtime is [not re-entrant]. The code below must ensure that only
15 //! one Rust thread is executing at a time (using the `INTERPRET` lock) or we
16 //! may observe `SIGSEGV` failures, e.g., while running `cargo test`.
17 //!
18 //! [not re-entrant]:
19 //!     https://ocaml.org/manual/intfc.html#ss:parallel-execution-long-running-c-code
20 //!
21 //! ### Warning
22 //!
23 //! This module uses an unsafe approach (`OCamlRuntime::init_persistent()` +
24 //! `OCamlRuntime::recover_handle()`) to initializing the `OCamlRuntime` based
25 //! on some [discussion] with `ocaml-interop` crate authors. This approach was
26 //! their recommendation to resolve seeing errors like `boxroot is not setup`
27 //! followed by a `SIGSEGV`; this is similar to the testing approach [they use].
28 //! Use this approach with care and note that it is only as safe as the OCaml
29 //! code running underneath.
30 //!
31 //! [discussion]: https://github.com/tezedge/ocaml-interop/issues/35
32 //! [they use]:
33 //!     https://github.com/tezedge/ocaml-interop/blob/master/testing/rust-caller/src/lib.rs
34 
35 use crate::{SpecExport, SpecInstance, SpecValue};
36 use ocaml_interop::{BoxRoot, OCamlRuntime, ToOCaml};
37 use std::sync::Mutex;
38 
39 static INTERPRET: Mutex<()> = Mutex::new(());
40 
41 /// Instantiate the WebAssembly module in the spec interpreter.
instantiate(module: &[u8]) -> Result<SpecInstance, String>42 pub fn instantiate(module: &[u8]) -> Result<SpecInstance, String> {
43     let _lock = INTERPRET.lock().unwrap();
44     OCamlRuntime::init_persistent();
45     let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() };
46 
47     let module = module.to_boxroot(ocaml_runtime);
48     let instance = ocaml_bindings::instantiate(ocaml_runtime, &module);
49     instance.to_rust(ocaml_runtime)
50 }
51 
52 /// Interpret the exported function `name` with the given `parameters`.
interpret( instance: &SpecInstance, name: &str, parameters: Option<Vec<SpecValue>>, ) -> Result<Vec<SpecValue>, String>53 pub fn interpret(
54     instance: &SpecInstance,
55     name: &str,
56     parameters: Option<Vec<SpecValue>>,
57 ) -> Result<Vec<SpecValue>, String> {
58     let _lock = INTERPRET.lock().unwrap();
59     OCamlRuntime::init_persistent();
60     let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() };
61 
62     // Prepare the box-rooted parameters.
63     let instance = instance.to_boxroot(ocaml_runtime);
64     let name = name.to_string().to_boxroot(ocaml_runtime);
65     let parameters = parameters.to_boxroot(ocaml_runtime);
66 
67     // Interpret the function.
68     let results = ocaml_bindings::interpret(ocaml_runtime, &instance, &name, &parameters);
69     results.to_rust(&ocaml_runtime)
70 }
71 
72 /// Interpret the first function in the passed WebAssembly module (in Wasm form,
73 /// currently, not WAT), optionally with the given parameters. If no parameters
74 /// are provided, the function is invoked with zeroed parameters.
interpret_legacy( module: &[u8], opt_parameters: Option<Vec<SpecValue>>, ) -> Result<Vec<SpecValue>, String>75 pub fn interpret_legacy(
76     module: &[u8],
77     opt_parameters: Option<Vec<SpecValue>>,
78 ) -> Result<Vec<SpecValue>, String> {
79     let _lock = INTERPRET.lock().unwrap();
80     OCamlRuntime::init_persistent();
81     let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() };
82 
83     // Parse and execute, returning results converted to Rust.
84     let module = module.to_boxroot(ocaml_runtime);
85     let opt_parameters = opt_parameters.to_boxroot(ocaml_runtime);
86     let results = ocaml_bindings::interpret_legacy(ocaml_runtime, &module, &opt_parameters);
87     results.to_rust(ocaml_runtime)
88 }
89 
90 /// Retrieve the export given by `name`.
export(instance: &SpecInstance, name: &str) -> Result<SpecExport, String>91 pub fn export(instance: &SpecInstance, name: &str) -> Result<SpecExport, String> {
92     let _lock = INTERPRET.lock().unwrap();
93     OCamlRuntime::init_persistent();
94     let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() };
95 
96     // Prepare the box-rooted parameters.
97     let instance = instance.to_boxroot(ocaml_runtime);
98     let name = name.to_string().to_boxroot(ocaml_runtime);
99 
100     // Export the value.
101     let results = ocaml_bindings::export(ocaml_runtime, &instance, &name);
102     results.to_rust(&ocaml_runtime)
103 }
104 
105 // Here we declare which functions we will use from the OCaml library. See
106 // https://docs.rs/ocaml-interop/0.8.4/ocaml_interop/index.html#example.
107 mod ocaml_bindings {
108     use super::*;
109     use ocaml_interop::{
110         FromOCaml, OCaml, OCamlBytes, OCamlInt32, OCamlInt64, OCamlList, impl_conv_ocaml_variant,
111         ocaml,
112     };
113 
114     // Using this macro converts the enum both ways: Rust to OCaml and OCaml to
115     // Rust. See
116     // https://docs.rs/ocaml-interop/0.8.4/ocaml_interop/macro.impl_conv_ocaml_variant.html.
117     impl_conv_ocaml_variant! {
118         SpecValue {
119             SpecValue::I32(i: OCamlInt32),
120             SpecValue::I64(i: OCamlInt64),
121             SpecValue::F32(i: OCamlInt32),
122             SpecValue::F64(i: OCamlInt64),
123             SpecValue::V128(i: OCamlBytes),
124         }
125     }
126 
127     // We need to also convert the `SpecExport` enum.
128     impl_conv_ocaml_variant! {
129         SpecExport {
130             SpecExport::Global(i: SpecValue),
131             SpecExport::Memory(i: OCamlBytes),
132         }
133     }
134 
135     // We manually show `SpecInstance` how to convert itself to and from OCaml.
136     unsafe impl FromOCaml<SpecInstance> for SpecInstance {
from_ocaml(v: OCaml<SpecInstance>) -> Self137         fn from_ocaml(v: OCaml<SpecInstance>) -> Self {
138             Self {
139                 repr: BoxRoot::new(v),
140             }
141         }
142     }
143     unsafe impl ToOCaml<SpecInstance> for SpecInstance {
to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, SpecInstance>144         fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, SpecInstance> {
145             BoxRoot::get(&self.repr, cr)
146         }
147     }
148 
149     // These functions must be exposed from OCaml with:
150     //  `Callback.register "interpret" interpret`
151     //
152     // In Rust, these functions look like:
153     //   `pub fn interpret(_: &mut OCamlRuntime, ...: OCamlRef<...>) -> BoxRoot<...>;`
154     //
155     // The `ocaml!` macro does not understand documentation, so the
156     // documentation is included here:
157     // - `instantiate`: clear the global store and instantiate a new WebAssembly
158     //   module from bytes
159     // - `interpret`: given an instance, call the function exported at `name`
160     // - `interpret_legacy`: starting from bytes, instantiate and execute the
161     //   first exported function
162     // - `export`: given an instance, get the value of the export at `name`
163     ocaml! {
164         pub fn instantiate(module: OCamlBytes) -> Result<SpecInstance, String>;
165         pub fn interpret(instance: SpecInstance, name: String, params: Option<OCamlList<SpecValue>>) -> Result<OCamlList<SpecValue>, String>;
166         pub fn interpret_legacy(module: OCamlBytes, params: Option<OCamlList<SpecValue>>) -> Result<OCamlList<SpecValue>, String>;
167         pub fn export(instance: SpecInstance, name: String) -> Result<SpecExport, String>;
168     }
169 }
170 
171 /// Initialize a persistent OCaml runtime.
172 ///
173 /// When used for fuzzing differentially with engines that also use signal
174 /// handlers, this function provides a way to explicitly set up the OCaml
175 /// runtime and configure its signal handlers.
setup_ocaml_runtime()176 pub fn setup_ocaml_runtime() {
177     let _lock = INTERPRET.lock().unwrap();
178     OCamlRuntime::init_persistent();
179 }
180 
181 #[cfg(test)]
182 mod tests {
183     use super::*;
184 
185     #[test]
invalid_function_name()186     fn invalid_function_name() {
187         let module = wat::parse_file("tests/add.wat").unwrap();
188         let instance = instantiate(&module).unwrap();
189         let results = interpret(
190             &instance,
191             "not-the-right-name",
192             Some(vec![SpecValue::I32(0), SpecValue::I32(0)]),
193         );
194         assert_eq!(results, Err("Not_found".to_string()));
195     }
196 
197     #[test]
multiple_invocation()198     fn multiple_invocation() {
199         let module = wat::parse_file("tests/add.wat").unwrap();
200         let instance = instantiate(&module).unwrap();
201 
202         let results1 = interpret(
203             &instance,
204             "add",
205             Some(vec![SpecValue::I32(42), SpecValue::I32(1)]),
206         )
207         .unwrap();
208         let results2 = interpret(
209             &instance,
210             "add",
211             Some(vec![SpecValue::I32(1), SpecValue::I32(42)]),
212         )
213         .unwrap();
214         assert_eq!(results1, results2);
215 
216         let results3 = interpret(
217             &instance,
218             "add",
219             Some(vec![SpecValue::I32(20), SpecValue::I32(23)]),
220         )
221         .unwrap();
222         assert_eq!(results2, results3);
223     }
224 
225     #[test]
multiple_invocation_legacy()226     fn multiple_invocation_legacy() {
227         let module = wat::parse_file("tests/add.wat").unwrap();
228 
229         let results1 =
230             interpret_legacy(&module, Some(vec![SpecValue::I32(42), SpecValue::I32(1)])).unwrap();
231         let results2 =
232             interpret_legacy(&module, Some(vec![SpecValue::I32(1), SpecValue::I32(42)])).unwrap();
233         assert_eq!(results1, results2);
234 
235         let results3 =
236             interpret_legacy(&module, Some(vec![SpecValue::I32(20), SpecValue::I32(23)])).unwrap();
237         assert_eq!(results2, results3);
238     }
239 
240     #[test]
oob()241     fn oob() {
242         let module = wat::parse_file("tests/oob.wat").unwrap();
243         let instance = instantiate(&module).unwrap();
244         let results = interpret(&instance, "oob", None);
245         assert_eq!(
246             results,
247             Err("Error(_, \"(Isabelle) trap: load\")".to_string())
248         );
249     }
250 
251     #[test]
oob_legacy()252     fn oob_legacy() {
253         let module = wat::parse_file("tests/oob.wat").unwrap();
254         let results = interpret_legacy(&module, None);
255         assert_eq!(
256             results,
257             Err("Error(_, \"(Isabelle) trap: load\")".to_string())
258         );
259     }
260 
261     #[test]
simd_not()262     fn simd_not() {
263         let module = wat::parse_file("tests/simd_not.wat").unwrap();
264         let instance = instantiate(&module).unwrap();
265 
266         let parameters = Some(vec![SpecValue::V128(vec![
267             0, 255, 0, 0, 255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0,
268         ])]);
269         let results = interpret(&instance, "simd_not", parameters).unwrap();
270 
271         assert_eq!(
272             results,
273             vec![SpecValue::V128(vec![
274                 255, 0, 255, 255, 0, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 255
275             ])]
276         );
277     }
278 
279     #[test]
simd_not_legacy()280     fn simd_not_legacy() {
281         let module = wat::parse_file("tests/simd_not.wat").unwrap();
282 
283         let parameters = Some(vec![SpecValue::V128(vec![
284             0, 255, 0, 0, 255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0,
285         ])]);
286         let results = interpret_legacy(&module, parameters).unwrap();
287 
288         assert_eq!(
289             results,
290             vec![SpecValue::V128(vec![
291                 255, 0, 255, 255, 0, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 255
292             ])]
293         );
294     }
295 
296     // See issue https://github.com/bytecodealliance/wasmtime/issues/4671.
297     #[test]
order_of_params()298     fn order_of_params() {
299         let module = wat::parse_file("tests/shr_s.wat").unwrap();
300         let instance = instantiate(&module).unwrap();
301 
302         let parameters = Some(vec![
303             SpecValue::I32(1795123818),
304             SpecValue::I32(-2147483648),
305         ]);
306         let results = interpret(&instance, "test", parameters).unwrap();
307 
308         assert_eq!(results, vec![SpecValue::I32(1795123818)]);
309     }
310 
311     // See issue https://github.com/bytecodealliance/wasmtime/issues/4671.
312     #[test]
order_of_params_legacy()313     fn order_of_params_legacy() {
314         let module = wat::parse_file("tests/shr_s.wat").unwrap();
315 
316         let parameters = Some(vec![
317             SpecValue::I32(1795123818),
318             SpecValue::I32(-2147483648),
319         ]);
320         let results = interpret_legacy(&module, parameters).unwrap();
321 
322         assert_eq!(results, vec![SpecValue::I32(1795123818)]);
323     }
324 
325     #[test]
load_store_and_export()326     fn load_store_and_export() {
327         let module = wat::parse_file("tests/memory.wat").unwrap();
328         let instance = instantiate(&module).unwrap();
329 
330         // Store 42 at offset 4.
331         let _ = interpret(
332             &instance,
333             "store_i32",
334             Some(vec![SpecValue::I32(4), SpecValue::I32(42)]),
335         );
336 
337         // Load an i32 from offset 4.
338         let loaded = interpret(&instance, "load_i32", Some(vec![SpecValue::I32(4)]));
339 
340         // Check stored value was retrieved.
341         assert_eq!(loaded.unwrap(), vec![SpecValue::I32(42)]);
342 
343         // Retrieve the memory exported with name "mem" and check that the
344         // 32-bit value at byte offset 4 of memory is 42.
345         let export = export(&instance, "mem");
346         match export.unwrap() {
347             SpecExport::Global(_) => panic!("incorrect export"),
348             SpecExport::Memory(m) => {
349                 assert_eq!(&m[0..10], [0, 0, 0, 0, 42, 0, 0, 0, 0, 0]);
350             }
351         }
352     }
353 }
354