1 //! Evaluate an exported Wasm function using the WebAssembly specification 2 //! reference interpreter. 3 4 use crate::generators::{DiffValue, DiffValueType, ModuleConfig}; 5 use crate::oracles::engine::{DiffEngine, DiffInstance}; 6 use anyhow::{anyhow, bail, Error, Result}; 7 use wasm_spec_interpreter::Value; 8 use wasmtime::Trap; 9 10 /// A wrapper for `wasm-spec-interpreter` as a [`DiffEngine`]. 11 pub struct SpecInterpreter; 12 13 impl SpecInterpreter { 14 /// Build a new [`SpecInterpreter`] but only if the configuration does not 15 /// rely on features that the current bindings (i.e., 16 /// `wasm-spec-interpreter`) do not support. 17 pub fn new(config: &ModuleConfig) -> Result<Self> { 18 if config.config.reference_types_enabled { 19 bail!("the spec interpreter bindings do not support reference types") 20 } 21 // TODO: right now the interpreter bindings only execute the first 22 // function in the module so if there's possibly more than one function 23 // it's not possible to run the other function. This should be fixed 24 // with improvements to the ocaml bindings to the interpreter. 25 if config.config.max_funcs > 1 { 26 bail!("the spec interpreter bindings can only support one function for now") 27 } 28 29 // TODO: right now the instantiation step for the interpreter does 30 // nothing and the evaluation step performs an instantiation followed by 31 // an execution. This means that instantiations which fail in other 32 // engines will "succeed" in the interpreter because the error is 33 // delayed to the execution. This should be fixed by making 34 // instantiation a first-class primitive in our interpreter bindings. 35 if config.config.max_tables > 0 { 36 bail!("the spec interpreter bindings do not fail as they should with out-of-bounds table accesses") 37 } 38 39 if config.config.memory64_enabled { 40 bail!("memory64 not implemented in spec interpreter"); 41 } 42 43 if config.config.threads_enabled { 44 bail!("spec interpreter does not support the threading proposal"); 45 } 46 Ok(Self) 47 } 48 } 49 50 impl DiffEngine for SpecInterpreter { 51 fn name(&self) -> &'static str { 52 "spec" 53 } 54 55 fn instantiate(&mut self, wasm: &[u8]) -> Result<Box<dyn DiffInstance>> { 56 // TODO: ideally we would avoid copying the module bytes here. 57 Ok(Box::new(SpecInstance { 58 wasm: wasm.to_vec(), 59 })) 60 } 61 62 fn assert_error_match(&self, trap: &Trap, err: Error) { 63 // TODO: implement this for the spec interpreter 64 drop((trap, err)); 65 } 66 } 67 68 struct SpecInstance { 69 wasm: Vec<u8>, 70 } 71 72 impl DiffInstance for SpecInstance { 73 fn name(&self) -> &'static str { 74 "spec" 75 } 76 77 fn evaluate( 78 &mut self, 79 _function_name: &str, 80 arguments: &[DiffValue], 81 _results: &[DiffValueType], 82 ) -> Result<Option<Vec<DiffValue>>> { 83 // The spec interpreter needs some work before it can fully support this 84 // interface: 85 // - TODO adapt `wasm-spec-interpreter` to use function name to select 86 // function to run 87 // - TODO adapt `wasm-spec-interpreter` to expose an "instance" with 88 // so we can hash memory, globals, etc. 89 let arguments = arguments.iter().map(Value::from).collect(); 90 match wasm_spec_interpreter::interpret(&self.wasm, Some(arguments)) { 91 Ok(results) => Ok(Some(results.into_iter().map(Value::into).collect())), 92 Err(err) => Err(anyhow!(err)), 93 } 94 } 95 96 fn get_global(&mut self, _name: &str, _ty: DiffValueType) -> Option<DiffValue> { 97 // TODO: should implement this 98 None 99 } 100 101 fn get_memory(&mut self, _name: &str, _shared: bool) -> Option<Vec<u8>> { 102 // TODO: should implement this 103 None 104 } 105 } 106 107 impl From<&DiffValue> for Value { 108 fn from(v: &DiffValue) -> Self { 109 match *v { 110 DiffValue::I32(n) => Value::I32(n), 111 DiffValue::I64(n) => Value::I64(n), 112 DiffValue::F32(n) => Value::F32(n as i32), 113 DiffValue::F64(n) => Value::F64(n as i64), 114 DiffValue::V128(n) => Value::V128(n.to_le_bytes().to_vec()), 115 DiffValue::FuncRef { .. } | DiffValue::ExternRef { .. } => unimplemented!(), 116 } 117 } 118 } 119 120 impl Into<DiffValue> for Value { 121 fn into(self) -> DiffValue { 122 match self { 123 Value::I32(n) => DiffValue::I32(n), 124 Value::I64(n) => DiffValue::I64(n), 125 Value::F32(n) => DiffValue::F32(n as u32), 126 Value::F64(n) => DiffValue::F64(n as u64), 127 Value::V128(n) => { 128 assert_eq!(n.len(), 16); 129 DiffValue::V128(u128::from_le_bytes(n.as_slice().try_into().unwrap())) 130 } 131 } 132 } 133 } 134 135 /// Set up the OCaml runtime for triggering its signal handler configuration. 136 /// 137 /// Because both the OCaml runtime and Wasmtime set up signal handlers, we must 138 /// carefully decide when to instantiate them; this function allows us to 139 /// control when. Wasmtime uses these signal handlers for catching various 140 /// WebAssembly failures. On certain OSes (e.g. Linux `x86_64`), the signal 141 /// handlers interfere, observable as an uncaught `SIGSEGV`--not even caught by 142 /// libFuzzer. 143 /// 144 /// This failure can be mitigated by always running Wasmtime second in 145 /// differential fuzzing. In some cases, however, this is not possible because 146 /// which engine will execute first is unknown. This function can be explicitly 147 /// executed first, e.g., during global initialization, to avoid this issue. 148 pub fn setup_ocaml_runtime() { 149 wasm_spec_interpreter::setup_ocaml_runtime(); 150 } 151 152 #[test] 153 fn smoke() { 154 if !wasm_spec_interpreter::support_compiled_in() { 155 return; 156 } 157 crate::oracles::engine::smoke_test_engine(|config| SpecInterpreter::new(&config.module_config)) 158 } 159