Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/fuzzing/src/oracles/engine.rs
1693 views
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 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).
69
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.
76
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.
83
fn evaluate(
84
&mut self,
85
function_name: &str,
86
arguments: &[DiffValue],
87
results: &[DiffValueType],
88
) -> anyhow::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.
92
fn get_global(&mut self, name: &str, ty: DiffValueType) -> Option<DiffValue>;
93
94
/// Same as `get_global` but for memory.
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.
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
/// ```
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.
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)]
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
use rand::prelude::*;
185
186
let mut rng = SmallRng::seed_from_u64(0);
187
let mut buf = vec![0; 2048];
188
let n = 100;
189
for _ in 0..n {
190
rng.fill_bytes(&mut buf);
191
let mut u = Unstructured::new(&buf);
192
let mut config = match u.arbitrary::<Config>() {
193
Ok(config) => config,
194
Err(_) => continue,
195
};
196
// This will ensure that wasmtime, which uses this configuration
197
// settings, can guaranteed instantiate a module.
198
config.set_differential_config();
199
200
let mut engine = match mk_engine(&mut u, &mut config) {
201
Ok(engine) => engine,
202
Err(e) => {
203
println!("skip {e:?}");
204
continue;
205
}
206
};
207
208
let wasm = wat::parse_str(
209
r#"
210
(module
211
(func (export "add") (param i32 i32) (result i32)
212
local.get 0
213
local.get 1
214
i32.add)
215
216
(global (export "global") i32 i32.const 1)
217
(memory (export "memory") 1)
218
)
219
"#,
220
)
221
.unwrap();
222
let mut instance = engine.instantiate(&wasm).unwrap();
223
let results = instance
224
.evaluate(
225
"add",
226
&[DiffValue::I32(1), DiffValue::I32(2)],
227
&[DiffValueType::I32],
228
)
229
.unwrap();
230
assert_eq!(results, Some(vec![DiffValue::I32(3)]));
231
232
if let Some(val) = instance.get_global("global", DiffValueType::I32) {
233
assert_eq!(val, DiffValue::I32(1));
234
}
235
236
if let Some(val) = instance.get_memory("memory", false) {
237
assert_eq!(val.len(), 65536);
238
for i in val.iter() {
239
assert_eq!(*i, 0);
240
}
241
}
242
243
return;
244
}
245
246
panic!("after {n} runs nothing ever ran, something is probably wrong");
247
}
248
249