#![allow(clippy::not_unsafe_ptr_arg_deref)]
mod error;
use std::alloc::Layout;
pub use error::*;
pub const HEADER_SIZE: usize = std::mem::size_of::<AllocationHeader>();
pub const MARKER_FREE: [u8; 8] = *b"Fr33Mmry";
pub const MARKER_USED: [u8; 8] = *b"U53dMmry";
#[repr(align(16))]
struct AllocationHeader {
marker: [u8; 8],
size: usize,
}
#[allow(clippy::cast_ptr_alignment)]
pub fn alloc(size: usize) -> Result<*mut u8, AllocationError> {
let size = size
.checked_add(HEADER_SIZE)
.ok_or(AllocationError::ArithmeticError)?
.checked_next_multiple_of(HEADER_SIZE)
.ok_or(AllocationError::ArithmeticError)?;
let layout = Layout::from_size_align(size, HEADER_SIZE)?;
let ptr = unsafe { std::alloc::alloc(layout) };
if ptr.is_null() {
return Err(AllocationError::OutOfMemory);
}
if 0 != (ptr as usize % HEADER_SIZE) {
unsafe { std::alloc::dealloc(ptr, layout) };
return Err(AllocationError::ImproperAlignment);
}
let header = unsafe { &mut *(ptr.cast::<AllocationHeader>()) };
header.marker = MARKER_USED;
header.size = size;
let ptr = unsafe { ptr.add(HEADER_SIZE) };
Ok(ptr)
}
pub fn free<T>(ptr: *mut T) -> Result<(), DeallocationError> {
if ptr.is_null() {
return Err(DeallocationError::NullPtr);
}
if 0 != ptr as usize % HEADER_SIZE {
return Err(DeallocationError::ImproperAlignment);
}
#[allow(clippy::cast_ptr_alignment)]
let header_ptr = unsafe { ptr.cast::<u8>().sub(HEADER_SIZE).cast::<AllocationHeader>() };
if !header_ptr.is_aligned() {
return Err(DeallocationError::ImproperAlignment);
}
let header = unsafe { &mut *header_ptr };
if header.marker == MARKER_FREE {
return Err(DeallocationError::DoubleFree);
} else if header.marker != MARKER_USED {
return Err(DeallocationError::InvalidAllocation);
}
let layout = Layout::from_size_align(header.size, HEADER_SIZE)?;
header.marker = MARKER_FREE;
unsafe { std::alloc::dealloc(header_ptr.cast(), layout) };
Ok(())
}
pub fn relloc(ptr: *mut u8, new_size: usize) -> Result<*mut u8, ReallocationError> {
if 0 == new_size {
if !ptr.is_null() {
free(ptr)?;
}
return Ok(std::ptr::null_mut());
}
if ptr.is_null() {
return Ok(alloc(new_size)?);
}
if 0 != ptr as usize % HEADER_SIZE {
return Err(ReallocationError::ImproperAlignment);
}
#[allow(clippy::cast_ptr_alignment)]
let header_ptr = unsafe { ptr.sub(HEADER_SIZE) }.cast::<AllocationHeader>();
if !header_ptr.is_aligned() {
return Err(ReallocationError::ImproperAlignment);
}
let header = unsafe { &*header_ptr };
if header.marker == MARKER_FREE {
return Err(ReallocationError::UseAfterFree);
} else if header.marker != MARKER_USED {
return Err(ReallocationError::InvalidPointer);
}
let new_ptr = alloc(new_size)?;
unsafe {
std::ptr::copy_nonoverlapping::<u8>(ptr, new_ptr, header.size.min(new_size));
}
match free(ptr) {
Ok(()) => Ok(new_ptr),
Err(err) => match free(new_ptr) {
Ok(()) => Err(err)?,
Err(err2) => Err(ReallocationError::FreeFailedTwice(err, err2)),
},
}
}