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