Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/wizer/fuzz/fuzz_targets/same_result.rs
2459 views
1
//! Check that we get the same result whether we
2
//!
3
//! 1. Call the initialization function
4
//! 2. Call the main function
5
//!
6
//! or
7
//!
8
//! 1. Call the initialization function
9
//! 2. Snapshot with Wizer
10
//! 3. Instantiate the snapshot
11
//! 4. Call the instantiated snapshot's main function
12
//!
13
//! When checking that we get the same result, we don't just consider the main
14
//! function's results: we also consider memories and globals.
15
16
#![no_main]
17
18
use libfuzzer_sys::{
19
arbitrary::{Arbitrary, Unstructured},
20
fuzz_target,
21
};
22
use std::pin::pin;
23
use std::task::{Context, Poll, Waker};
24
use wasm_smith::MemoryOffsetChoices;
25
use wasmtime::*;
26
27
const FUEL: u32 = 1_000;
28
29
fuzz_target!(|data: &[u8]| {
30
let _ = env_logger::try_init();
31
32
let mut u = Unstructured::new(data);
33
34
let mut config = wasm_smith::Config::arbitrary(&mut u).unwrap();
35
config.max_memories = 10;
36
37
// We want small memories that are quick to compare, but we also want to
38
// allow memories to grow so we can shake out any memory-growth-related
39
// bugs, so we choose `2` instead of `1`.
40
config.max_memory32_bytes = 2 * 65536;
41
config.max_memory64_bytes = 2 * 65536;
42
43
// Always generate at least one function that we can hopefully use as an
44
// initialization function.
45
config.min_funcs = 1;
46
47
config.max_funcs = 10;
48
49
// Always at least one export, hopefully a function we can use as an
50
// initialization routine.
51
config.min_exports = 1;
52
53
config.max_exports = 10;
54
55
// Always use an offset immediate that is within the memory's minimum
56
// size. This should make trapping on loads/stores a little less
57
// frequent.
58
config.memory_offset_choices = MemoryOffsetChoices(1, 0, 0);
59
60
config.reference_types_enabled = false;
61
config.bulk_memory_enabled = false;
62
63
// Disable all imports
64
config.min_imports = 0;
65
config.max_imports = 0;
66
67
let Ok(mut module) = wasm_smith::Module::new(config, &mut u) else {
68
return;
69
};
70
module.ensure_termination(FUEL).unwrap();
71
let wasm = module.to_bytes();
72
73
if log::log_enabled!(log::Level::Debug) {
74
log::debug!("Writing test case to `test.wasm`");
75
std::fs::write("test.wasm", &wasm).unwrap();
76
if let Ok(wat) = wasmprinter::print_bytes(&wasm) {
77
log::debug!("Writing disassembly to `test.wat`");
78
std::fs::write("test.wat", wat).unwrap();
79
}
80
}
81
82
let config = Config::new();
83
let engine = Engine::new(&config).unwrap();
84
let module = Module::new(&engine, &wasm).unwrap();
85
86
let mut main_funcs = vec![];
87
let mut init_funcs = vec![];
88
for exp in module.exports() {
89
if let ExternType::Func(ty) = exp.ty() {
90
main_funcs.push(exp.name());
91
if ty.params().len() == 0 && ty.results().len() == 0 {
92
init_funcs.push(exp.name());
93
}
94
}
95
}
96
97
'init_loop: for init_func in init_funcs {
98
log::debug!("Using initialization function: {init_func:?}");
99
100
// Create a wizened snapshot of the given Wasm using `init_func` as the
101
// initialization routine.
102
let snapshot_wasm = {
103
let mut wizer = wasmtime_wizer::Wizer::new();
104
let mut store = Store::new(&engine, ());
105
wizer.init_func(init_func);
106
107
match assert_ready(wizer.run(&mut store, &wasm, async |store, module| {
108
Instance::new(store, module, &[])
109
})) {
110
Err(_) => continue 'init_loop,
111
Ok(s) => s,
112
}
113
};
114
let snapshot_module =
115
Module::new(&engine, &snapshot_wasm).expect("snapshot should be valid wasm");
116
117
// Now check that each "main" function behaves the same whether we call
118
// it on an instantiated snapshot or if we instantiate the original
119
// Wasm, call the initialization routine, and then call the "main"
120
// function.
121
'main_loop: for main_func in &main_funcs {
122
if *main_func == init_func {
123
// Wizer un-exports the initialization function, so we can't use
124
// it as a main function.
125
continue 'main_loop;
126
}
127
log::debug!("Using main function: {main_func:?}");
128
129
let mut store = Store::new(&engine, ());
130
131
// Instantiate the snapshot and call the main function.
132
let snapshot_instance = Instance::new(&mut store, &snapshot_module, &[]).unwrap();
133
let snapshot_main_func = snapshot_instance.get_func(&mut store, main_func).unwrap();
134
let main_args = snapshot_main_func
135
.ty(&store)
136
.params()
137
.map(|t| t.default_value().unwrap())
138
.collect::<Vec<_>>();
139
let mut snapshot_result =
140
vec![wasmtime::Val::I32(0); snapshot_main_func.ty(&store).results().len()];
141
let snapshot_call_result =
142
snapshot_main_func.call(&mut store, &main_args, &mut snapshot_result);
143
144
// Instantiate the original Wasm and then call the initialization
145
// and main functions back to back.
146
let instance = Instance::new(&mut store, &module, &[]).unwrap();
147
let init_func = instance
148
.get_typed_func::<(), ()>(&mut store, init_func)
149
.unwrap();
150
init_func.call(&mut store, ()).unwrap();
151
let main_func = instance.get_func(&mut store, main_func).unwrap();
152
let mut result = vec![wasmtime::Val::I32(0); main_func.ty(&store).results().len()];
153
let call_result = main_func.call(&mut store, &main_args, &mut result);
154
155
// Check that the function return values / traps are the same.
156
match (snapshot_call_result, call_result) {
157
// Both did not trap.
158
(Ok(()), Ok(())) => {
159
assert_eq!(snapshot_result.len(), result.len());
160
for (s, r) in snapshot_result.iter().zip(result.iter()) {
161
assert_val_eq(s, r);
162
}
163
}
164
165
// Both trapped.
166
(Err(_), Err(_)) => {}
167
168
// Divergence.
169
(s, r) => {
170
panic!(
171
"divergence between whether the main function traps or not!\n\n\
172
no snapshotting result = {r:?}\n\n\
173
snapshotted result = {s:?}",
174
);
175
}
176
}
177
178
// Assert that all other exports have the same state as well.
179
let exports = snapshot_instance
180
.exports(&mut store)
181
.map(|export| export.name().to_string())
182
.collect::<Vec<_>>();
183
for name in exports.iter() {
184
let export = snapshot_instance.get_export(&mut store, &name).unwrap();
185
match export {
186
Extern::Global(snapshot_global) => {
187
let global = instance.get_global(&mut store, &name).unwrap();
188
assert_val_eq(&snapshot_global.get(&mut store), &global.get(&mut store));
189
}
190
Extern::Memory(snapshot_memory) => {
191
let memory = instance.get_memory(&mut store, &name).unwrap();
192
let snapshot_memory = snapshot_memory.data(&store);
193
let memory = memory.data(&store);
194
assert_eq!(snapshot_memory.len(), memory.len());
195
// NB: Don't use `assert_eq` here so that we don't
196
// try to print the full memories' debug
197
// representations on failure.
198
if snapshot_memory != memory {
199
panic!("divergence between snapshot and non-snapshot memories");
200
}
201
}
202
Extern::SharedMemory(_)
203
| Extern::Func(_)
204
| Extern::Table(_)
205
| Extern::Tag(_) => continue,
206
}
207
}
208
}
209
}
210
});
211
212
fn assert_ready<F: Future>(f: F) -> F::Output {
213
let mut context = Context::from_waker(Waker::noop());
214
match pin!(f).poll(&mut context) {
215
Poll::Ready(ret) => ret,
216
Poll::Pending => panic!("future wasn't ready"),
217
}
218
}
219
220
fn assert_val_eq(a: &Val, b: &Val) {
221
match (a, b) {
222
(Val::I32(a), Val::I32(b)) => assert_eq!(a, b),
223
(Val::I64(a), Val::I64(b)) => assert_eq!(a, b),
224
(Val::F32(a), Val::F32(b)) => assert!({
225
let a = f32::from_bits(*a);
226
let b = f32::from_bits(*b);
227
a == b || (a.is_nan() && b.is_nan())
228
}),
229
(Val::F64(a), Val::F64(b)) => assert!({
230
let a = f64::from_bits(*a);
231
let b = f64::from_bits(*b);
232
a == b || (a.is_nan() && b.is_nan())
233
}),
234
(Val::V128(a), Val::V128(b)) => assert_eq!(a, b),
235
_ => panic!("{a:?} != {b:?}"),
236
}
237
}
238
239