use std::cell::Cell;
use crate::mutator::{RtResult, Trap};
thread_local! {
static LIMIT: Cell<usize> = const { Cell::new(0) };
}
pub const OVERFLOW: &str = "SystemStackError: stack level too deep";
pub fn margin(size: usize) -> usize {
(size / 4).min(256 << 10)
}
pub fn limit_for(lo: usize, hi: usize) -> usize {
lo + margin(hi.saturating_sub(lo))
}
#[inline(always)]
fn sp() -> usize {
let probe = 0u8;
std::hint::black_box(&probe) as *const u8 as usize
}
#[inline]
pub fn check() -> RtResult<()> {
let lim = LIMIT.with(|l| l.get());
if sp() < lim || lim == 0 {
return check_slow();
}
Ok(())
}
pub fn check_extra(extra: usize) -> RtResult<()> {
let mut lim = LIMIT.with(|l| l.get());
if lim == 0 {
lim = init_thread();
}
if lim > 1 && sp() < lim.saturating_add(extra) {
return Err(Trap::Panic(OVERFLOW.into()));
}
Ok(())
}
#[cold]
#[inline(never)]
fn check_slow() -> RtResult<()> {
let mut lim = LIMIT.with(|l| l.get());
if lim == 0 {
lim = init_thread();
}
if sp() < lim {
return Err(Trap::Panic(OVERFLOW.into()));
}
Ok(())
}
pub fn init_thread() -> usize {
let lim = match thread_bounds() {
Some((lo, hi)) if lo < hi => limit_for(lo, hi),
_ => 1,
};
LIMIT.with(|l| l.set(lim));
lim
}
pub fn set_limit(lim: usize) -> usize {
LIMIT.with(|l| l.replace(lim))
}
#[cfg(all(not(miri), any(target_os = "linux", target_os = "android")))]
fn thread_bounds() -> Option<(usize, usize)> {
unsafe {
let mut attr: libc::pthread_attr_t = std::mem::zeroed();
if libc::pthread_getattr_np(libc::pthread_self(), &mut attr) != 0 {
return None;
}
let mut addr: *mut libc::c_void = std::ptr::null_mut();
let mut size: libc::size_t = 0;
let r = libc::pthread_attr_getstack(&attr, &mut addr, &mut size);
libc::pthread_attr_destroy(&mut attr);
(r == 0 && !addr.is_null()).then(|| (addr as usize, addr as usize + size))
}
}
#[cfg(all(not(miri), any(target_os = "macos", target_os = "ios")))]
fn thread_bounds() -> Option<(usize, usize)> {
unsafe {
let t = libc::pthread_self();
let hi = libc::pthread_get_stackaddr_np(t) as usize;
let size = libc::pthread_get_stacksize_np(t);
(hi > size).then(|| (hi - size, hi))
}
}
#[cfg(all(not(miri), windows))]
fn thread_bounds() -> Option<(usize, usize)> {
#[link(name = "kernel32")]
extern "system" {
fn GetCurrentThreadStackLimits(low: *mut usize, high: *mut usize);
}
let (mut lo, mut hi) = (0usize, 0usize);
unsafe { GetCurrentThreadStackLimits(&mut lo, &mut hi) };
Some((lo, hi))
}
#[cfg(any(
miri,
not(any(target_os = "linux", target_os = "android", target_os = "macos", target_os = "ios", windows))
))]
fn thread_bounds() -> Option<(usize, usize)> {
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shallow_check_passes() {
assert!(check().is_ok());
}
#[test]
fn limit_above_the_stack_fires() {
let prev = set_limit(usize::MAX);
let r = check();
set_limit(prev);
assert!(matches!(r, Err(Trap::Panic(m)) if m == OVERFLOW));
}
#[cfg(not(miri))]
#[test]
fn deep_native_recursion_is_caught() {
fn down(n: u64) -> RtResult<u64> {
check()?;
let pad = std::hint::black_box([n; 32]);
Ok(down(n + 1)? + pad[0])
}
let r = std::thread::Builder::new().stack_size(1 << 20).spawn(|| down(0)).unwrap().join().unwrap();
assert!(r.is_err());
}
}