Path: blob/main/crates/fuzzing/src/oracles/engine.rs
1693 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 anyhow::Error;5use arbitrary::Unstructured;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]) -> anyhow::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) -> anyhow::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{183use rand::prelude::*;184185let mut rng = SmallRng::seed_from_u64(0);186let mut buf = vec![0; 2048];187let n = 100;188for _ in 0..n {189rng.fill_bytes(&mut buf);190let mut u = Unstructured::new(&buf);191let mut config = match u.arbitrary::<Config>() {192Ok(config) => config,193Err(_) => continue,194};195// This will ensure that wasmtime, which uses this configuration196// settings, can guaranteed instantiate a module.197config.set_differential_config();198199let mut engine = match mk_engine(&mut u, &mut config) {200Ok(engine) => engine,201Err(e) => {202println!("skip {e:?}");203continue;204}205};206207let wasm = wat::parse_str(208r#"209(module210(func (export "add") (param i32 i32) (result i32)211local.get 0212local.get 1213i32.add)214215(global (export "global") i32 i32.const 1)216(memory (export "memory") 1)217)218"#,219)220.unwrap();221let mut instance = engine.instantiate(&wasm).unwrap();222let results = instance223.evaluate(224"add",225&[DiffValue::I32(1), DiffValue::I32(2)],226&[DiffValueType::I32],227)228.unwrap();229assert_eq!(results, Some(vec![DiffValue::I32(3)]));230231if let Some(val) = instance.get_global("global", DiffValueType::I32) {232assert_eq!(val, DiffValue::I32(1));233}234235if let Some(val) = instance.get_memory("memory", false) {236assert_eq!(val.len(), 65536);237for i in val.iter() {238assert_eq!(*i, 0);239}240}241242return;243}244245panic!("after {n} runs nothing ever ran, something is probably wrong");246}247248249