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
assert_ready<F: Future>(f: F) -> F::Output212 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
assert_val_eq(a: &Val, b: &Val)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