Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/tests/all/stack_overflow.rs
1692 views
1
#![cfg(not(miri))]
2
3
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
4
use wasmtime::*;
5
use wasmtime_test_macros::wasmtime_test;
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 = if cfg!(target_arch = "x86_64") {
14
let mut config = Config::new();
15
// Force cranelift-based libcalls to show up by ensuring that platform
16
// support is turned off.
17
unsafe {
18
config.cranelift_flag_set("has_avx", "false");
19
config.cranelift_flag_set("has_sse42", "false");
20
config.cranelift_flag_set("has_sse41", "false");
21
config.cranelift_flag_set("has_ssse3", "false");
22
config.cranelift_flag_set("has_sse3", "false");
23
}
24
Store::new(&Engine::new(&config)?, ())
25
} else {
26
Store::<()>::default()
27
};
28
29
// Create a module that's infinitely recursive, but calls the host on each
30
// level of wasm stack to always test how much host stack we have left.
31
//
32
// Each of the function exports of this module calls out to the host in a
33
// different way, and each one is tested below to make sure that the way of
34
// exiting out to the host is tested thoroughly.
35
let module = Module::new(
36
store.engine(),
37
r#"
38
(module
39
(import "" "" (func $host1))
40
(import "" "" (func $host2))
41
42
;; exit via wasm-to-native trampoline
43
(func $recursive1 (export "f1")
44
call $host1
45
call $recursive1)
46
47
;; exit via wasm-to-array trampoline
48
(func $recursive2 (export "f2")
49
call $host2
50
call $recursive2)
51
52
;; exit via a wasmtime-based libcall
53
(memory 1)
54
(func $recursive3 (export "f3")
55
(drop (memory.grow (i32.const 0)))
56
call $recursive3)
57
58
;; exit via a cranelift-based libcall
59
(func $recursive4 (export "f4")
60
(drop (call $f32_ceil (f32.const 0)))
61
call $recursive4)
62
(func $f32_ceil (param f32) (result f32)
63
(f32.ceil (local.get 0)))
64
)
65
"#,
66
)?;
67
let host1 = Func::wrap(&mut store, test_host_stack);
68
let ty = FuncType::new(store.engine(), [], []);
69
let host2 = Func::new(&mut store, ty, |_, _, _| {
70
test_host_stack();
71
Ok(())
72
});
73
let instance = Instance::new(&mut store, &module, &[host1.into(), host2.into()])?;
74
let f1 = instance.get_typed_func::<(), ()>(&mut store, "f1")?;
75
let f2 = instance.get_typed_func::<(), ()>(&mut store, "f2")?;
76
let f3 = instance.get_typed_func::<(), ()>(&mut store, "f3")?;
77
let f4 = instance.get_typed_func::<(), ()>(&mut store, "f4")?;
78
79
// Make sure that our function traps and the trap says that the call stack
80
// has been exhausted.
81
let hits1 = HITS.load(SeqCst);
82
let trap = f1.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
83
assert_eq!(trap, Trap::StackOverflow);
84
let hits2 = HITS.load(SeqCst);
85
let trap = f2.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
86
assert_eq!(trap, Trap::StackOverflow);
87
let hits3 = HITS.load(SeqCst);
88
let trap = f3.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
89
assert_eq!(trap, Trap::StackOverflow);
90
let hits4 = HITS.load(SeqCst);
91
let trap = f4.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
92
assert_eq!(trap, Trap::StackOverflow);
93
let hits5 = HITS.load(SeqCst);
94
95
// Additionally, however, and this is the crucial test, make sure that the
96
// host function actually completed. If HITS is 1 then we entered but didn't
97
// exit meaning we segfaulted while executing the host, yet still tried to
98
// recover from it with longjmp.
99
assert_eq!(hits1, 0);
100
assert_eq!(hits2, 0);
101
assert_eq!(hits3, 0);
102
assert_eq!(hits4, 0);
103
assert_eq!(hits5, 0);
104
105
return Ok(());
106
107
fn test_host_stack() {
108
HITS.fetch_add(1, SeqCst);
109
assert!(consume_some_stack(0, HOST_STACK) > 0);
110
HITS.fetch_sub(1, SeqCst);
111
}
112
113
#[inline(never)]
114
fn consume_some_stack(ptr: usize, stack: usize) -> usize {
115
if stack == 0 {
116
return ptr;
117
}
118
let mut space = [0u8; 1024];
119
consume_some_stack(space.as_mut_ptr() as usize, stack.saturating_sub(1024))
120
}
121
}
122
123
#[wasmtime_test]
124
fn big_stack_works_ok(config: &mut Config) -> Result<()> {
125
// This test takes 1m+ in ASAN and isn't too useful, so prune it.
126
if cfg!(asan) {
127
return Ok(());
128
}
129
130
const N: usize = 10000;
131
132
// Build a module with a function that uses a very large amount of stack space,
133
// modeled here by calling an i64-returning-function many times followed by
134
// adding them all into one i64.
135
//
136
// This should exercise the ability to consume multi-page stacks and
137
// only touch a few internals of it at a time.
138
let mut s = String::new();
139
s.push_str("(module\n");
140
s.push_str("(func (export \"\") (result i64)\n");
141
s.push_str("i64.const 0\n");
142
for _ in 0..N {
143
s.push_str("call $get\n");
144
}
145
for _ in 0..N {
146
s.push_str("i64.add\n");
147
}
148
s.push_str(")\n");
149
s.push_str("(func $get (result i64) i64.const 0)\n");
150
s.push_str(")\n");
151
152
// Disable cranelift optimizations to ensure that this test doesn't take too
153
// long in debug mode due to the large size of its code.
154
config.cranelift_opt_level(OptLevel::None);
155
config.cranelift_regalloc_algorithm(RegallocAlgorithm::SinglePass);
156
let engine = Engine::new(config)?;
157
158
let mut store = Store::new(&engine, ());
159
let module = Module::new(store.engine(), &s)?;
160
let instance = Instance::new(&mut store, &module, &[])?;
161
let func = instance.get_typed_func::<(), i64>(&mut store, "")?;
162
assert_eq!(func.call(&mut store, ())?, 0);
163
Ok(())
164
}
165
166