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