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