use std::alloc::{GlobalAlloc, Layout, System};
use std::cell::Cell;
use std::sync::atomic::AtomicBool;
pub static AUDIT_ENABLED: AtomicBool = AtomicBool::new(false);
thread_local! {
static TRACKING_ACTIVE: Cell<bool> = const { Cell::new(false) };
static ALLOC_COUNT_TLS: Cell<usize> = const { Cell::new(0) };
}
pub fn get_alloc_count() -> usize {
ALLOC_COUNT_TLS.try_with(|count| count.get()).unwrap_or(0)
}
pub fn set_alloc_count(val: usize) {
let _ = ALLOC_COUNT_TLS.try_with(|count| count.set(val));
}
pub fn is_tracking_active() -> bool {
TRACKING_ACTIVE
.try_with(|active| active.get())
.unwrap_or(false)
}
pub fn set_tracking_active(active: bool) {
let _ = TRACKING_ACTIVE.try_with(|a| a.set(active));
}
pub struct CountingAllocator;
impl CountingAllocator {
pub unsafe fn alloc(layout: Layout) -> *mut u8 {
if is_tracking_active() {
let _ = ALLOC_COUNT_TLS.try_with(|count| {
count.set(count.get() + 1);
});
}
unsafe { System.alloc(layout) }
}
pub unsafe fn dealloc(ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
}
unsafe impl GlobalAlloc for CountingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
unsafe { Self::alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { Self::dealloc(ptr, layout) }
}
}
pub struct TrackingGuard {
_private: (),
}
impl TrackingGuard {
pub fn new() -> Self {
set_tracking_active(true);
set_alloc_count(0);
Self { _private: () }
}
}
impl Default for TrackingGuard {
fn default() -> Self {
Self::new()
}
}
impl Drop for TrackingGuard {
fn drop(&mut self) {
set_tracking_active(false);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::Ordering;
fn fresh_state() {
set_tracking_active(false);
set_alloc_count(0);
AUDIT_ENABLED.store(false, Ordering::Relaxed);
}
#[test]
fn tracking_guard_new_sets_active() {
fresh_state();
assert!(!is_tracking_active());
let guard = TrackingGuard::new();
assert!(is_tracking_active());
drop(guard);
}
#[test]
fn tracking_guard_new_resets_alloc_count() {
set_alloc_count(42);
let guard = TrackingGuard::new();
assert_eq!(get_alloc_count(), 0);
drop(guard);
}
#[test]
fn tracking_guard_drop_clears_tracking_active() {
fresh_state();
let guard = TrackingGuard::new();
assert!(is_tracking_active());
drop(guard);
assert!(!is_tracking_active());
}
#[test]
fn tracking_guard_default_works() {
fresh_state();
let guard = TrackingGuard::default();
assert!(is_tracking_active());
assert_eq!(get_alloc_count(), 0);
drop(guard);
}
#[test]
fn multiple_guards_work() {
fresh_state();
let g1 = TrackingGuard::new();
assert!(is_tracking_active());
drop(g1);
assert!(!is_tracking_active());
let g2 = TrackingGuard::new();
assert!(is_tracking_active());
drop(g2);
assert!(!is_tracking_active());
}
#[test]
fn alloc_count_is_zero_after_tracking_guard() {
fresh_state();
let _g = TrackingGuard::new();
assert_eq!(get_alloc_count(), 0);
}
#[test]
fn parallel_allocation_tracking_isolation() {
use std::thread;
fresh_state();
let num_threads = 8;
let mut handles = Vec::new();
for i in 0..num_threads {
handles.push(thread::spawn(move || {
let _guard = TrackingGuard::new();
let mut v = Vec::new();
for j in 0..(i + 1) * 10 {
v.push(Box::new(j));
}
let count = get_alloc_count();
assert!(
count >= (i + 1) * 10,
"Thread {} should have detected allocations, got {}",
i,
count
);
count
}));
}
let mut results = Vec::new();
for handle in handles {
results.push(handle.join().unwrap());
}
for i in 1..num_threads {
assert!(
results[i] > results[i - 1],
"Thread allocation counts should be isolated and distinct, got: {:?}",
results
);
}
}
}