//! Interpret WebAssembly modules using the OCaml spec interpreter. //! //! ``` //! # use wasm_spec_interpreter::{SpecValue, interpret, instantiate}; //! let module = wat::parse_file("tests/add.wat").unwrap(); //! let instance = instantiate(&module).unwrap(); //! let parameters = vec![SpecValue::I32(42), SpecValue::I32(1)]; //! let results = interpret(&instance, "add", Some(parameters)).unwrap(); //! assert_eq!(results, &[SpecValue::I32(43)]); //! ``` //! //! ### Warning //! //! The OCaml runtime is [not re-entrant]. The code below must ensure that only //! one Rust thread is executing at a time (using the `INTERPRET` lock) or we //! may observe `SIGSEGV` failures, e.g., while running `cargo test`. //! //! [not re-entrant]: //! https://ocaml.org/manual/intfc.html#ss:parallel-execution-long-running-c-code //! //! ### Warning //! //! This module uses an unsafe approach (`OCamlRuntime::init_persistent()` + //! `OCamlRuntime::recover_handle()`) to initializing the `OCamlRuntime` based //! on some [discussion] with `ocaml-interop` crate authors. This approach was //! their recommendation to resolve seeing errors like `boxroot is not setup` //! followed by a `SIGSEGV`; this is similar to the testing approach [they use]. //! Use this approach with care and note that it is only as safe as the OCaml //! code running underneath. //! //! [discussion]: https://github.com/tezedge/ocaml-interop/issues/35 //! [they use]: //! https://github.com/tezedge/ocaml-interop/blob/master/testing/rust-caller/src/lib.rs use crate::{SpecExport, SpecInstance, SpecValue}; use ocaml_interop::{BoxRoot, OCamlRuntime, ToOCaml}; use once_cell::sync::Lazy; use std::sync::Mutex; static INTERPRET: Lazy> = Lazy::new(|| Mutex::new(())); /// Instantiate the WebAssembly module in the spec interpreter. pub fn instantiate(module: &[u8]) -> Result { let _lock = INTERPRET.lock().unwrap(); OCamlRuntime::init_persistent(); let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() }; let module = module.to_boxroot(ocaml_runtime); let instance = ocaml_bindings::instantiate(ocaml_runtime, &module); instance.to_rust(ocaml_runtime) } /// Interpret the exported function `name` with the given `parameters`. pub fn interpret( instance: &SpecInstance, name: &str, parameters: Option>, ) -> Result, String> { let _lock = INTERPRET.lock().unwrap(); OCamlRuntime::init_persistent(); let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() }; // Prepare the box-rooted parameters. let instance = instance.to_boxroot(ocaml_runtime); let name = name.to_string().to_boxroot(ocaml_runtime); let parameters = parameters.to_boxroot(ocaml_runtime); // Interpret the function. let results = ocaml_bindings::interpret(ocaml_runtime, &instance, &name, ¶meters); results.to_rust(&ocaml_runtime) } /// Interpret the first function in the passed WebAssembly module (in Wasm form, /// currently, not WAT), optionally with the given parameters. If no parameters /// are provided, the function is invoked with zeroed parameters. pub fn interpret_legacy( module: &[u8], opt_parameters: Option>, ) -> Result, String> { let _lock = INTERPRET.lock().unwrap(); OCamlRuntime::init_persistent(); let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() }; // Parse and execute, returning results converted to Rust. let module = module.to_boxroot(ocaml_runtime); let opt_parameters = opt_parameters.to_boxroot(ocaml_runtime); let results = ocaml_bindings::interpret_legacy(ocaml_runtime, &module, &opt_parameters); results.to_rust(ocaml_runtime) } /// Retrieve the export given by `name`. pub fn export(instance: &SpecInstance, name: &str) -> Result { let _lock = INTERPRET.lock().unwrap(); OCamlRuntime::init_persistent(); let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() }; // Prepare the box-rooted parameters. let instance = instance.to_boxroot(ocaml_runtime); let name = name.to_string().to_boxroot(ocaml_runtime); // Export the value. let results = ocaml_bindings::export(ocaml_runtime, &instance, &name); results.to_rust(&ocaml_runtime) } // Here we declare which functions we will use from the OCaml library. See // https://docs.rs/ocaml-interop/0.8.4/ocaml_interop/index.html#example. mod ocaml_bindings { use super::*; use ocaml_interop::{ impl_conv_ocaml_variant, ocaml, FromOCaml, OCaml, OCamlBytes, OCamlInt32, OCamlInt64, OCamlList, }; // Using this macro converts the enum both ways: Rust to OCaml and OCaml to // Rust. See // https://docs.rs/ocaml-interop/0.8.4/ocaml_interop/macro.impl_conv_ocaml_variant.html. impl_conv_ocaml_variant! { SpecValue { SpecValue::I32(i: OCamlInt32), SpecValue::I64(i: OCamlInt64), SpecValue::F32(i: OCamlInt32), SpecValue::F64(i: OCamlInt64), SpecValue::V128(i: OCamlBytes), } } // We need to also convert the `SpecExport` enum. impl_conv_ocaml_variant! { SpecExport { SpecExport::Global(i: SpecValue), SpecExport::Memory(i: OCamlBytes), } } // We manually show `SpecInstance` how to convert itself to and from OCaml. unsafe impl FromOCaml for SpecInstance { fn from_ocaml(v: OCaml) -> Self { Self { repr: BoxRoot::new(v), } } } unsafe impl ToOCaml for SpecInstance { fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, SpecInstance> { BoxRoot::get(&self.repr, cr) } } // These functions must be exposed from OCaml with: // `Callback.register "interpret" interpret` // // In Rust, these functions look like: // `pub fn interpret(_: &mut OCamlRuntime, ...: OCamlRef<...>) -> BoxRoot<...>;` // // The `ocaml!` macro does not understand documentation, so the // documentation is included here: // - `instantiate`: clear the global store and instantiate a new WebAssembly // module from bytes // - `interpret`: given an instance, call the function exported at `name` // - `interpret_legacy`: starting from bytes, instantiate and execute the // first exported function // - `export`: given an instance, get the value of the export at `name` ocaml! { pub fn instantiate(module: OCamlBytes) -> Result; pub fn interpret(instance: SpecInstance, name: String, params: Option>) -> Result, String>; pub fn interpret_legacy(module: OCamlBytes, params: Option>) -> Result, String>; pub fn export(instance: SpecInstance, name: String) -> Result; } } /// Initialize a persistent OCaml runtime. /// /// When used for fuzzing differentially with engines that also use signal /// handlers, this function provides a way to explicitly set up the OCaml /// runtime and configure its signal handlers. pub fn setup_ocaml_runtime() { let _lock = INTERPRET.lock().unwrap(); OCamlRuntime::init_persistent(); } #[cfg(test)] mod tests { use super::*; #[test] fn invalid_function_name() { let module = wat::parse_file("tests/add.wat").unwrap(); let instance = instantiate(&module).unwrap(); let results = interpret( &instance, "not-the-right-name", Some(vec![SpecValue::I32(0), SpecValue::I32(0)]), ); assert_eq!(results, Err("Not_found".to_string())); } #[test] fn multiple_invocation() { let module = wat::parse_file("tests/add.wat").unwrap(); let instance = instantiate(&module).unwrap(); let results1 = interpret( &instance, "add", Some(vec![SpecValue::I32(42), SpecValue::I32(1)]), ) .unwrap(); let results2 = interpret( &instance, "add", Some(vec![SpecValue::I32(1), SpecValue::I32(42)]), ) .unwrap(); assert_eq!(results1, results2); let results3 = interpret( &instance, "add", Some(vec![SpecValue::I32(20), SpecValue::I32(23)]), ) .unwrap(); assert_eq!(results2, results3); } #[test] fn multiple_invocation_legacy() { let module = wat::parse_file("tests/add.wat").unwrap(); let results1 = interpret_legacy(&module, Some(vec![SpecValue::I32(42), SpecValue::I32(1)])).unwrap(); let results2 = interpret_legacy(&module, Some(vec![SpecValue::I32(1), SpecValue::I32(42)])).unwrap(); assert_eq!(results1, results2); let results3 = interpret_legacy(&module, Some(vec![SpecValue::I32(20), SpecValue::I32(23)])).unwrap(); assert_eq!(results2, results3); } #[test] fn oob() { let module = wat::parse_file("tests/oob.wat").unwrap(); let instance = instantiate(&module).unwrap(); let results = interpret(&instance, "oob", None); assert_eq!( results, Err("Error(_, \"(Isabelle) trap: load\")".to_string()) ); } #[test] fn oob_legacy() { let module = wat::parse_file("tests/oob.wat").unwrap(); let results = interpret_legacy(&module, None); assert_eq!( results, Err("Error(_, \"(Isabelle) trap: load\")".to_string()) ); } #[test] fn simd_not() { let module = wat::parse_file("tests/simd_not.wat").unwrap(); let instance = instantiate(&module).unwrap(); let parameters = Some(vec![SpecValue::V128(vec![ 0, 255, 0, 0, 255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, ])]); let results = interpret(&instance, "simd_not", parameters).unwrap(); assert_eq!( results, vec![SpecValue::V128(vec![ 255, 0, 255, 255, 0, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 255 ])] ); } #[test] fn simd_not_legacy() { let module = wat::parse_file("tests/simd_not.wat").unwrap(); let parameters = Some(vec![SpecValue::V128(vec![ 0, 255, 0, 0, 255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, ])]); let results = interpret_legacy(&module, parameters).unwrap(); assert_eq!( results, vec![SpecValue::V128(vec![ 255, 0, 255, 255, 0, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 255 ])] ); } // See issue https://github.com/bytecodealliance/wasmtime/issues/4671. #[test] fn order_of_params() { let module = wat::parse_file("tests/shr_s.wat").unwrap(); let instance = instantiate(&module).unwrap(); let parameters = Some(vec![ SpecValue::I32(1795123818), SpecValue::I32(-2147483648), ]); let results = interpret(&instance, "test", parameters).unwrap(); assert_eq!(results, vec![SpecValue::I32(1795123818)]); } // See issue https://github.com/bytecodealliance/wasmtime/issues/4671. #[test] fn order_of_params_legacy() { let module = wat::parse_file("tests/shr_s.wat").unwrap(); let parameters = Some(vec![ SpecValue::I32(1795123818), SpecValue::I32(-2147483648), ]); let results = interpret_legacy(&module, parameters).unwrap(); assert_eq!(results, vec![SpecValue::I32(1795123818)]); } #[test] fn load_store_and_export() { let module = wat::parse_file("tests/memory.wat").unwrap(); let instance = instantiate(&module).unwrap(); // Store 42 at offset 4. let _ = interpret( &instance, "store_i32", Some(vec![SpecValue::I32(4), SpecValue::I32(42)]), ); // Load an i32 from offset 4. let loaded = interpret(&instance, "load_i32", Some(vec![SpecValue::I32(4)])); // Check stored value was retrieved. assert_eq!(loaded.unwrap(), vec![SpecValue::I32(42)]); // Retrieve the memory exported with name "mem" and check that the // 32-bit value at byte offset 4 of memory is 42. let export = export(&instance, "mem"); match export.unwrap() { SpecExport::Global(_) => panic!("incorrect export"), SpecExport::Memory(m) => { assert_eq!(&m[0..10], [0, 0, 0, 0, 42, 0, 0, 0, 0, 0]); } } } }