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