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