1 //! Define the interface for differential evaluation of Wasm functions. 2 3 use crate::generators::{CompilerStrategy, Config, DiffValue, DiffValueType}; 4 use crate::oracles::{diff_wasmi::WasmiEngine, diff_wasmtime::WasmtimeEngine}; 5 use anyhow::Error; 6 use arbitrary::Unstructured; 7 use wasmtime::Trap; 8 9 /// Returns a function which can be used to build the engine name specified. 10 /// 11 /// `None` is returned if the named engine does not have support compiled into 12 /// this crate. 13 pub fn build( 14 u: &mut Unstructured<'_>, 15 name: &str, 16 config: &mut Config, 17 ) -> arbitrary::Result<Option<Box<dyn DiffEngine>>> { 18 let engine: Box<dyn DiffEngine> = match name { 19 "wasmtime" => Box::new(WasmtimeEngine::new(u, config, CompilerStrategy::Cranelift)?), 20 "wasmi" => Box::new(WasmiEngine::new(config)), 21 22 #[cfg(target_arch = "x86_64")] 23 "winch" => Box::new(WasmtimeEngine::new(u, config, CompilerStrategy::Winch)?), 24 #[cfg(not(target_arch = "x86_64"))] 25 "winch" => return Ok(None), 26 27 #[cfg(feature = "fuzz-spec-interpreter")] 28 "spec" => Box::new(crate::oracles::diff_spec::SpecInterpreter::new(config)), 29 #[cfg(not(feature = "fuzz-spec-interpreter"))] 30 "spec" => return Ok(None), 31 32 #[cfg(not(any(windows, target_arch = "s390x", target_arch = "riscv64")))] 33 "v8" => Box::new(crate::oracles::diff_v8::V8Engine::new(config)), 34 #[cfg(any(windows, target_arch = "s390x", target_arch = "riscv64"))] 35 "v8" => return Ok(None), 36 37 _ => panic!("unknown engine {name}"), 38 }; 39 40 Ok(Some(engine)) 41 } 42 43 /// Provide a way to instantiate Wasm modules. 44 pub trait DiffEngine { 45 /// Return the name of the engine. 46 fn name(&self) -> &'static str; 47 48 /// Create a new instance with the given engine. 49 fn instantiate(&mut self, wasm: &[u8]) -> anyhow::Result<Box<dyn DiffInstance>>; 50 51 /// Tests that the wasmtime-originating `trap` matches the error this engine 52 /// generated. 53 fn assert_error_match(&self, trap: &Trap, err: &Error); 54 55 /// Returns whether the error specified from this engine might be stack 56 /// overflow. 57 fn is_stack_overflow(&self, err: &Error) -> bool; 58 } 59 60 /// Provide a way to evaluate Wasm functions--a Wasm instance implemented by a 61 /// specific engine (i.e., compiler or interpreter). 62 pub trait DiffInstance { 63 /// Return the name of the engine behind this instance. 64 fn name(&self) -> &'static str; 65 66 /// Evaluate an exported function with the given values. 67 /// 68 /// Any error, such as a trap, should be returned through an `Err`. If this 69 /// engine cannot invoke the function signature then `None` should be 70 /// returned and this invocation will be skipped. 71 fn evaluate( 72 &mut self, 73 function_name: &str, 74 arguments: &[DiffValue], 75 results: &[DiffValueType], 76 ) -> anyhow::Result<Option<Vec<DiffValue>>>; 77 78 /// Attempts to return the value of the specified global, returning `None` 79 /// if this engine doesn't support retrieving globals at this time. 80 fn get_global(&mut self, name: &str, ty: DiffValueType) -> Option<DiffValue>; 81 82 /// Same as `get_global` but for memory. 83 fn get_memory(&mut self, name: &str, shared: bool) -> Option<Vec<u8>>; 84 } 85 86 /// Initialize any global state associated with runtimes that may be 87 /// differentially executed against. 88 pub fn setup_engine_runtimes() { 89 #[cfg(feature = "fuzz-spec-interpreter")] 90 crate::oracles::diff_spec::setup_ocaml_runtime(); 91 } 92 93 /// Build a list of allowed values from the given `defaults` using the 94 /// `env_list`. 95 /// 96 /// ``` 97 /// # use wasmtime_fuzzing::oracles::engine::build_allowed_env_list; 98 /// // Passing no `env_list` returns the defaults: 99 /// assert_eq!(build_allowed_env_list(None, &["a"]), vec!["a"]); 100 /// // We can build up a subset of the defaults: 101 /// assert_eq!(build_allowed_env_list(Some(vec!["b".to_string()]), &["a","b"]), vec!["b"]); 102 /// // Alternately we can subtract from the defaults: 103 /// assert_eq!(build_allowed_env_list(Some(vec!["-a".to_string()]), &["a","b"]), vec!["b"]); 104 /// ``` 105 /// ```should_panic 106 /// # use wasmtime_fuzzing::oracles::engine::build_allowed_env_list; 107 /// // We are not allowed to mix set "addition" and "subtraction"; the following 108 /// // will panic: 109 /// build_allowed_env_list(Some(vec!["-a".to_string(), "b".to_string()]), &["a", "b"]); 110 /// ``` 111 /// ```should_panic 112 /// # use wasmtime_fuzzing::oracles::engine::build_allowed_env_list; 113 /// // This will also panic if invalid values are used: 114 /// build_allowed_env_list(Some(vec!["c".to_string()]), &["a", "b"]); 115 /// ``` 116 pub fn build_allowed_env_list<'a>( 117 env_list: Option<Vec<String>>, 118 defaults: &[&'a str], 119 ) -> Vec<&'a str> { 120 if let Some(configured) = &env_list { 121 // Check that the names are either all additions or all subtractions. 122 let subtract_from_defaults = configured.iter().all(|c| c.starts_with("-")); 123 let add_from_defaults = configured.iter().all(|c| !c.starts_with("-")); 124 let start = if subtract_from_defaults { 1 } else { 0 }; 125 if !subtract_from_defaults && !add_from_defaults { 126 panic!( 127 "all configured values must either subtract or add from defaults; found mixed values: {:?}", 128 &env_list 129 ); 130 } 131 132 // Check that the configured names are valid ones. 133 for c in configured { 134 if !defaults.contains(&&c[start..]) { 135 panic!( 136 "invalid environment configuration `{}`; must be one of: {:?}", 137 c, defaults 138 ); 139 } 140 } 141 142 // Select only the allowed names. 143 let mut allowed = Vec::with_capacity(defaults.len()); 144 for &d in defaults { 145 let mentioned = configured.iter().any(|c| &c[start..] == d); 146 if (add_from_defaults && mentioned) || (subtract_from_defaults && !mentioned) { 147 allowed.push(d); 148 } 149 } 150 allowed 151 } else { 152 defaults.to_vec() 153 } 154 } 155 156 /// Retrieve a comma-delimited list of values from an environment variable. 157 pub fn parse_env_list(env_variable: &str) -> Option<Vec<String>> { 158 std::env::var(env_variable) 159 .ok() 160 .map(|l| l.split(",").map(|s| s.to_owned()).collect()) 161 } 162 163 #[cfg(test)] 164 pub fn smoke_test_engine<T>( 165 mk_engine: impl Fn(&mut arbitrary::Unstructured<'_>, &mut Config) -> arbitrary::Result<T>, 166 ) where 167 T: DiffEngine, 168 { 169 use rand::prelude::*; 170 171 let mut rng = SmallRng::seed_from_u64(0); 172 let mut buf = vec![0; 2048]; 173 let n = 100; 174 for _ in 0..n { 175 rng.fill_bytes(&mut buf); 176 let mut u = Unstructured::new(&buf); 177 let mut config = match u.arbitrary::<Config>() { 178 Ok(config) => config, 179 Err(_) => continue, 180 }; 181 // This will ensure that wasmtime, which uses this configuration 182 // settings, can guaranteed instantiate a module. 183 config.set_differential_config(); 184 185 let mut engine = match mk_engine(&mut u, &mut config) { 186 Ok(engine) => engine, 187 Err(e) => { 188 println!("skip {:?}", e); 189 continue; 190 } 191 }; 192 193 let wasm = wat::parse_str( 194 r#" 195 (module 196 (func (export "add") (param i32 i32) (result i32) 197 local.get 0 198 local.get 1 199 i32.add) 200 201 (global (export "global") i32 i32.const 1) 202 (memory (export "memory") 1) 203 ) 204 "#, 205 ) 206 .unwrap(); 207 let mut instance = engine.instantiate(&wasm).unwrap(); 208 let results = instance 209 .evaluate( 210 "add", 211 &[DiffValue::I32(1), DiffValue::I32(2)], 212 &[DiffValueType::I32], 213 ) 214 .unwrap(); 215 assert_eq!(results, Some(vec![DiffValue::I32(3)])); 216 217 if let Some(val) = instance.get_global("global", DiffValueType::I32) { 218 assert_eq!(val, DiffValue::I32(1)); 219 } 220 221 if let Some(val) = instance.get_memory("memory", false) { 222 assert_eq!(val.len(), 65536); 223 for i in val.iter() { 224 assert_eq!(*i, 0); 225 } 226 } 227 228 return; 229 } 230 231 panic!("after {n} runs nothing ever ran, something is probably wrong"); 232 } 233