1 use crate::generators::Stacks; 2 use anyhow::bail; 3 use wasmtime::*; 4 5 /// Run the given `Stacks` test case and assert that the host's view of the Wasm 6 /// stack matches the test case's understanding of the Wasm stack. 7 /// 8 /// Returns the maximum stack depth we checked. 9 pub fn check_stacks(stacks: Stacks) -> usize { 10 let wasm = stacks.wasm(); 11 crate::oracles::log_wasm(&wasm); 12 13 let engine = Engine::default(); 14 let module = Module::new(&engine, &wasm).expect("should compile okay"); 15 16 let mut linker = Linker::new(&engine); 17 linker 18 .func_wrap( 19 "host", 20 "check_stack", 21 |mut caller: Caller<'_, ()>| -> Result<()> { 22 let fuel = caller 23 .get_export("fuel") 24 .expect("should export `fuel`") 25 .into_global() 26 .expect("`fuel` export should be a global"); 27 28 let fuel_left = fuel.get(&mut caller).unwrap_i32(); 29 if fuel_left == 0 { 30 bail!(Trap::OutOfFuel); 31 } 32 33 fuel.set(&mut caller, Val::I32(fuel_left - 1)).unwrap(); 34 Ok(()) 35 }, 36 ) 37 .unwrap() 38 .func_wrap( 39 "host", 40 "call_func", 41 |mut caller: Caller<'_, ()>, f: Option<Func>| { 42 let f = f.unwrap(); 43 let ty = f.ty(&caller); 44 let params = vec![Val::I32(0); ty.params().len()]; 45 let mut results = vec![Val::I32(0); ty.results().len()]; 46 f.call(&mut caller, ¶ms, &mut results)?; 47 Ok(()) 48 }, 49 ) 50 .unwrap(); 51 52 let mut store = Store::new(&engine, ()); 53 54 let instance = linker 55 .instantiate(&mut store, &module) 56 .expect("should instantiate okay"); 57 58 let run = instance 59 .get_typed_func::<(u32,), ()>(&mut store, "run") 60 .expect("should export `run` function"); 61 62 let mut max_stack_depth = 0; 63 for input in stacks.inputs().iter().copied() { 64 log::debug!("input: {input}"); 65 if let Err(trap) = run.call(&mut store, (input.into(),)) { 66 log::debug!("trap: {trap:?}"); 67 let get_stack = instance 68 .get_typed_func::<(), (u32, u32)>(&mut store, "get_stack") 69 .expect("should export `get_stack` function as expected"); 70 71 let (ptr, len) = get_stack 72 .call(&mut store, ()) 73 .expect("`get_stack` should not trap"); 74 75 let memory = instance 76 .get_memory(&mut store, "memory") 77 .expect("should have `memory` export"); 78 79 let host_trace = trap.downcast_ref::<WasmBacktrace>().unwrap().frames(); 80 let trap = trap.downcast_ref::<Trap>().unwrap(); 81 max_stack_depth = max_stack_depth.max(host_trace.len()); 82 assert_stack_matches(&mut store, memory, ptr, len, host_trace, *trap); 83 } 84 } 85 max_stack_depth 86 } 87 88 /// Assert that the Wasm program's view of the stack matches the host's view. 89 fn assert_stack_matches( 90 store: &mut impl AsContextMut, 91 memory: Memory, 92 ptr: u32, 93 len: u32, 94 host_trace: &[FrameInfo], 95 trap: Trap, 96 ) { 97 let mut data = vec![0; len as usize]; 98 memory 99 .read(&mut *store, ptr as usize, &mut data) 100 .expect("should be in bounds"); 101 102 let mut wasm_trace = vec![]; 103 for entry in data.chunks(4).rev() { 104 let mut bytes = [0; 4]; 105 bytes.copy_from_slice(entry); 106 let entry = u32::from_le_bytes(bytes); 107 wasm_trace.push(entry); 108 } 109 110 // If the test case here trapped due to stack overflow then the host trace 111 // will have one more frame than the wasm trace. The wasm didn't actually 112 // get to the point of pushing onto its own trace stack where the host will 113 // be able to see the exact function that triggered the stack overflow. In 114 // this situation the host trace is asserted to be one larger and then the 115 // top frame (first) of the host trace is discarded. 116 let host_trace = if trap == Trap::StackOverflow { 117 assert_eq!(host_trace.len(), wasm_trace.len() + 1); 118 &host_trace[1..] 119 } else { 120 host_trace 121 }; 122 123 log::debug!("Wasm thinks its stack is: {wasm_trace:?}"); 124 log::debug!( 125 "Host thinks the stack is: {:?}", 126 host_trace 127 .iter() 128 .map(|f| f.func_index()) 129 .collect::<Vec<_>>() 130 ); 131 132 assert_eq!(wasm_trace.len(), host_trace.len()); 133 for (wasm_entry, host_entry) in wasm_trace.into_iter().zip(host_trace) { 134 assert_eq!(wasm_entry, host_entry.func_index()); 135 } 136 } 137 138 #[cfg(test)] 139 mod tests { 140 use super::*; 141 use crate::test::gen_until_pass; 142 143 const TARGET_STACK_DEPTH: usize = 10; 144 145 #[test] 146 fn smoke_test() { 147 gen_until_pass(|stacks: Stacks, _u| { 148 let max_stack_depth = check_stacks(stacks); 149 Ok(max_stack_depth >= TARGET_STACK_DEPTH) 150 }); 151 } 152 } 153