Path: blob/main/crates/fuzzing/src/oracles/engine.rs
3052 views
//! Define the interface for differential evaluation of Wasm functions.12use crate::generators::{CompilerStrategy, Config, DiffValue, DiffValueType};3use crate::oracles::{diff_wasmi::WasmiEngine, diff_wasmtime::WasmtimeEngine};4use arbitrary::Unstructured;5use wasmtime::Error;6use wasmtime::Trap;78/// Returns a function which can be used to build the engine name specified.9///10/// `None` is returned if the named engine does not have support compiled into11/// this crate.12pub fn build(13u: &mut Unstructured<'_>,14name: &str,15config: &mut Config,16) -> arbitrary::Result<Option<Box<dyn DiffEngine>>> {17let engine: Box<dyn DiffEngine> = match name {18"wasmtime" => Box::new(WasmtimeEngine::new(19u,20config,21CompilerStrategy::CraneliftNative,22)?),23"pulley" => Box::new(WasmtimeEngine::new(24u,25config,26CompilerStrategy::CraneliftPulley,27)?),28"wasmi" => Box::new(WasmiEngine::new(config)),2930#[cfg(target_arch = "x86_64")]31"winch" => Box::new(WasmtimeEngine::new(u, config, CompilerStrategy::Winch)?),32#[cfg(not(target_arch = "x86_64"))]33"winch" => return Ok(None),3435#[cfg(feature = "fuzz-spec-interpreter")]36"spec" => Box::new(crate::oracles::diff_spec::SpecInterpreter::new(config)),37#[cfg(not(feature = "fuzz-spec-interpreter"))]38"spec" => return Ok(None),3940#[cfg(not(any(windows, target_arch = "s390x", target_arch = "riscv64")))]41"v8" => Box::new(crate::oracles::diff_v8::V8Engine::new(config)),42#[cfg(any(windows, target_arch = "s390x", target_arch = "riscv64"))]43"v8" => return Ok(None),4445_ => panic!("unknown engine {name}"),46};4748Ok(Some(engine))49}5051/// Provide a way to instantiate Wasm modules.52pub trait DiffEngine {53/// Return the name of the engine.54fn name(&self) -> &'static str;5556/// Create a new instance with the given engine.57fn instantiate(&mut self, wasm: &[u8]) -> wasmtime::Result<Box<dyn DiffInstance>>;5859/// Tests that the wasmtime-originating `trap` matches the error this engine60/// generated.61fn assert_error_match(&self, err: &Error, trap: &Trap);6263/// Returns whether the error specified from this engine is64/// non-deterministic, like a stack overflow or an attempt to allocate an65/// object that is too large (which is non-deterministic because it may66/// depend on which collector it was configured with or memory available on67/// the system).68fn is_non_deterministic_error(&self, err: &Error) -> bool;69}7071/// Provide a way to evaluate Wasm functions--a Wasm instance implemented by a72/// specific engine (i.e., compiler or interpreter).73pub trait DiffInstance {74/// Return the name of the engine behind this instance.75fn name(&self) -> &'static str;7677/// Evaluate an exported function with the given values.78///79/// Any error, such as a trap, should be returned through an `Err`. If this80/// engine cannot invoke the function signature then `None` should be81/// returned and this invocation will be skipped.82fn evaluate(83&mut self,84function_name: &str,85arguments: &[DiffValue],86results: &[DiffValueType],87) -> wasmtime::Result<Option<Vec<DiffValue>>>;8889/// Attempts to return the value of the specified global, returning `None`90/// if this engine doesn't support retrieving globals at this time.91fn get_global(&mut self, name: &str, ty: DiffValueType) -> Option<DiffValue>;9293/// Same as `get_global` but for memory.94fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>>;95}9697/// Initialize any global state associated with runtimes that may be98/// differentially executed against.99pub fn setup_engine_runtimes() {100#[cfg(feature = "fuzz-spec-interpreter")]101crate::oracles::diff_spec::setup_ocaml_runtime();102}103104/// Build a list of allowed values from the given `defaults` using the105/// `env_list`.106///107/// The entries in `defaults` are preserved, in order, and are replaced with108/// `None` in the returned list if they are disabled.109///110/// ```111/// # use wasmtime_fuzzing::oracles::engine::build_allowed_env_list;112/// // Passing no `env_list` returns the defaults:113/// assert_eq!(build_allowed_env_list(None, &["a"]), vec![Some("a")]);114/// // We can build up a subset of the defaults:115/// assert_eq!(build_allowed_env_list(Some(vec!["b".to_string()]), &["a","b"]), vec![None, Some("b")]);116/// // Alternately we can subtract from the defaults:117/// assert_eq!(build_allowed_env_list(Some(vec!["-a".to_string()]), &["a","b"]), vec![None, Some("b")]);118/// ```119/// ```should_panic120/// # use wasmtime_fuzzing::oracles::engine::build_allowed_env_list;121/// // We are not allowed to mix set "addition" and "subtraction"; the following122/// // will panic:123/// build_allowed_env_list(Some(vec!["-a".to_string(), "b".to_string()]), &["a", "b"]);124/// ```125/// ```should_panic126/// # use wasmtime_fuzzing::oracles::engine::build_allowed_env_list;127/// // This will also panic if invalid values are used:128/// build_allowed_env_list(Some(vec!["c".to_string()]), &["a", "b"]);129/// ```130pub fn build_allowed_env_list<'a>(131env_list: Option<Vec<String>>,132defaults: &[&'a str],133) -> Vec<Option<&'a str>> {134if let Some(configured) = &env_list {135// Check that the names are either all additions or all subtractions.136let subtract_from_defaults = configured.iter().all(|c| c.starts_with("-"));137let add_from_defaults = configured.iter().all(|c| !c.starts_with("-"));138let start = if subtract_from_defaults { 1 } else { 0 };139if !subtract_from_defaults && !add_from_defaults {140panic!(141"all configured values must either subtract or add from defaults; found mixed values: {:?}",142&env_list143);144}145146// Check that the configured names are valid ones.147for c in configured {148if !defaults.contains(&&c[start..]) {149panic!("invalid environment configuration `{c}`; must be one of: {defaults:?}");150}151}152153// Select only the allowed names.154let mut allowed = Vec::with_capacity(defaults.len());155for &d in defaults {156let mentioned = configured.iter().any(|c| &c[start..] == d);157if (add_from_defaults && mentioned) || (subtract_from_defaults && !mentioned) {158allowed.push(Some(d));159} else {160allowed.push(None);161}162}163allowed164} else {165defaults.iter().copied().map(Some).collect()166}167}168169/// Retrieve a comma-delimited list of values from an environment variable.170pub fn parse_env_list(env_variable: &str) -> Option<Vec<String>> {171std::env::var(env_variable)172.ok()173.map(|l| l.split(",").map(|s| s.to_owned()).collect())174}175176/// Smoke test an engine with a given config.177#[cfg(test)]178pub fn smoke_test_engine<T>(179mk_engine: impl Fn(&mut arbitrary::Unstructured<'_>, &mut Config) -> arbitrary::Result<T>,180) where181T: DiffEngine,182{183crate::test::test_n_times(5, |mut config: Config, u| {184// This will ensure that wasmtime, which uses this configuration185// settings, can guaranteed instantiate a module.186config.set_differential_config();187188let mut engine = match mk_engine(u, &mut config) {189Ok(engine) => engine,190Err(e) => {191println!("skip {e:?}");192return Ok(());193}194};195196let wasm = wat::parse_str(197r#"198(module199(func (export "add") (param i32 i32) (result i32)200local.get 0201local.get 1202i32.add)203204(global (export "global") i32 i32.const 1)205(memory (export "memory") 1)206)207"#,208)209.unwrap();210let mut instance = engine.instantiate(&wasm).unwrap();211let results = instance212.evaluate(213"add",214&[DiffValue::I32(1), DiffValue::I32(2)],215&[DiffValueType::I32],216)217.unwrap();218assert_eq!(results, Some(vec![DiffValue::I32(3)]));219220if let Some(val) = instance.get_global("global", DiffValueType::I32) {221assert_eq!(val, DiffValue::I32(1));222}223224if let Some(val) = instance.get_memory("memory", false) {225assert_eq!(val.len(), 65536);226for i in val.iter() {227assert_eq!(*i, 0);228}229}230231Ok(())232})233}234235236