1 use crate::generators::Stacks;
2 use wasmtime::*;
3 
4 /// Run the given `Stacks` test case and assert that the host's view of the Wasm
5 /// stack matches the test case's understanding of the Wasm stack.
6 ///
7 /// Returns the maximum stack depth we checked.
8 pub fn check_stacks(stacks: Stacks) -> usize {
9     let wasm = stacks.wasm();
10     crate::oracles::log_wasm(&wasm);
11 
12     let engine = Engine::default();
13     let module = Module::new(&engine, &wasm).expect("should compile okay");
14 
15     let mut linker = Linker::new(&engine);
16     linker
17         .func_wrap(
18             "host",
19             "check_stack",
20             |mut caller: Caller<'_, ()>| -> Result<(), Trap> {
21                 let fuel = caller
22                     .get_export("fuel")
23                     .expect("should export `fuel`")
24                     .into_global()
25                     .expect("`fuel` export should be a global");
26 
27                 let fuel_left = fuel.get(&mut caller).unwrap_i32();
28                 if fuel_left == 0 {
29                     return Err(Trap::new("out of fuel"));
30                 }
31 
32                 fuel.set(&mut caller, Val::I32(fuel_left - 1)).unwrap();
33                 Ok(())
34             },
35         )
36         .unwrap()
37         .func_wrap(
38             "host",
39             "call_func",
40             |mut caller: Caller<'_, ()>, f: Option<Func>| {
41                 let f = f.unwrap();
42                 f.call(&mut caller, &[], &mut [])?;
43                 Ok(())
44             },
45         )
46         .unwrap();
47 
48     let mut store = Store::new(&engine, ());
49 
50     let instance = linker
51         .instantiate(&mut store, &module)
52         .expect("should instantiate okay");
53 
54     let run = instance
55         .get_typed_func::<(u32,), (), _>(&mut store, "run")
56         .expect("should export `run` function");
57 
58     let mut max_stack_depth = 0;
59     for input in stacks.inputs().iter().copied() {
60         log::debug!("input: {}", input);
61         if let Err(trap) = run.call(&mut store, (input.into(),)) {
62             log::debug!("trap: {}", trap);
63             let get_stack = instance
64                 .get_typed_func::<(), (u32, u32), _>(&mut store, "get_stack")
65                 .expect("should export `get_stack` function as expected");
66 
67             let (ptr, len) = get_stack
68                 .call(&mut store, ())
69                 .expect("`get_stack` should not trap");
70 
71             let memory = instance
72                 .get_memory(&mut store, "memory")
73                 .expect("should have `memory` export");
74 
75             let host_trace = trap.trace().unwrap();
76             max_stack_depth = max_stack_depth.max(host_trace.len());
77             assert_stack_matches(&mut store, memory, ptr, len, host_trace, trap.trap_code());
78         }
79     }
80     max_stack_depth
81 }
82 
83 /// Assert that the Wasm program's view of the stack matches the host's view.
84 fn assert_stack_matches(
85     store: &mut impl AsContextMut,
86     memory: Memory,
87     ptr: u32,
88     len: u32,
89     host_trace: &[FrameInfo],
90     trap_code: Option<TrapCode>,
91 ) {
92     let mut data = vec![0; len as usize];
93     memory
94         .read(&mut *store, ptr as usize, &mut data)
95         .expect("should be in bounds");
96 
97     let mut wasm_trace = vec![];
98     for entry in data.chunks(4).rev() {
99         let mut bytes = [0; 4];
100         bytes.copy_from_slice(entry);
101         let entry = u32::from_le_bytes(bytes);
102         wasm_trace.push(entry);
103     }
104 
105     // If the test case here trapped due to stack overflow then the host trace
106     // will have one more frame than the wasm trace. The wasm didn't actually
107     // get to the point of pushing onto its own trace stack where the host will
108     // be able to see the exact function that triggered the stack overflow. In
109     // this situation the host trace is asserted to be one larger and then the
110     // top frame (first) of the host trace is discarded.
111     let host_trace = if trap_code == Some(TrapCode::StackOverflow) {
112         assert_eq!(host_trace.len(), wasm_trace.len() + 1);
113         &host_trace[1..]
114     } else {
115         host_trace
116     };
117 
118     log::debug!("Wasm thinks its stack is: {:?}", wasm_trace);
119     log::debug!(
120         "Host thinks the stack is: {:?}",
121         host_trace
122             .iter()
123             .map(|f| f.func_index())
124             .collect::<Vec<_>>()
125     );
126 
127     assert_eq!(wasm_trace.len(), host_trace.len());
128     for (wasm_entry, host_entry) in wasm_trace.into_iter().zip(host_trace) {
129         assert_eq!(wasm_entry, host_entry.func_index());
130     }
131 }
132 
133 #[cfg(test)]
134 mod tests {
135     use super::*;
136     use arbitrary::{Arbitrary, Unstructured};
137     use rand::prelude::*;
138 
139     const TARGET_STACK_DEPTH: usize = 10;
140 
141     #[test]
142     fn smoke_test() {
143         let mut rng = SmallRng::seed_from_u64(0);
144         let mut buf = vec![0; 2048];
145 
146         for _ in 0..1024 {
147             rng.fill_bytes(&mut buf);
148             let u = Unstructured::new(&buf);
149             if let Ok(stacks) = Stacks::arbitrary_take_rest(u) {
150                 let max_stack_depth = check_stacks(stacks);
151                 if max_stack_depth >= TARGET_STACK_DEPTH {
152                     return;
153                 }
154             }
155         }
156 
157         panic!(
158             "never generated a `Stacks` test case that reached {TARGET_STACK_DEPTH} \
159              deep stack frames",
160         );
161     }
162 }
163