1 //! Evaluate an exported Wasm function using the wasmi interpreter. 2 3 use crate::generators::{Config, DiffValue, DiffValueType}; 4 use crate::oracles::engine::{DiffEngine, DiffInstance}; 5 use anyhow::{Context, Error, Result}; 6 use wasmtime::{Trap, TrapCode}; 7 8 /// A wrapper for `wasmi` as a [`DiffEngine`]. 9 pub struct WasmiEngine; 10 11 impl WasmiEngine { 12 pub(crate) fn new(config: &mut Config) -> Self { 13 let config = &mut config.module_config.config; 14 config.reference_types_enabled = false; 15 config.simd_enabled = false; 16 config.multi_value_enabled = false; 17 config.saturating_float_to_int_enabled = false; 18 config.sign_extension_enabled = false; 19 config.memory64_enabled = false; 20 config.bulk_memory_enabled = false; 21 config.threads_enabled = false; 22 config.max_memories = config.max_memories.min(1); 23 config.min_memories = config.min_memories.min(1); 24 config.max_tables = config.max_tables.min(1); 25 config.min_tables = config.min_tables.min(1); 26 27 Self 28 } 29 } 30 31 impl DiffEngine for WasmiEngine { 32 fn name(&self) -> &'static str { 33 "wasmi" 34 } 35 36 fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> { 37 let module = wasmi::Module::from_buffer(wasm).context("unable to validate Wasm module")?; 38 let instance = wasmi::ModuleInstance::new(&module, &wasmi::ImportsBuilder::default()) 39 .context("unable to instantiate module in wasmi")?; 40 let instance = instance.run_start(&mut wasmi::NopExternals)?; 41 Ok(Box::new(WasmiInstance { module, instance })) 42 } 43 44 fn assert_error_match(&self, trap: &Trap, err: &Error) { 45 // Acquire a `wasmi::Trap` from the wasmi error which we'll use to 46 // assert that it has the same kind of trap as the wasmtime-based trap. 47 let wasmi = match err.downcast_ref::<wasmi::Error>() { 48 Some(wasmi::Error::Trap(trap)) => trap, 49 50 // Out-of-bounds data segments turn into this category which 51 // Wasmtime reports as a `MemoryOutOfBounds`. 52 Some(wasmi::Error::Memory(msg)) => { 53 assert_eq!( 54 trap.trap_code(), 55 Some(TrapCode::MemoryOutOfBounds), 56 "wasmtime error did not match wasmi: {msg}" 57 ); 58 return; 59 } 60 61 // Ignore this for now, looks like "elements segment does not fit" 62 // falls into this category and to avoid doing string matching this 63 // is just ignored. 64 Some(wasmi::Error::Instantiation(msg)) => { 65 log::debug!("ignoring wasmi instantiation error: {msg}"); 66 return; 67 } 68 69 Some(other) => panic!("unexpected wasmi error: {}", other), 70 71 None => err 72 .downcast_ref::<wasmi::Trap>() 73 .expect(&format!("not a trap: {:?}", err)), 74 }; 75 match wasmi.kind() { 76 wasmi::TrapKind::StackOverflow => { 77 assert_eq!(trap.trap_code(), Some(TrapCode::StackOverflow)) 78 } 79 wasmi::TrapKind::MemoryAccessOutOfBounds => { 80 assert_eq!(trap.trap_code(), Some(TrapCode::MemoryOutOfBounds)) 81 } 82 wasmi::TrapKind::Unreachable => { 83 assert_eq!(trap.trap_code(), Some(TrapCode::UnreachableCodeReached)) 84 } 85 wasmi::TrapKind::TableAccessOutOfBounds => { 86 assert_eq!(trap.trap_code(), Some(TrapCode::TableOutOfBounds)) 87 } 88 wasmi::TrapKind::ElemUninitialized => { 89 assert_eq!(trap.trap_code(), Some(TrapCode::IndirectCallToNull)) 90 } 91 wasmi::TrapKind::DivisionByZero => { 92 assert_eq!(trap.trap_code(), Some(TrapCode::IntegerDivisionByZero)) 93 } 94 wasmi::TrapKind::IntegerOverflow => { 95 assert_eq!(trap.trap_code(), Some(TrapCode::IntegerOverflow)) 96 } 97 wasmi::TrapKind::InvalidConversionToInt => { 98 assert_eq!(trap.trap_code(), Some(TrapCode::BadConversionToInteger)) 99 } 100 wasmi::TrapKind::UnexpectedSignature => { 101 assert_eq!(trap.trap_code(), Some(TrapCode::BadSignature)) 102 } 103 wasmi::TrapKind::Host(_) => unreachable!(), 104 } 105 } 106 107 fn is_stack_overflow(&self, err: &Error) -> bool { 108 let trap = match err.downcast_ref::<wasmi::Error>() { 109 Some(wasmi::Error::Trap(trap)) => trap, 110 Some(_) => return false, 111 None => match err.downcast_ref::<wasmi::Trap>() { 112 Some(trap) => trap, 113 None => return false, 114 }, 115 }; 116 match trap.kind() { 117 wasmi::TrapKind::StackOverflow => true, 118 _ => false, 119 } 120 } 121 } 122 123 /// A wrapper for `wasmi` Wasm instances. 124 struct WasmiInstance { 125 #[allow(dead_code)] // reason = "the module must live as long as its reference" 126 module: wasmi::Module, 127 instance: wasmi::ModuleRef, 128 } 129 130 impl DiffInstance for WasmiInstance { 131 fn name(&self) -> &'static str { 132 "wasmi" 133 } 134 135 fn evaluate( 136 &mut self, 137 function_name: &str, 138 arguments: &[DiffValue], 139 _results: &[DiffValueType], 140 ) -> Result<Option<Vec<DiffValue>>> { 141 let arguments: Vec<_> = arguments.iter().map(wasmi::RuntimeValue::from).collect(); 142 let export = self 143 .instance 144 .export_by_name(function_name) 145 .context(format!( 146 "unable to find function '{}' in wasmi instance", 147 function_name 148 ))?; 149 let function = export.as_func().context("wasmi export is not a function")?; 150 let result = wasmi::FuncInstance::invoke(&function, &arguments, &mut wasmi::NopExternals) 151 .context("failed while invoking function in wasmi")?; 152 Ok(Some(if let Some(result) = result { 153 vec![result.into()] 154 } else { 155 vec![] 156 })) 157 } 158 159 fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> { 160 match self.instance.export_by_name(name) { 161 Some(wasmi::ExternVal::Global(g)) => Some(g.get().into()), 162 _ => unreachable!(), 163 } 164 } 165 166 fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>> { 167 assert!(!shared); 168 match self.instance.export_by_name(name) { 169 Some(wasmi::ExternVal::Memory(m)) => { 170 // `wasmi` memory may be stored non-contiguously; copy 171 // it out to a contiguous chunk. 172 let mut buffer: Vec<u8> = vec![0; m.current_size().0 * 65536]; 173 m.get_into(0, &mut buffer[..]) 174 .expect("can access wasmi memory"); 175 Some(buffer) 176 } 177 _ => unreachable!(), 178 } 179 } 180 } 181 182 impl From<&DiffValue> for wasmi::RuntimeValue { 183 fn from(v: &DiffValue) -> Self { 184 use wasmi::RuntimeValue::*; 185 match *v { 186 DiffValue::I32(n) => I32(n), 187 DiffValue::I64(n) => I64(n), 188 DiffValue::F32(n) => F32(wasmi::nan_preserving_float::F32::from_bits(n)), 189 DiffValue::F64(n) => F64(wasmi::nan_preserving_float::F64::from_bits(n)), 190 DiffValue::V128(_) | DiffValue::FuncRef { .. } | DiffValue::ExternRef { .. } => { 191 unimplemented!() 192 } 193 } 194 } 195 } 196 197 impl Into<DiffValue> for wasmi::RuntimeValue { 198 fn into(self) -> DiffValue { 199 use wasmi::RuntimeValue::*; 200 match self { 201 I32(n) => DiffValue::I32(n), 202 I64(n) => DiffValue::I64(n), 203 F32(n) => DiffValue::F32(n.to_bits()), 204 F64(n) => DiffValue::F64(n.to_bits()), 205 } 206 } 207 } 208 209 #[test] 210 fn smoke() { 211 crate::oracles::engine::smoke_test_engine(|_, config| Ok(WasmiEngine::new(config))) 212 } 213