Path: blob/main/crates/fuzzing/src/oracles/diff_wasmi.rs
1693 views
//! Evaluate an exported Wasm function using the wasmi interpreter.12use crate::generators::{Config, DiffValue, DiffValueType};3use crate::oracles::engine::{DiffEngine, DiffInstance};4use anyhow::{Context, Error, Result};5use wasmtime::Trap;67/// A wrapper for `wasmi` as a [`DiffEngine`].8pub struct WasmiEngine {9engine: wasmi::Engine,10}1112impl WasmiEngine {13pub(crate) fn new(config: &mut Config) -> Self {14let config = &mut config.module_config.config;15// Force generated Wasm modules to never have features that Wasmi doesn't support.16config.relaxed_simd_enabled = false;17config.threads_enabled = false;18config.exceptions_enabled = false;19config.gc_enabled = false;2021// FIXME: once the active fuzz bug for wasmi's simd differential fuzzing22// has been fixed and we've updated then this should be re-enabled.23config.simd_enabled = false;24// FIXME: requires updating to a wasmi that contains25// wasmi-labs/wasmi#1531.26config.memory64_enabled = false;27// FIXME: until https://github.com/wasmi-labs/wasmi/issues/1544 is fixed.28config.wide_arithmetic_enabled = false;2930let mut wasmi_config = wasmi::Config::default();31wasmi_config32.consume_fuel(false)33.floats(true)34.wasm_mutable_global(true)35.wasm_sign_extension(config.sign_extension_ops_enabled)36.wasm_saturating_float_to_int(config.saturating_float_to_int_enabled)37.wasm_multi_value(config.multi_value_enabled)38.wasm_bulk_memory(config.bulk_memory_enabled)39.wasm_reference_types(config.reference_types_enabled)40.wasm_tail_call(config.tail_call_enabled)41.wasm_multi_memory(config.max_memories > 1)42.wasm_extended_const(config.extended_const_enabled)43.wasm_custom_page_sizes(config.custom_page_sizes_enabled)44.wasm_memory64(config.memory64_enabled)45.wasm_simd(config.simd_enabled)46.wasm_wide_arithmetic(config.wide_arithmetic_enabled);47Self {48engine: wasmi::Engine::new(&wasmi_config),49}50}5152fn trap_code(&self, err: &Error) -> Option<wasmi::core::TrapCode> {53let err = err.downcast_ref::<wasmi::Error>()?;54if let Some(code) = err.as_trap_code() {55return Some(code);56}5758match err.kind() {59wasmi::errors::ErrorKind::Instantiation(60wasmi::errors::InstantiationError::ElementSegmentDoesNotFit { .. },61) => Some(wasmi::core::TrapCode::TableOutOfBounds),62wasmi::errors::ErrorKind::Memory(wasmi::errors::MemoryError::OutOfBoundsAccess) => {63Some(wasmi::core::TrapCode::MemoryOutOfBounds)64}65_ => {66log::trace!("unknown wasmi error: {:?}", err.kind());67None68}69}70}71}7273impl DiffEngine for WasmiEngine {74fn name(&self) -> &'static str {75"wasmi"76}7778fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> {79let module =80wasmi::Module::new(&self.engine, wasm).context("unable to validate Wasm module")?;81let mut store = wasmi::Store::new(&self.engine, ());82let instance = wasmi::Linker::<()>::new(&self.engine)83.instantiate(&mut store, &module)84.and_then(|i| i.start(&mut store))85.context("unable to instantiate module in wasmi")?;86Ok(Box::new(WasmiInstance { store, instance }))87}8889fn assert_error_match(&self, lhs: &Error, rhs: &Trap) {90match self.trap_code(lhs) {91Some(code) => assert_eq!(wasmi_to_wasmtime_trap_code(code), *rhs),92None => panic!("unexpected wasmi error {lhs:?}"),93}94}9596fn is_non_deterministic_error(&self, err: &Error) -> bool {97matches!(98self.trap_code(err),99Some(wasmi::core::TrapCode::StackOverflow)100)101}102}103104/// Converts `wasmi` trap code to `wasmtime` trap code.105fn wasmi_to_wasmtime_trap_code(trap: wasmi::core::TrapCode) -> Trap {106use wasmi::core::TrapCode;107match trap {108TrapCode::UnreachableCodeReached => Trap::UnreachableCodeReached,109TrapCode::MemoryOutOfBounds => Trap::MemoryOutOfBounds,110TrapCode::TableOutOfBounds => Trap::TableOutOfBounds,111TrapCode::IndirectCallToNull => Trap::IndirectCallToNull,112TrapCode::IntegerDivisionByZero => Trap::IntegerDivisionByZero,113TrapCode::IntegerOverflow => Trap::IntegerOverflow,114TrapCode::BadConversionToInteger => Trap::BadConversionToInteger,115TrapCode::StackOverflow => Trap::StackOverflow,116TrapCode::BadSignature => Trap::BadSignature,117TrapCode::OutOfFuel => unimplemented!("built-in fuel metering is unused"),118TrapCode::GrowthOperationLimited => unimplemented!("resource limiter is unused"),119}120}121122/// A wrapper for `wasmi` Wasm instances.123struct WasmiInstance {124store: wasmi::Store<()>,125instance: wasmi::Instance,126}127128impl DiffInstance for WasmiInstance {129fn name(&self) -> &'static str {130"wasmi"131}132133fn evaluate(134&mut self,135function_name: &str,136arguments: &[DiffValue],137result_tys: &[DiffValueType],138) -> Result<Option<Vec<DiffValue>>> {139let function = self140.instance141.get_export(&self.store, function_name)142.and_then(wasmi::Extern::into_func)143.unwrap();144let arguments: Vec<_> = arguments.iter().map(|x| x.into()).collect();145let mut results = vec![wasmi::Val::I32(0); result_tys.len()];146function147.call(&mut self.store, &arguments, &mut results)148.context("wasmi function trap")?;149Ok(Some(results.into_iter().map(Into::into).collect()))150}151152fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> {153Some(154self.instance155.get_export(&self.store, name)156.unwrap()157.into_global()158.unwrap()159.get(&self.store)160.into(),161)162}163164fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>> {165assert!(!shared);166Some(167self.instance168.get_export(&self.store, name)169.unwrap()170.into_memory()171.unwrap()172.data(&self.store)173.to_vec(),174)175}176}177178impl From<&DiffValue> for wasmi::Val {179fn from(v: &DiffValue) -> Self {180use wasmi::Val as WasmiValue;181match *v {182DiffValue::I32(n) => WasmiValue::I32(n),183DiffValue::I64(n) => WasmiValue::I64(n),184DiffValue::F32(n) => WasmiValue::F32(wasmi::core::F32::from_bits(n)),185DiffValue::F64(n) => WasmiValue::F64(wasmi::core::F64::from_bits(n)),186DiffValue::V128(n) => WasmiValue::V128(wasmi::core::V128::from(n)),187DiffValue::FuncRef { null } => {188assert!(null);189WasmiValue::FuncRef(wasmi::FuncRef::null())190}191DiffValue::ExternRef { null } => {192assert!(null);193WasmiValue::ExternRef(wasmi::ExternRef::null())194}195DiffValue::AnyRef { .. } => unimplemented!(),196DiffValue::ExnRef { .. } => unimplemented!(),197DiffValue::ContRef { .. } => unimplemented!(),198}199}200}201202impl From<wasmi::Val> for DiffValue {203fn from(value: wasmi::Val) -> Self {204use wasmi::Val as WasmiValue;205match value {206WasmiValue::I32(n) => DiffValue::I32(n),207WasmiValue::I64(n) => DiffValue::I64(n),208WasmiValue::F32(n) => DiffValue::F32(n.to_bits()),209WasmiValue::F64(n) => DiffValue::F64(n.to_bits()),210WasmiValue::V128(n) => DiffValue::V128(n.as_u128()),211WasmiValue::FuncRef(f) => DiffValue::FuncRef { null: f.is_null() },212WasmiValue::ExternRef(e) => DiffValue::ExternRef { null: e.is_null() },213}214}215}216217#[cfg(test)]218mod tests {219use super::*;220221#[test]222fn smoke() {223crate::oracles::engine::smoke_test_engine(|_, config| Ok(WasmiEngine::new(config)))224}225}226227228