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