#![cfg(feature = "alloc-global")]
use std::alloc::Layout;
use std::sync::atomic::{AtomicBool, Ordering};
use sefer_alloc::registry::{bootstrap, HeapRegistry};
static SERIAL: AtomicBool = AtomicBool::new(false);
struct SerialGuard;
impl SerialGuard {
fn acquire() -> Self {
while SERIAL
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
std::hint::spin_loop();
}
SerialGuard
}
}
impl Drop for SerialGuard {
fn drop(&mut self) {
SERIAL.store(false, Ordering::Release);
}
}
#[test]
fn alloc_loop_in_one_segment_does_not_panic() {
let _serial = SerialGuard::acquire();
let _ = bootstrap::ensure();
let heap = HeapRegistry::claim();
assert!(!heap.is_null(), "HeapRegistry::claim returned null");
let layout = Layout::from_size_align(64, 8).unwrap();
let mut ptrs: Vec<*mut u8> = Vec::new();
for i in 0u8..200 {
let p = unsafe { (*heap).alloc(layout) };
assert!(!p.is_null(), "alloc returned null at iteration {i}");
unsafe {
core::ptr::write_bytes(p, i, 64);
assert_eq!(p.read(), i, "read-back mismatch at iteration {i}");
}
ptrs.push(p);
}
for p in ptrs {
unsafe { (*heap).dealloc(p, layout) };
}
unsafe { HeapRegistry::recycle(heap) };
}
#[test]
fn alloc_across_classes_works() {
let _serial = SerialGuard::acquire();
let _ = bootstrap::ensure();
let heap = HeapRegistry::claim();
assert!(!heap.is_null(), "HeapRegistry::claim returned null");
let sizes: &[usize] = &[16, 32, 64, 128, 256, 512, 1024, 2048];
let n_per_size: usize = 16;
let mut all_ptrs: Vec<(*mut u8, Layout)> = Vec::new();
for &sz in sizes {
let layout = Layout::from_size_align(sz, 8).unwrap();
for i in 0u8..n_per_size as u8 {
let p = unsafe { (*heap).alloc(layout) };
assert!(!p.is_null(), "alloc({sz}) returned null at i={i}");
unsafe {
core::ptr::write_bytes(p, i.wrapping_add(sz as u8), sz);
assert_eq!(
p.read(),
i.wrapping_add(sz as u8),
"read-back mismatch at sz={sz} i={i}"
);
}
all_ptrs.push((p, layout));
}
}
for (p, layout) in all_ptrs {
unsafe { (*heap).dealloc(p, layout) };
}
unsafe { HeapRegistry::recycle(heap) };
}
#[cfg(feature = "alloc-xthread")]
#[test]
fn cross_thread_dealloc_after_stamp_cache_works() {
use sefer_alloc::Heap;
let _serial = SerialGuard::acquire();
let _ = bootstrap::ensure();
let mut heap = Heap::new().expect("Heap::new failed");
let layout = Layout::from_size_align(64, 8).unwrap();
const N: usize = 64;
let mut ptrs: Vec<(*mut u8, u64)> = Vec::new();
for i in 0..N {
let p = heap.alloc(layout);
assert!(!p.is_null(), "alloc returned null at i={i}");
let tag = (i as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15);
unsafe { core::ptr::write(p as *mut u64, tag) };
ptrs.push((p, tag));
}
for &(p, tag) in &ptrs {
let read = unsafe { core::ptr::read(p as *const u64) };
assert_eq!(read, tag, "tag corrupt before cross-thread free");
}
let addrs: Vec<usize> = ptrs.iter().map(|&(p, _)| p as usize).collect();
std::thread::spawn(move || {
for addr in addrs {
Heap::dealloc_any_thread(addr as *mut u8, layout);
}
})
.join()
.expect("cross-thread free thread panicked");
let mut new_ptrs = Vec::new();
for _ in 0..N {
let p = heap.alloc(layout);
assert!(!p.is_null(), "re-alloc returned null");
unsafe {
core::ptr::write_bytes(p, 0xBB, 64);
assert_eq!(p.read(), 0xBB, "re-alloc block not writable (UAF?)");
}
new_ptrs.push(p);
}
for p in new_ptrs {
heap.dealloc(p, layout);
}
}