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