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, &params, &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