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