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