use bumpalo::Bump;
use rand::Rng;
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicBool, Ordering};
struct Allocator(AtomicBool);
impl Allocator {
fn is_returning_null(&self) -> bool {
self.0.load(Ordering::SeqCst)
}
fn set_returning_null(&self, returning_null: bool) {
self.0.store(returning_null, Ordering::SeqCst);
}
fn toggle_returning_null(&self) {
self.set_returning_null(!self.is_returning_null());
}
#[allow(dead_code)] fn with_successful_allocs<F, T>(&self, callback: F) -> T
where
F: FnOnce() -> T,
{
let old_returning_null = self.is_returning_null();
self.set_returning_null(false);
let result = callback();
self.set_returning_null(old_returning_null);
result
}
fn with_alloc_failures<F, T>(&self, callback: F) -> T
where
F: FnOnce() -> T,
{
let old_returning_null = self.is_returning_null();
self.set_returning_null(true);
let result = callback();
self.set_returning_null(old_returning_null);
result
}
}
unsafe impl GlobalAlloc for Allocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if self.is_returning_null() {
core::ptr::null_mut()
} else {
System.alloc(layout)
}
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
System.dealloc(ptr, layout);
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
if self.is_returning_null() {
core::ptr::null_mut()
} else {
System.realloc(ptr, layout, new_size)
}
}
}
#[global_allocator]
static GLOBAL_ALLOCATOR: Allocator = Allocator(AtomicBool::new(false));
macro_rules! assert {
($cond:expr $(, $args:tt)*) => {
if !$cond {
GLOBAL_ALLOCATOR.set_returning_null(false);
panic!(concat!("Assertion failed: ", stringify!($cond)));
}
};
}
fn main() {
macro_rules! test {
($name:expr, $test:expr $(,)*) => {
($name, $test as fn())
};
}
let tests = [
test!("Bump::try_new fails when global allocator fails", || {
GLOBAL_ALLOCATOR.with_alloc_failures(|| {
assert!(Bump::try_new().is_err());
});
}),
test!(
"test try_alloc_layout with and without global allocation failures",
|| {
const NUM_TESTS: usize = 5000;
const MAX_BYTES_ALLOCATED: usize = 65536;
let mut bump = Bump::try_new().unwrap();
let mut bytes_allocated = bump.chunk_capacity();
let layout = Layout::from_size_align(bump.chunk_capacity(), 1).unwrap();
assert!(bump.try_alloc_layout(layout).is_ok());
let mut rng = rand::thread_rng();
for _ in 0..NUM_TESTS {
if rng.gen() {
GLOBAL_ALLOCATOR.toggle_returning_null();
}
let layout = Layout::from_size_align(bump.chunk_capacity() + 1, 1).unwrap();
if GLOBAL_ALLOCATOR.is_returning_null() {
assert!(bump.try_alloc_layout(layout).is_err());
} else {
assert!(bump.try_alloc_layout(layout).is_ok());
bytes_allocated += bump.chunk_capacity();
}
if bytes_allocated >= MAX_BYTES_ALLOCATED {
bump = GLOBAL_ALLOCATOR.with_successful_allocs(|| Bump::try_new().unwrap());
bytes_allocated = bump.chunk_capacity();
}
}
},
),
#[cfg(feature = "collections")]
test!("test Vec::try_reserve and Vec::try_reserve_exact", || {
use bumpalo::collections::Vec;
let bump = Bump::try_new().unwrap();
GLOBAL_ALLOCATOR.with_alloc_failures(|| {
let mut vec = Vec::<u8>::new_in(&bump);
let chunk_cap = bump.chunk_capacity();
assert!(vec.try_reserve(chunk_cap).is_ok());
assert!(vec.try_reserve_exact(chunk_cap).is_ok());
assert!(vec.try_reserve(chunk_cap + 1).is_err());
assert!(vec.try_reserve_exact(chunk_cap + 1).is_err());
});
GLOBAL_ALLOCATOR.with_successful_allocs(|| {
let mut vec = Vec::<u8>::new_in(&bump);
let chunk_cap = bump.chunk_capacity();
assert!(vec.try_reserve(chunk_cap).is_ok());
assert!(vec.try_reserve_exact(chunk_cap).is_ok());
assert!(vec.try_reserve(chunk_cap + 1).is_ok());
assert!(vec.try_reserve_exact(chunk_cap + 1).is_ok());
});
}),
];
for (name, test) in tests.iter() {
assert!(!GLOBAL_ALLOCATOR.is_returning_null());
eprintln!("=== {} ===", name);
test();
GLOBAL_ALLOCATOR.set_returning_null(false);
}
}