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