//! Stack allocator tests. //! //! Covers allocation, alignment, read/write across the usable region, and //! (via subprocess) that the guard page actually SIGSEGVs. use smarm::stack::Stack; #[test] fn top_is_16_byte_aligned() { let s = Stack::new(64 * 1024, 4096).unwrap(); assert_eq!(s.top() as usize % 16, 0); } #[test] fn top_is_within_allocation() { let s = Stack::new(64 * 1024, 4096).unwrap(); let top = s.top() as usize; let base = s.usable_base() as usize; assert!(top > base); assert!(top <= base + s.stack_size()); } #[test] fn write_and_read_top_of_stack() { let s = Stack::new(64 * 1024, 4096).unwrap(); let sentinel: u64 = 0xDEAD_BEEF_CAFE_1234; unsafe { let ptr = s.top().sub(8) as *mut u64; ptr.write_volatile(sentinel); assert_eq!(ptr.read_volatile(), sentinel); } } #[test] fn write_and_read_bottom_of_usable_region() { let s = Stack::new(64 * 1024, 4096).unwrap(); let sentinel: u64 = 0x0102_0304_0506_0708; unsafe { let ptr = s.usable_base() as *mut u64; ptr.write_volatile(sentinel); assert_eq!(ptr.read_volatile(), sentinel); } } #[test] fn small_stack_allocates() { assert!(Stack::new(4096, 4096).is_ok()); } #[test] fn large_stack_allocates() { assert!(Stack::new(8 * 1024 * 1024, 4096).is_ok()); } #[test] fn stack_size_at_least_requested() { let s = Stack::new(64 * 1024, 4096).unwrap(); assert!(s.stack_size() >= 64 * 1024); } // --------------------------------------------------------------------------- // Guard page SIGSEGV tests — subprocess-based. // --------------------------------------------------------------------------- use std::env; use std::process::Command; fn run_as_child_if_requested() { match env::var("SMARM_SUBTEST").as_deref() { Ok("guard_page_direct") => { let s = Stack::new(64 * 1024, 4096).unwrap(); unsafe { let guard_ptr = s.usable_base().sub(1); guard_ptr.write_volatile(0xAB); } std::process::exit(0); } Ok("wide_guard_top") => { // One byte below the usable region, 64 KiB guard: must fault. let s = Stack::new(64 * 1024, 64 * 1024).unwrap(); unsafe { s.usable_base().sub(1).write_volatile(0xAB); } std::process::exit(0); } Ok("wide_guard_bottom") => { // The very bottom page of a 64 KiB guard: an unprobed C-style // leap over a small guard lands here — must still fault. let s = Stack::new(64 * 1024, 64 * 1024).unwrap(); unsafe { s.usable_base().sub(64 * 1024).write_volatile(0xAB); } std::process::exit(0); } Ok("stack_overflow") => { let s = Stack::new(64 * 1024, 4096).unwrap(); unsafe { let mut ptr = s.top().sub(1); let stop = s.usable_base().sub(1); while ptr >= stop { ptr.write_volatile(0xFF); ptr = ptr.sub(1); } } std::process::exit(0); } _ => {} } } fn spawn_subtest(name: &str) -> std::process::ExitStatus { let exe = env::current_exe().unwrap(); Command::new(exe) .env("SMARM_SUBTEST", name) .args(["--test-threads=1", "--quiet"]) .status() .expect("failed to spawn subprocess") } #[test] fn guard_page_causes_sigsegv() { run_as_child_if_requested(); let status = spawn_subtest("guard_page_direct"); #[cfg(unix)] { use std::os::unix::process::ExitStatusExt; assert_eq!(status.signal(), Some(11), "expected SIGSEGV, got: {:?}", status); } } #[test] fn stack_overflow_causes_sigsegv() { run_as_child_if_requested(); let status = spawn_subtest("stack_overflow"); #[cfg(unix)] { use std::os::unix::process::ExitStatusExt; assert_eq!(status.signal(), Some(11), "expected SIGSEGV, got: {:?}", status); } } // --------------------------------------------------------------------------- // RFC 019 — explicit shape: rounding, guard accessor, wide-guard coverage. // --------------------------------------------------------------------------- #[test] fn sizes_round_up_to_page() { let s = Stack::new(64 * 1024 + 1, 4096 + 1).unwrap(); assert_eq!(s.stack_size() % 4096, 0); assert_eq!(s.guard_size() % 4096, 0); assert!(s.stack_size() >= 64 * 1024 + 1); assert!(s.guard_size() >= 4096 + 1); } #[test] fn shape_reports_rounded_sizes() { let s = Stack::new(64 * 1024, 64 * 1024).unwrap(); assert_eq!(s.shape(), (64 * 1024, 64 * 1024)); } #[test] fn usable_base_sits_above_guard() { let s = Stack::new(64 * 1024, 64 * 1024).unwrap(); // The usable region must start exactly guard_size above the mapping // base: a write at usable_base is legal, one byte below is not (the // subprocess tests below prove the "not"). let sentinel: u64 = 0x1111_2222_3333_4444; unsafe { let ptr = s.usable_base() as *mut u64; ptr.write_volatile(sentinel); assert_eq!(ptr.read_volatile(), sentinel); } } #[test] fn wide_guard_faults_at_top() { run_as_child_if_requested(); let status = spawn_subtest("wide_guard_top"); #[cfg(unix)] { use std::os::unix::process::ExitStatusExt; assert_eq!(status.signal(), Some(11), "expected SIGSEGV, got: {:?}", status); } } #[test] fn wide_guard_faults_at_bottom() { run_as_child_if_requested(); let status = spawn_subtest("wide_guard_bottom"); #[cfg(unix)] { use std::os::unix::process::ExitStatusExt; assert_eq!(status.signal(), Some(11), "expected SIGSEGV, got: {:?}", status); } }