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