Path: blob/main/crates/wizer/fuzz/fuzz_targets/same_result.rs
2459 views
//! Check that we get the same result whether we1//!2//! 1. Call the initialization function3//! 2. Call the main function4//!5//! or6//!7//! 1. Call the initialization function8//! 2. Snapshot with Wizer9//! 3. Instantiate the snapshot10//! 4. Call the instantiated snapshot's main function11//!12//! When checking that we get the same result, we don't just consider the main13//! function's results: we also consider memories and globals.1415#![no_main]1617use libfuzzer_sys::{18arbitrary::{Arbitrary, Unstructured},19fuzz_target,20};21use std::pin::pin;22use std::task::{Context, Poll, Waker};23use wasm_smith::MemoryOffsetChoices;24use wasmtime::*;2526const FUEL: u32 = 1_000;2728fuzz_target!(|data: &[u8]| {29let _ = env_logger::try_init();3031let mut u = Unstructured::new(data);3233let mut config = wasm_smith::Config::arbitrary(&mut u).unwrap();34config.max_memories = 10;3536// We want small memories that are quick to compare, but we also want to37// allow memories to grow so we can shake out any memory-growth-related38// bugs, so we choose `2` instead of `1`.39config.max_memory32_bytes = 2 * 65536;40config.max_memory64_bytes = 2 * 65536;4142// Always generate at least one function that we can hopefully use as an43// initialization function.44config.min_funcs = 1;4546config.max_funcs = 10;4748// Always at least one export, hopefully a function we can use as an49// initialization routine.50config.min_exports = 1;5152config.max_exports = 10;5354// Always use an offset immediate that is within the memory's minimum55// size. This should make trapping on loads/stores a little less56// frequent.57config.memory_offset_choices = MemoryOffsetChoices(1, 0, 0);5859config.reference_types_enabled = false;60config.bulk_memory_enabled = false;6162// Disable all imports63config.min_imports = 0;64config.max_imports = 0;6566let Ok(mut module) = wasm_smith::Module::new(config, &mut u) else {67return;68};69module.ensure_termination(FUEL).unwrap();70let wasm = module.to_bytes();7172if log::log_enabled!(log::Level::Debug) {73log::debug!("Writing test case to `test.wasm`");74std::fs::write("test.wasm", &wasm).unwrap();75if let Ok(wat) = wasmprinter::print_bytes(&wasm) {76log::debug!("Writing disassembly to `test.wat`");77std::fs::write("test.wat", wat).unwrap();78}79}8081let config = Config::new();82let engine = Engine::new(&config).unwrap();83let module = Module::new(&engine, &wasm).unwrap();8485let mut main_funcs = vec![];86let mut init_funcs = vec![];87for exp in module.exports() {88if let ExternType::Func(ty) = exp.ty() {89main_funcs.push(exp.name());90if ty.params().len() == 0 && ty.results().len() == 0 {91init_funcs.push(exp.name());92}93}94}9596'init_loop: for init_func in init_funcs {97log::debug!("Using initialization function: {init_func:?}");9899// Create a wizened snapshot of the given Wasm using `init_func` as the100// initialization routine.101let snapshot_wasm = {102let mut wizer = wasmtime_wizer::Wizer::new();103let mut store = Store::new(&engine, ());104wizer.init_func(init_func);105106match assert_ready(wizer.run(&mut store, &wasm, async |store, module| {107Instance::new(store, module, &[])108})) {109Err(_) => continue 'init_loop,110Ok(s) => s,111}112};113let snapshot_module =114Module::new(&engine, &snapshot_wasm).expect("snapshot should be valid wasm");115116// Now check that each "main" function behaves the same whether we call117// it on an instantiated snapshot or if we instantiate the original118// Wasm, call the initialization routine, and then call the "main"119// function.120'main_loop: for main_func in &main_funcs {121if *main_func == init_func {122// Wizer un-exports the initialization function, so we can't use123// it as a main function.124continue 'main_loop;125}126log::debug!("Using main function: {main_func:?}");127128let mut store = Store::new(&engine, ());129130// Instantiate the snapshot and call the main function.131let snapshot_instance = Instance::new(&mut store, &snapshot_module, &[]).unwrap();132let snapshot_main_func = snapshot_instance.get_func(&mut store, main_func).unwrap();133let main_args = snapshot_main_func134.ty(&store)135.params()136.map(|t| t.default_value().unwrap())137.collect::<Vec<_>>();138let mut snapshot_result =139vec![wasmtime::Val::I32(0); snapshot_main_func.ty(&store).results().len()];140let snapshot_call_result =141snapshot_main_func.call(&mut store, &main_args, &mut snapshot_result);142143// Instantiate the original Wasm and then call the initialization144// and main functions back to back.145let instance = Instance::new(&mut store, &module, &[]).unwrap();146let init_func = instance147.get_typed_func::<(), ()>(&mut store, init_func)148.unwrap();149init_func.call(&mut store, ()).unwrap();150let main_func = instance.get_func(&mut store, main_func).unwrap();151let mut result = vec![wasmtime::Val::I32(0); main_func.ty(&store).results().len()];152let call_result = main_func.call(&mut store, &main_args, &mut result);153154// Check that the function return values / traps are the same.155match (snapshot_call_result, call_result) {156// Both did not trap.157(Ok(()), Ok(())) => {158assert_eq!(snapshot_result.len(), result.len());159for (s, r) in snapshot_result.iter().zip(result.iter()) {160assert_val_eq(s, r);161}162}163164// Both trapped.165(Err(_), Err(_)) => {}166167// Divergence.168(s, r) => {169panic!(170"divergence between whether the main function traps or not!\n\n\171no snapshotting result = {r:?}\n\n\172snapshotted result = {s:?}",173);174}175}176177// Assert that all other exports have the same state as well.178let exports = snapshot_instance179.exports(&mut store)180.map(|export| export.name().to_string())181.collect::<Vec<_>>();182for name in exports.iter() {183let export = snapshot_instance.get_export(&mut store, &name).unwrap();184match export {185Extern::Global(snapshot_global) => {186let global = instance.get_global(&mut store, &name).unwrap();187assert_val_eq(&snapshot_global.get(&mut store), &global.get(&mut store));188}189Extern::Memory(snapshot_memory) => {190let memory = instance.get_memory(&mut store, &name).unwrap();191let snapshot_memory = snapshot_memory.data(&store);192let memory = memory.data(&store);193assert_eq!(snapshot_memory.len(), memory.len());194// NB: Don't use `assert_eq` here so that we don't195// try to print the full memories' debug196// representations on failure.197if snapshot_memory != memory {198panic!("divergence between snapshot and non-snapshot memories");199}200}201Extern::SharedMemory(_)202| Extern::Func(_)203| Extern::Table(_)204| Extern::Tag(_) => continue,205}206}207}208}209});210211fn assert_ready<F: Future>(f: F) -> F::Output {212let mut context = Context::from_waker(Waker::noop());213match pin!(f).poll(&mut context) {214Poll::Ready(ret) => ret,215Poll::Pending => panic!("future wasn't ready"),216}217}218219fn assert_val_eq(a: &Val, b: &Val) {220match (a, b) {221(Val::I32(a), Val::I32(b)) => assert_eq!(a, b),222(Val::I64(a), Val::I64(b)) => assert_eq!(a, b),223(Val::F32(a), Val::F32(b)) => assert!({224let a = f32::from_bits(*a);225let b = f32::from_bits(*b);226a == b || (a.is_nan() && b.is_nan())227}),228(Val::F64(a), Val::F64(b)) => assert!({229let a = f64::from_bits(*a);230let b = f64::from_bits(*b);231a == b || (a.is_nan() && b.is_nan())232}),233(Val::V128(a), Val::V128(b)) => assert_eq!(a, b),234_ => panic!("{a:?} != {b:?}"),235}236}237238239