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