1 #![cfg(not(miri))]
2 
3 use anyhow::Result;
4 use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
5 use wasmtime::*;
6 
7 #[test]
8 fn host_always_has_some_stack() -> Result<()> {
9     static HITS: AtomicUsize = AtomicUsize::new(0);
10     // assume hosts always have at least 128k of stack
11     const HOST_STACK: usize = 128 * 1024;
12 
13     let mut store = Store::<()>::default();
14 
15     // Create a module that's infinitely recursive, but calls the host on each
16     // level of wasm stack to always test how much host stack we have left.
17     let module = Module::new(
18         store.engine(),
19         r#"
20             (module
21                 (import "" "" (func $host))
22                 (func $recursive (export "foo")
23                     call $host
24                     call $recursive)
25             )
26         "#,
27     )?;
28     let func = Func::wrap(&mut store, test_host_stack);
29     let instance = Instance::new(&mut store, &module, &[func.into()])?;
30     let foo = instance.get_typed_func::<(), ()>(&mut store, "foo")?;
31 
32     // Make sure that our function traps and the trap says that the call stack
33     // has been exhausted.
34     let trap = foo.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
35     assert_eq!(trap, Trap::StackOverflow);
36 
37     // Additionally, however, and this is the crucial test, make sure that the
38     // host function actually completed. If HITS is 1 then we entered but didn't
39     // exit meaning we segfaulted while executing the host, yet still tried to
40     // recover from it with longjmp.
41     assert_eq!(HITS.load(SeqCst), 0);
42 
43     return Ok(());
44 
45     fn test_host_stack() {
46         HITS.fetch_add(1, SeqCst);
47         assert!(consume_some_stack(0, HOST_STACK) > 0);
48         HITS.fetch_sub(1, SeqCst);
49     }
50 
51     #[inline(never)]
52     fn consume_some_stack(ptr: usize, stack: usize) -> usize {
53         if stack == 0 {
54             return ptr;
55         }
56         let mut space = [0u8; 1024];
57         consume_some_stack(space.as_mut_ptr() as usize, stack.saturating_sub(1024))
58     }
59 }
60 
61 #[test]
62 fn big_stack_works_ok() -> Result<()> {
63     const N: usize = 10000;
64 
65     // Build a module with a function that uses a very large amount of stack space,
66     // modeled here by calling an i64-returning-function many times followed by
67     // adding them all into one i64.
68     //
69     // This should exercise the ability to consume multi-page stacks and
70     // only touch a few internals of it at a time.
71     let mut s = String::new();
72     s.push_str("(module\n");
73     s.push_str("(func (export \"\") (result i64)\n");
74     s.push_str("i64.const 0\n");
75     for _ in 0..N {
76         s.push_str("call $get\n");
77     }
78     for _ in 0..N {
79         s.push_str("i64.add\n");
80     }
81     s.push_str(")\n");
82     s.push_str("(func $get (result i64) i64.const 0)\n");
83     s.push_str(")\n");
84 
85     let mut store = Store::<()>::default();
86     let module = Module::new(store.engine(), &s)?;
87     let instance = Instance::new(&mut store, &module, &[])?;
88     let func = instance.get_typed_func::<(), i64>(&mut store, "")?;
89     assert_eq!(func.call(&mut store, ())?, 0);
90     Ok(())
91 }
92