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