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 = match trap.downcast_ref::<WasmBacktrace>() { 82 Some(bt) => bt.frames(), 83 None => { 84 assert!(stacks.limit.is_none()); 85 continue; 86 } 87 }; 88 let trap = trap.downcast_ref::<Trap>().unwrap(); 89 max_stack_depth = max_stack_depth.max(host_trace.len()); 90 assert_stack_matches( 91 &mut store, 92 memory, 93 ptr, 94 len, 95 host_trace, 96 *trap, 97 stacks.limit, 98 ); 99 } 100 } 101 max_stack_depth 102 } 103 104 /// Assert that the Wasm program's view of the stack matches the host's view. 105 fn assert_stack_matches( 106 store: &mut impl AsContextMut, 107 memory: Memory, 108 ptr: u32, 109 len: u32, 110 host_trace: &[FrameInfo], 111 trap: Trap, 112 limit: Option<std::num::NonZeroUsize>, 113 ) { 114 let mut data = vec![0; len as usize]; 115 memory 116 .read(&mut *store, ptr as usize, &mut data) 117 .expect("should be in bounds"); 118 119 let mut wasm_trace = vec![]; 120 for entry in data.chunks(4).rev() { 121 let mut bytes = [0; 4]; 122 bytes.copy_from_slice(entry); 123 let entry = u32::from_le_bytes(bytes); 124 wasm_trace.push(entry); 125 } 126 127 let trace_limit = match limit { 128 Some(n) => n.get(), 129 None => { 130 // Backtraces are disabled; the host trace should be empty. 131 assert!(host_trace.is_empty()); 132 return; 133 } 134 }; 135 136 // If the test case here trapped due to stack overflow then the host trace 137 // will have one more frame than the wasm trace. The wasm didn't actually 138 // get to the point of pushing onto its own trace stack where the host will 139 // be able to see the exact function that triggered the stack overflow. In 140 // this situation the host trace is asserted to be one larger and then the 141 // top frame (first) of the host trace is discarded. 142 let (host_trace, wasm_trace) = if trap == Trap::StackOverflow { 143 if host_trace.len() == trace_limit { 144 assert!( 145 trace_limit <= wasm_trace.len() + 1, 146 "Host trace size {} is larger than expected {}", 147 trace_limit, 148 wasm_trace.len() + 1 149 ); 150 } else { 151 assert_eq!(host_trace.len(), wasm_trace.len() + 1); 152 } 153 ( 154 &host_trace[1..], 155 &wasm_trace.get(..trace_limit - 1).unwrap_or(&wasm_trace), 156 ) 157 } else { 158 ( 159 host_trace, 160 &wasm_trace.get(..trace_limit).unwrap_or(&wasm_trace), 161 ) 162 }; 163 164 log::debug!("Wasm thinks its stack is: {wasm_trace:?}"); 165 log::debug!( 166 "Host thinks the stack is: {:?}", 167 host_trace 168 .iter() 169 .map(|f| f.func_index()) 170 .collect::<Vec<_>>() 171 ); 172 173 assert_eq!(wasm_trace.len(), host_trace.len()); 174 for (wasm_entry, host_entry) in wasm_trace.into_iter().zip(host_trace) { 175 assert_eq!(wasm_entry, &host_entry.func_index()); 176 } 177 } 178 179 #[cfg(test)] 180 mod tests { 181 use super::*; 182 use crate::test::gen_until_pass; 183 184 const TARGET_STACK_DEPTH: usize = 10; 185 186 #[test] 187 fn smoke_test() { 188 gen_until_pass(|stacks: Stacks, _u| { 189 let max_stack_depth = check_stacks(stacks); 190 Ok(max_stack_depth >= TARGET_STACK_DEPTH) 191 }); 192 } 193 } 194