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