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 f.call(&mut caller, &[], &mut [])?; 44 Ok(()) 45 }, 46 ) 47 .unwrap(); 48 49 let mut store = Store::new(&engine, ()); 50 51 let instance = linker 52 .instantiate(&mut store, &module) 53 .expect("should instantiate okay"); 54 55 let run = instance 56 .get_typed_func::<(u32,), ()>(&mut store, "run") 57 .expect("should export `run` function"); 58 59 let mut max_stack_depth = 0; 60 for input in stacks.inputs().iter().copied() { 61 log::debug!("input: {}", input); 62 if let Err(trap) = run.call(&mut store, (input.into(),)) { 63 log::debug!("trap: {:?}", trap); 64 let get_stack = instance 65 .get_typed_func::<(), (u32, u32)>(&mut store, "get_stack") 66 .expect("should export `get_stack` function as expected"); 67 68 let (ptr, len) = get_stack 69 .call(&mut store, ()) 70 .expect("`get_stack` should not trap"); 71 72 let memory = instance 73 .get_memory(&mut store, "memory") 74 .expect("should have `memory` export"); 75 76 let host_trace = trap.downcast_ref::<WasmBacktrace>().unwrap().frames(); 77 let trap = trap.downcast_ref::<Trap>().unwrap(); 78 max_stack_depth = max_stack_depth.max(host_trace.len()); 79 assert_stack_matches(&mut store, memory, ptr, len, host_trace, *trap); 80 } 81 } 82 max_stack_depth 83 } 84 85 /// Assert that the Wasm program's view of the stack matches the host's view. 86 fn assert_stack_matches( 87 store: &mut impl AsContextMut, 88 memory: Memory, 89 ptr: u32, 90 len: u32, 91 host_trace: &[FrameInfo], 92 trap: Trap, 93 ) { 94 let mut data = vec![0; len as usize]; 95 memory 96 .read(&mut *store, ptr as usize, &mut data) 97 .expect("should be in bounds"); 98 99 let mut wasm_trace = vec![]; 100 for entry in data.chunks(4).rev() { 101 let mut bytes = [0; 4]; 102 bytes.copy_from_slice(entry); 103 let entry = u32::from_le_bytes(bytes); 104 wasm_trace.push(entry); 105 } 106 107 // If the test case here trapped due to stack overflow then the host trace 108 // will have one more frame than the wasm trace. The wasm didn't actually 109 // get to the point of pushing onto its own trace stack where the host will 110 // be able to see the exact function that triggered the stack overflow. In 111 // this situation the host trace is asserted to be one larger and then the 112 // top frame (first) of the host trace is discarded. 113 let host_trace = if trap == Trap::StackOverflow { 114 assert_eq!(host_trace.len(), wasm_trace.len() + 1); 115 &host_trace[1..] 116 } else { 117 host_trace 118 }; 119 120 log::debug!("Wasm thinks its stack is: {:?}", wasm_trace); 121 log::debug!( 122 "Host thinks the stack is: {:?}", 123 host_trace 124 .iter() 125 .map(|f| f.func_index()) 126 .collect::<Vec<_>>() 127 ); 128 129 assert_eq!(wasm_trace.len(), host_trace.len()); 130 for (wasm_entry, host_entry) in wasm_trace.into_iter().zip(host_trace) { 131 assert_eq!(wasm_entry, host_entry.func_index()); 132 } 133 } 134 135 #[cfg(test)] 136 mod tests { 137 use super::*; 138 use arbitrary::{Arbitrary, Unstructured}; 139 use rand::prelude::*; 140 141 const TARGET_STACK_DEPTH: usize = 10; 142 143 #[test] 144 fn smoke_test() { 145 let mut rng = SmallRng::seed_from_u64(0); 146 let mut buf = vec![0; 2048]; 147 148 for _ in 0..1024 { 149 rng.fill_bytes(&mut buf); 150 let u = Unstructured::new(&buf); 151 if let Ok(stacks) = Stacks::arbitrary_take_rest(u) { 152 let max_stack_depth = check_stacks(stacks); 153 if max_stack_depth >= TARGET_STACK_DEPTH { 154 return; 155 } 156 } 157 } 158 159 panic!( 160 "never generated a `Stacks` test case that reached {TARGET_STACK_DEPTH} \ 161 deep stack frames", 162 ); 163 } 164 } 165