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