Add a crate to interface with the WebAssembly spec interpreter
The WebAssembly spec interpreter is written in OCaml and the new crate uses `ocaml-interop` along with a small OCaml wrapper to interpret Wasm modules in-process. The build process for this crate is currently Linux-specific: it requires several OCaml packages (e.g. `apt install -y ocaml-nox ocamlbuild`) as well as `make`, `cp`, and `ar`.
This commit is contained in:
33
crates/fuzzing/wasm-spec-interpreter/src/lib.rs
Normal file
33
crates/fuzzing/wasm-spec-interpreter/src/lib.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
//! This library provides a way to interpret Wasm functions in the official Wasm
|
||||
//! specification interpreter, written in OCaml, from Rust.
|
||||
//!
|
||||
//! In order to not break Wasmtime's build, this library will always compile. It
|
||||
//! does depend on certain tools (see `README.md`) that may or may not be
|
||||
//! available in the environment:
|
||||
//! - when the tools are available, we build and link to an OCaml static
|
||||
//! library (see `with_library` module)
|
||||
//! - when the tools are not available, this library will panic at runtime (see
|
||||
//! `without_library` module).
|
||||
|
||||
/// Enumerate the kinds of Wasm values.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Value {
|
||||
I32(i32),
|
||||
I64(i64),
|
||||
F32(i32),
|
||||
F64(i64),
|
||||
}
|
||||
|
||||
#[cfg(feature = "has-libinterpret")]
|
||||
mod with_library;
|
||||
#[cfg(feature = "has-libinterpret")]
|
||||
pub use with_library::*;
|
||||
|
||||
#[cfg(not(feature = "has-libinterpret"))]
|
||||
mod without_library;
|
||||
#[cfg(not(feature = "has-libinterpret"))]
|
||||
pub use without_library::*;
|
||||
|
||||
// If the user is fuzzing`, we expect the OCaml library to have been built.
|
||||
#[cfg(all(fuzzing, not(feature = "has-libinterpret")))]
|
||||
compile_error!("The OCaml library was not built.");
|
||||
98
crates/fuzzing/wasm-spec-interpreter/src/with_library.rs
Normal file
98
crates/fuzzing/wasm-spec-interpreter/src/with_library.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
//! Interpret WebAssembly modules using the OCaml spec interpreter.
|
||||
//! ```
|
||||
//! # use wasm_spec_interpreter::{Value, interpret};
|
||||
//! let module = wat::parse_file("tests/add.wat").unwrap();
|
||||
//! let parameters = vec![Value::I32(42), Value::I32(1)];
|
||||
//! let results = interpret(&module, parameters).unwrap();
|
||||
//! assert_eq!(results, &[Value::I32(43)]);
|
||||
//! ```
|
||||
use crate::Value;
|
||||
use lazy_static::lazy_static;
|
||||
use ocaml_interop::{OCamlRuntime, ToOCaml};
|
||||
use std::sync::Mutex;
|
||||
|
||||
lazy_static! {
|
||||
static ref INTERPRET: Mutex<()> = Mutex::new(());
|
||||
}
|
||||
|
||||
/// Interpret the first function in the passed WebAssembly module (in Wasm form,
|
||||
/// currently, not WAT) with the given parameters.
|
||||
pub fn interpret(module: &[u8], parameters: Vec<Value>) -> Result<Vec<Value>, String> {
|
||||
// The OCaml runtime is not re-entrant
|
||||
// (https://ocaml.org/manual/intfc.html#ss:parallel-execution-long-running-c-code).
|
||||
// We need to make sure that only one Rust thread is executing at a time
|
||||
// (using this lock) or we can observe `SIGSEGV` failures while running
|
||||
// `cargo test`.
|
||||
let _lock = INTERPRET.lock().unwrap();
|
||||
// Here we use an unsafe approach to initializing the `OCamlRuntime` based
|
||||
// on the discussion in https://github.com/tezedge/ocaml-interop/issues/35.
|
||||
// This was the recommendation to resolve seeing errors like `boxroot is not
|
||||
// setup` followed by a `SIGSEGV`; this is similar to the testing approach
|
||||
// in
|
||||
// https://github.com/tezedge/ocaml-interop/blob/master/testing/rust-caller/src/lib.rs
|
||||
// and is only as safe as the OCaml code running underneath.
|
||||
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 parameters = parameters.to_boxroot(ocaml_runtime);
|
||||
let results = ocaml_bindings::interpret(ocaml_runtime, &module, ¶meters);
|
||||
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, 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! {
|
||||
Value {
|
||||
Value::I32(i: OCamlInt32),
|
||||
Value::I64(i: OCamlInt64),
|
||||
Value::F32(i: OCamlInt32),
|
||||
Value::F64(i: OCamlInt64),
|
||||
}
|
||||
}
|
||||
|
||||
// These functions must be exposed from OCaml with:
|
||||
// `Callback.register "interpret" interpret`
|
||||
//
|
||||
// In Rust, this function becomes:
|
||||
// `pub fn interpret(_: &mut OCamlRuntime, ...: OCamlRef<...>) -> BoxRoot<...>;`
|
||||
ocaml! {
|
||||
pub fn interpret(module: OCamlBytes, params: OCamlList<Value>) -> Result<OCamlList<Value>, String>;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn multiple() {
|
||||
let module = wat::parse_file("tests/add.wat").unwrap();
|
||||
let parameters = vec![Value::I32(42), Value::I32(1)];
|
||||
let results1 = interpret(&module, parameters.clone()).unwrap();
|
||||
let results2 = interpret(&module, parameters.clone()).unwrap();
|
||||
assert_eq!(results1, results2);
|
||||
let results3 = interpret(&module, parameters).unwrap();
|
||||
assert_eq!(results2, results3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oob() {
|
||||
let module = wat::parse_file("tests/oob.wat").unwrap();
|
||||
let parameters = vec![];
|
||||
let results = interpret(&module, parameters);
|
||||
assert_eq!(
|
||||
results,
|
||||
Err("Error(_, \"out of bounds memory access\")".to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
17
crates/fuzzing/wasm-spec-interpreter/src/without_library.rs
Normal file
17
crates/fuzzing/wasm-spec-interpreter/src/without_library.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
//! Panic when interpreting WebAssembly modules; see the rationale for this in
|
||||
//! `lib.rs`.
|
||||
//!
|
||||
//! ```should_panic
|
||||
//! # use wasm_spec_interpreter::interpret;
|
||||
//! let _ = interpret(&[], vec![]);
|
||||
//! ```
|
||||
|
||||
use crate::Value;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn interpret(_module: &[u8], _parameters: Vec<Value>) -> Result<Vec<Value>, String> {
|
||||
panic!(
|
||||
"wasm-spec-interpreter was built without its Rust-to-OCaml shim \
|
||||
library; re-compile with the dependencies listed in its README.md."
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user