Path: blob/main/crates/fuzzing/wasm-spec-interpreter/src/with_library.rs
3069 views
//! Interpret WebAssembly modules using the OCaml spec interpreter.1//!2//! ```3//! # use wasm_spec_interpreter::{SpecValue, interpret, instantiate};4//! let module = wat::parse_file("tests/add.wat").unwrap();5//! let instance = instantiate(&module).unwrap();6//! let parameters = vec![SpecValue::I32(42), SpecValue::I32(1)];7//! let results = interpret(&instance, "add", Some(parameters)).unwrap();8//! assert_eq!(results, &[SpecValue::I32(43)]);9//! ```10//!11//! ### Warning12//!13//! The OCaml runtime is [not re-entrant]. The code below must ensure that only14//! one Rust thread is executing at a time (using the `INTERPRET` lock) or we15//! may observe `SIGSEGV` failures, e.g., while running `cargo test`.16//!17//! [not re-entrant]:18//! https://ocaml.org/manual/intfc.html#ss:parallel-execution-long-running-c-code19//!20//! ### Warning21//!22//! This module uses an unsafe approach (`OCamlRuntime::init_persistent()` +23//! `OCamlRuntime::recover_handle()`) to initializing the `OCamlRuntime` based24//! on some [discussion] with `ocaml-interop` crate authors. This approach was25//! their recommendation to resolve seeing errors like `boxroot is not setup`26//! followed by a `SIGSEGV`; this is similar to the testing approach [they use].27//! Use this approach with care and note that it is only as safe as the OCaml28//! code running underneath.29//!30//! [discussion]: https://github.com/tezedge/ocaml-interop/issues/3531//! [they use]:32//! https://github.com/tezedge/ocaml-interop/blob/master/testing/rust-caller/src/lib.rs3334use crate::{SpecExport, SpecInstance, SpecValue};35use ocaml_interop::{BoxRoot, OCamlRuntime, ToOCaml};36use std::sync::Mutex;3738static INTERPRET: Mutex<()> = Mutex::new(());3940/// Instantiate the WebAssembly module in the spec interpreter.41pub fn instantiate(module: &[u8]) -> Result<SpecInstance, String> {42let _lock = INTERPRET.lock().unwrap();43OCamlRuntime::init_persistent();44let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() };4546let module = module.to_boxroot(ocaml_runtime);47let instance = ocaml_bindings::instantiate(ocaml_runtime, &module);48instance.to_rust(ocaml_runtime)49}5051/// Interpret the exported function `name` with the given `parameters`.52pub fn interpret(53instance: &SpecInstance,54name: &str,55parameters: Option<Vec<SpecValue>>,56) -> Result<Vec<SpecValue>, String> {57let _lock = INTERPRET.lock().unwrap();58OCamlRuntime::init_persistent();59let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() };6061// Prepare the box-rooted parameters.62let instance = instance.to_boxroot(ocaml_runtime);63let name = name.to_string().to_boxroot(ocaml_runtime);64let parameters = parameters.to_boxroot(ocaml_runtime);6566// Interpret the function.67let results = ocaml_bindings::interpret(ocaml_runtime, &instance, &name, ¶meters);68results.to_rust(&ocaml_runtime)69}7071/// Interpret the first function in the passed WebAssembly module (in Wasm form,72/// currently, not WAT), optionally with the given parameters. If no parameters73/// are provided, the function is invoked with zeroed parameters.74pub fn interpret_legacy(75module: &[u8],76opt_parameters: Option<Vec<SpecValue>>,77) -> Result<Vec<SpecValue>, String> {78let _lock = INTERPRET.lock().unwrap();79OCamlRuntime::init_persistent();80let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() };8182// Parse and execute, returning results converted to Rust.83let module = module.to_boxroot(ocaml_runtime);84let opt_parameters = opt_parameters.to_boxroot(ocaml_runtime);85let results = ocaml_bindings::interpret_legacy(ocaml_runtime, &module, &opt_parameters);86results.to_rust(ocaml_runtime)87}8889/// Retrieve the export given by `name`.90pub fn export(instance: &SpecInstance, name: &str) -> Result<SpecExport, String> {91let _lock = INTERPRET.lock().unwrap();92OCamlRuntime::init_persistent();93let ocaml_runtime = unsafe { OCamlRuntime::recover_handle() };9495// Prepare the box-rooted parameters.96let instance = instance.to_boxroot(ocaml_runtime);97let name = name.to_string().to_boxroot(ocaml_runtime);9899// Export the value.100let results = ocaml_bindings::export(ocaml_runtime, &instance, &name);101results.to_rust(&ocaml_runtime)102}103104// Here we declare which functions we will use from the OCaml library. See105// https://docs.rs/ocaml-interop/0.8.4/ocaml_interop/index.html#example.106mod ocaml_bindings {107use super::*;108use ocaml_interop::{109FromOCaml, OCaml, OCamlBytes, OCamlInt32, OCamlInt64, OCamlList, impl_conv_ocaml_variant,110ocaml,111};112113// Using this macro converts the enum both ways: Rust to OCaml and OCaml to114// Rust. See115// https://docs.rs/ocaml-interop/0.8.4/ocaml_interop/macro.impl_conv_ocaml_variant.html.116impl_conv_ocaml_variant! {117SpecValue {118SpecValue::I32(i: OCamlInt32),119SpecValue::I64(i: OCamlInt64),120SpecValue::F32(i: OCamlInt32),121SpecValue::F64(i: OCamlInt64),122SpecValue::V128(i: OCamlBytes),123}124}125126// We need to also convert the `SpecExport` enum.127impl_conv_ocaml_variant! {128SpecExport {129SpecExport::Global(i: SpecValue),130SpecExport::Memory(i: OCamlBytes),131}132}133134// We manually show `SpecInstance` how to convert itself to and from OCaml.135unsafe impl FromOCaml<SpecInstance> for SpecInstance {136fn from_ocaml(v: OCaml<SpecInstance>) -> Self {137Self {138repr: BoxRoot::new(v),139}140}141}142unsafe impl ToOCaml<SpecInstance> for SpecInstance {143fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, SpecInstance> {144BoxRoot::get(&self.repr, cr)145}146}147148// These functions must be exposed from OCaml with:149// `Callback.register "interpret" interpret`150//151// In Rust, these functions look like:152// `pub fn interpret(_: &mut OCamlRuntime, ...: OCamlRef<...>) -> BoxRoot<...>;`153//154// The `ocaml!` macro does not understand documentation, so the155// documentation is included here:156// - `instantiate`: clear the global store and instantiate a new WebAssembly157// module from bytes158// - `interpret`: given an instance, call the function exported at `name`159// - `interpret_legacy`: starting from bytes, instantiate and execute the160// first exported function161// - `export`: given an instance, get the value of the export at `name`162ocaml! {163pub fn instantiate(module: OCamlBytes) -> Result<SpecInstance, String>;164pub fn interpret(instance: SpecInstance, name: String, params: Option<OCamlList<SpecValue>>) -> Result<OCamlList<SpecValue>, String>;165pub fn interpret_legacy(module: OCamlBytes, params: Option<OCamlList<SpecValue>>) -> Result<OCamlList<SpecValue>, String>;166pub fn export(instance: SpecInstance, name: String) -> Result<SpecExport, String>;167}168}169170/// Initialize a persistent OCaml runtime.171///172/// When used for fuzzing differentially with engines that also use signal173/// handlers, this function provides a way to explicitly set up the OCaml174/// runtime and configure its signal handlers.175pub fn setup_ocaml_runtime() {176let _lock = INTERPRET.lock().unwrap();177OCamlRuntime::init_persistent();178}179180#[cfg(test)]181mod tests {182use super::*;183184#[test]185fn invalid_function_name() {186let module = wat::parse_file("tests/add.wat").unwrap();187let instance = instantiate(&module).unwrap();188let results = interpret(189&instance,190"not-the-right-name",191Some(vec![SpecValue::I32(0), SpecValue::I32(0)]),192);193assert_eq!(results, Err("Not_found".to_string()));194}195196#[test]197fn multiple_invocation() {198let module = wat::parse_file("tests/add.wat").unwrap();199let instance = instantiate(&module).unwrap();200201let results1 = interpret(202&instance,203"add",204Some(vec![SpecValue::I32(42), SpecValue::I32(1)]),205)206.unwrap();207let results2 = interpret(208&instance,209"add",210Some(vec![SpecValue::I32(1), SpecValue::I32(42)]),211)212.unwrap();213assert_eq!(results1, results2);214215let results3 = interpret(216&instance,217"add",218Some(vec![SpecValue::I32(20), SpecValue::I32(23)]),219)220.unwrap();221assert_eq!(results2, results3);222}223224#[test]225fn multiple_invocation_legacy() {226let module = wat::parse_file("tests/add.wat").unwrap();227228let results1 =229interpret_legacy(&module, Some(vec![SpecValue::I32(42), SpecValue::I32(1)])).unwrap();230let results2 =231interpret_legacy(&module, Some(vec![SpecValue::I32(1), SpecValue::I32(42)])).unwrap();232assert_eq!(results1, results2);233234let results3 =235interpret_legacy(&module, Some(vec![SpecValue::I32(20), SpecValue::I32(23)])).unwrap();236assert_eq!(results2, results3);237}238239#[test]240fn oob() {241let module = wat::parse_file("tests/oob.wat").unwrap();242let instance = instantiate(&module).unwrap();243let results = interpret(&instance, "oob", None);244assert_eq!(245results,246Err("Error(_, \"(Isabelle) trap: load\")".to_string())247);248}249250#[test]251fn oob_legacy() {252let module = wat::parse_file("tests/oob.wat").unwrap();253let results = interpret_legacy(&module, None);254assert_eq!(255results,256Err("Error(_, \"(Isabelle) trap: load\")".to_string())257);258}259260#[test]261fn simd_not() {262let module = wat::parse_file("tests/simd_not.wat").unwrap();263let instance = instantiate(&module).unwrap();264265let parameters = Some(vec![SpecValue::V128(vec![2660, 255, 0, 0, 255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0,267])]);268let results = interpret(&instance, "simd_not", parameters).unwrap();269270assert_eq!(271results,272vec![SpecValue::V128(vec![273255, 0, 255, 255, 0, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 255274])]275);276}277278#[test]279fn simd_not_legacy() {280let module = wat::parse_file("tests/simd_not.wat").unwrap();281282let parameters = Some(vec![SpecValue::V128(vec![2830, 255, 0, 0, 255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0,284])]);285let results = interpret_legacy(&module, parameters).unwrap();286287assert_eq!(288results,289vec![SpecValue::V128(vec![290255, 0, 255, 255, 0, 255, 255, 255, 255, 0, 255, 255, 255, 255, 255, 255291])]292);293}294295// See issue https://github.com/bytecodealliance/wasmtime/issues/4671.296#[test]297fn order_of_params() {298let module = wat::parse_file("tests/shr_s.wat").unwrap();299let instance = instantiate(&module).unwrap();300301let parameters = Some(vec![302SpecValue::I32(1795123818),303SpecValue::I32(-2147483648),304]);305let results = interpret(&instance, "test", parameters).unwrap();306307assert_eq!(results, vec![SpecValue::I32(1795123818)]);308}309310// See issue https://github.com/bytecodealliance/wasmtime/issues/4671.311#[test]312fn order_of_params_legacy() {313let module = wat::parse_file("tests/shr_s.wat").unwrap();314315let parameters = Some(vec![316SpecValue::I32(1795123818),317SpecValue::I32(-2147483648),318]);319let results = interpret_legacy(&module, parameters).unwrap();320321assert_eq!(results, vec![SpecValue::I32(1795123818)]);322}323324#[test]325fn load_store_and_export() {326let module = wat::parse_file("tests/memory.wat").unwrap();327let instance = instantiate(&module).unwrap();328329// Store 42 at offset 4.330let _ = interpret(331&instance,332"store_i32",333Some(vec![SpecValue::I32(4), SpecValue::I32(42)]),334);335336// Load an i32 from offset 4.337let loaded = interpret(&instance, "load_i32", Some(vec![SpecValue::I32(4)]));338339// Check stored value was retrieved.340assert_eq!(loaded.unwrap(), vec![SpecValue::I32(42)]);341342// Retrieve the memory exported with name "mem" and check that the343// 32-bit value at byte offset 4 of memory is 42.344let export = export(&instance, "mem");345match export.unwrap() {346SpecExport::Global(_) => panic!("incorrect export"),347SpecExport::Memory(m) => {348assert_eq!(&m[0..10], [0, 0, 0, 0, 42, 0, 0, 0, 0, 0]);349}350}351}352}353354355