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