#![allow(dead_code, reason = "shared between multiple test binaries; some helpers may be unused per-file")]
use core::alloc::Layout;
use core::cell::Cell;
use core::hash::{Hash, Hasher};
use core::ptr::NonNull;
use std::collections::hash_map::DefaultHasher;
use allocator_api2::alloc::{AllocError, Allocator, Global};
pub(crate) fn hash_of<T: Hash>(v: &T) -> u64 {
let mut h = DefaultHasher::new();
v.hash(&mut h);
h.finish()
}
#[derive(Clone)]
pub(crate) struct FailingAllocator {
remaining: std::rc::Rc<Cell<usize>>,
}
impl FailingAllocator {
pub(crate) fn new(allow_n_allocs: usize) -> Self {
Self {
remaining: std::rc::Rc::new(Cell::new(allow_n_allocs)),
}
}
}
unsafe impl Allocator for FailingAllocator {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
let r = self.remaining.get();
if r == 0 {
return Err(AllocError);
}
self.remaining.set(r - 1);
Global.allocate(layout)
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
unsafe { Global.deallocate(ptr, layout) };
}
}
#[derive(Clone)]
pub(crate) struct TrackingAllocator {
live_chunks: std::rc::Rc<Cell<isize>>,
live_bytes: std::rc::Rc<Cell<isize>>,
}
impl TrackingAllocator {
pub(crate) fn new() -> Self {
Self {
live_chunks: std::rc::Rc::new(Cell::new(0)),
live_bytes: std::rc::Rc::new(Cell::new(0)),
}
}
pub(crate) fn live_chunks(&self) -> isize {
self.live_chunks.get()
}
pub(crate) fn live_bytes(&self) -> isize {
self.live_bytes.get()
}
}
unsafe impl Allocator for TrackingAllocator {
#[expect(clippy::cast_possible_wrap, reason = "test allocator: chunk sizes fit in isize")]
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
let p = Global.allocate(layout)?;
self.live_chunks.set(self.live_chunks.get() + 1);
self.live_bytes.set(self.live_bytes.get() + layout.size() as isize);
Ok(p)
}
#[expect(clippy::cast_possible_wrap, reason = "test allocator: chunk sizes fit in isize")]
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
unsafe { Global.deallocate(ptr, layout) };
self.live_chunks.set(self.live_chunks.get() - 1);
self.live_bytes.set(self.live_bytes.get() - layout.size() as isize);
}
}
#[derive(Clone)]
pub(crate) struct SendTrackingAllocator {
live_chunks: std::sync::Arc<core::sync::atomic::AtomicIsize>,
live_bytes: std::sync::Arc<core::sync::atomic::AtomicIsize>,
}
impl SendTrackingAllocator {
pub(crate) fn new() -> Self {
Self {
live_chunks: std::sync::Arc::new(core::sync::atomic::AtomicIsize::new(0)),
live_bytes: std::sync::Arc::new(core::sync::atomic::AtomicIsize::new(0)),
}
}
pub(crate) fn live_chunks(&self) -> isize {
self.live_chunks.load(core::sync::atomic::Ordering::Relaxed)
}
pub(crate) fn live_bytes(&self) -> isize {
self.live_bytes.load(core::sync::atomic::Ordering::Relaxed)
}
}
unsafe impl Allocator for SendTrackingAllocator {
#[expect(clippy::cast_possible_wrap, reason = "test allocator: chunk sizes fit in isize")]
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
let p = Global.allocate(layout)?;
self.live_chunks.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
self.live_bytes
.fetch_add(layout.size() as isize, core::sync::atomic::Ordering::Relaxed);
Ok(p)
}
#[expect(clippy::cast_possible_wrap, reason = "test allocator: chunk sizes fit in isize")]
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
unsafe { Global.deallocate(ptr, layout) };
self.live_chunks.fetch_sub(1, core::sync::atomic::Ordering::Relaxed);
self.live_bytes
.fetch_sub(layout.size() as isize, core::sync::atomic::Ordering::Relaxed);
}
}
#[derive(Clone, Copy, Default)]
pub(crate) struct BadAddressAllocator;
unsafe impl Allocator for BadAddressAllocator {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
let size = layout.size();
let align = layout.align();
if size == 0 || align == 0 {
return Err(AllocError);
}
let target_end_floor = 1usize << (usize::BITS - 1); let unaligned_start = target_end_floor.checked_sub(size).ok_or(AllocError)?;
let mask = align - 1;
let start_addr = unaligned_start.checked_add(mask).ok_or(AllocError)? & !mask;
let nn = unsafe { NonNull::new_unchecked(core::ptr::without_provenance_mut::<u8>(start_addr)) };
Ok(NonNull::slice_from_raw_parts(nn, size))
}
unsafe fn deallocate(&self, _ptr: NonNull<u8>, _layout: Layout) {
}
}
#[derive(Clone)]
pub(crate) struct SendFailingAllocator {
remaining: std::sync::Arc<core::sync::atomic::AtomicUsize>,
}
impl SendFailingAllocator {
pub(crate) fn new(allow_n_allocs: usize) -> Self {
Self {
remaining: std::sync::Arc::new(core::sync::atomic::AtomicUsize::new(allow_n_allocs)),
}
}
}
unsafe impl Allocator for SendFailingAllocator {
fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
use core::sync::atomic::Ordering;
loop {
let r = self.remaining.load(Ordering::Relaxed);
if r == 0 {
return Err(AllocError);
}
if self
.remaining
.compare_exchange(r, r - 1, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
return Global.allocate(layout);
}
}
}
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
unsafe { Global.deallocate(ptr, layout) };
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Droppy(pub &'static str);
std::thread_local! {
static DROPPY_LOG: Cell<Option<Vec<&'static str>>> = const { Cell::new(None) };
}
impl Drop for Droppy {
fn drop(&mut self) {
DROPPY_LOG.with(|c| {
let mut v = c.take().unwrap_or_default();
v.push(self.0);
c.set(Some(v));
});
}
}
impl Droppy {
pub(crate) fn take_log() -> Vec<&'static str> {
DROPPY_LOG.with(|c| c.take().unwrap_or_default())
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct DropCounter(pub std::sync::Arc<core::sync::atomic::AtomicUsize>);
impl DropCounter {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn count(&self) -> usize {
self.0.load(core::sync::atomic::Ordering::Relaxed)
}
}
impl Drop for DropCounter {
fn drop(&mut self) {
self.0.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
}