1 //! Evaluate an exported Wasm function using the WebAssembly specification 2 //! reference interpreter. 3 4 use crate::generators::{Config, DiffValue, DiffValueType}; 5 use crate::oracles::engine::{DiffEngine, DiffInstance}; 6 use anyhow::{anyhow, Error, Result}; 7 use wasm_spec_interpreter::SpecValue; 8 use wasmtime::Trap; 9 10 /// A wrapper for `wasm-spec-interpreter` as a [`DiffEngine`]. 11 pub struct SpecInterpreter; 12 13 impl SpecInterpreter { 14 pub(crate) fn new(config: &mut Config) -> Self { 15 let config = &mut config.module_config.config; 16 17 config.min_memories = config.min_memories.min(1); 18 config.max_memories = config.max_memories.min(1); 19 config.min_tables = config.min_tables.min(1); 20 config.max_tables = config.max_tables.min(1); 21 22 config.memory64_enabled = false; 23 config.threads_enabled = false; 24 config.bulk_memory_enabled = false; 25 config.reference_types_enabled = false; 26 config.tail_call_enabled = false; 27 config.relaxed_simd_enabled = false; 28 29 Self 30 } 31 } 32 33 impl DiffEngine for SpecInterpreter { 34 fn name(&self) -> &'static str { 35 "spec" 36 } 37 38 fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> { 39 let instance = wasm_spec_interpreter::instantiate(wasm) 40 .map_err(|e| anyhow!("failed to instantiate in spec interpreter: {}", e))?; 41 Ok(Box::new(SpecInstance { instance })) 42 } 43 44 fn assert_error_match(&self, trap: &Trap, err: &Error) { 45 // TODO: implement this for the spec interpreter 46 let _ = (trap, err); 47 } 48 49 fn is_stack_overflow(&self, err: &Error) -> bool { 50 err.to_string().contains("(Isabelle) call stack exhausted") 51 } 52 } 53 54 struct SpecInstance { 55 instance: wasm_spec_interpreter::SpecInstance, 56 } 57 58 impl DiffInstance for SpecInstance { 59 fn name(&self) -> &'static str { 60 "spec" 61 } 62 63 fn evaluate( 64 &mut self, 65 function_name: &str, 66 arguments: &[DiffValue], 67 _results: &[DiffValueType], 68 ) -> Result<Option<Vec<DiffValue>>> { 69 let arguments = arguments.iter().map(SpecValue::from).collect(); 70 match wasm_spec_interpreter::interpret(&self.instance, function_name, Some(arguments)) { 71 Ok(results) => Ok(Some(results.into_iter().map(SpecValue::into).collect())), 72 Err(err) => Err(anyhow!(err)), 73 } 74 } 75 76 fn get_global(&mut self, name: &str, _ty: DiffValueType) -> Option<DiffValue> { 77 use wasm_spec_interpreter::{export, SpecExport::Global}; 78 if let Ok(Global(g)) = export(&self.instance, name) { 79 Some(g.into()) 80 } else { 81 panic!("expected an exported global value at name `{name}`") 82 } 83 } 84 85 fn get_memory(&mut self, name: &str, _shared: bool) -> Option<Vec<u8>> { 86 use wasm_spec_interpreter::{export, SpecExport::Memory}; 87 if let Ok(Memory(m)) = export(&self.instance, name) { 88 Some(m) 89 } else { 90 panic!("expected an exported memory at name `{name}`") 91 } 92 } 93 } 94 95 impl From<&DiffValue> for SpecValue { 96 fn from(v: &DiffValue) -> Self { 97 match *v { 98 DiffValue::I32(n) => SpecValue::I32(n), 99 DiffValue::I64(n) => SpecValue::I64(n), 100 DiffValue::F32(n) => SpecValue::F32(n as i32), 101 DiffValue::F64(n) => SpecValue::F64(n as i64), 102 DiffValue::V128(n) => SpecValue::V128(n.to_le_bytes().to_vec()), 103 DiffValue::FuncRef { .. } | DiffValue::ExternRef { .. } | DiffValue::AnyRef { .. } => { 104 unimplemented!() 105 } 106 } 107 } 108 } 109 110 impl Into<DiffValue> for SpecValue { 111 fn into(self) -> DiffValue { 112 match self { 113 SpecValue::I32(n) => DiffValue::I32(n), 114 SpecValue::I64(n) => DiffValue::I64(n), 115 SpecValue::F32(n) => DiffValue::F32(n as u32), 116 SpecValue::F64(n) => DiffValue::F64(n as u64), 117 SpecValue::V128(n) => { 118 assert_eq!(n.len(), 16); 119 DiffValue::V128(u128::from_le_bytes(n.as_slice().try_into().unwrap())) 120 } 121 } 122 } 123 } 124 125 /// Set up the OCaml runtime for triggering its signal handler configuration. 126 /// 127 /// Because both the OCaml runtime and Wasmtime set up signal handlers, we must 128 /// carefully decide when to instantiate them; this function allows us to 129 /// control when. Wasmtime uses these signal handlers for catching various 130 /// WebAssembly failures. On certain OSes (e.g. Linux `x86_64`), the signal 131 /// handlers interfere, observable as an uncaught `SIGSEGV`--not even caught by 132 /// libFuzzer. 133 /// 134 /// This failure can be mitigated by always running Wasmtime second in 135 /// differential fuzzing. In some cases, however, this is not possible because 136 /// which engine will execute first is unknown. This function can be explicitly 137 /// executed first, e.g., during global initialization, to avoid this issue. 138 pub fn setup_ocaml_runtime() { 139 wasm_spec_interpreter::setup_ocaml_runtime(); 140 } 141 142 #[cfg(test)] 143 mod tests { 144 use super::*; 145 146 #[test] 147 fn smoke() { 148 if !wasm_spec_interpreter::support_compiled_in() { 149 return; 150 } 151 crate::oracles::engine::smoke_test_engine(|_, config| Ok(SpecInterpreter::new(config))) 152 } 153 } 154