ps_alloc/
lib.rs

1mod error;
2
3use std::alloc::Layout;
4
5pub use error::*;
6
7pub const ALIGNMENT: usize = std::mem::size_of::<Allocation>();
8pub const MARKER_FREE: [u8; 8] = *b"Fr33Mmry";
9pub const MARKER_USED: [u8; 8] = *b"U53dMmry";
10
11#[repr(align(16))]
12struct Allocation {
13    marker: [u8; 8],
14    size: usize,
15}
16
17/// - A reasonably safe implementation of `alloc`.
18/// - Memory allocated by this function must be freed by this crate's `free`.
19/// - Caller guarantees `free` is called before the returned pointer goes out of scope.
20/// # Errors
21/// - `Err(ArithmeticError)` on integer overflow.
22/// - `Err(ImproperAlignment)` if the global allocator returns a misaligned pointer.
23/// - `Err(LayoutError)` if [`ALIGNMENT`] isn't a power of 2 or the computed size is not aligned.
24/// - `Err(OutOfMemory)` if `alloc()` returns a `nullptr`.
25#[allow(clippy::cast_ptr_alignment)]
26pub fn alloc(size: usize) -> Result<*mut u8, AllocationError> {
27    let size = size
28        .checked_add(ALIGNMENT)
29        .ok_or(AllocationError::ArithmeticError)?
30        .checked_next_multiple_of(ALIGNMENT)
31        .ok_or(AllocationError::ArithmeticError)?;
32
33    let layout = Layout::from_size_align(size, ALIGNMENT)?;
34
35    let ptr = unsafe { std::alloc::alloc(layout) };
36
37    if ptr.is_null() {
38        return Err(AllocationError::OutOfMemory);
39    }
40
41    if 0 != (ptr as usize % ALIGNMENT) {
42        unsafe { std::alloc::dealloc(ptr, layout) };
43
44        return Err(AllocationError::ImproperAlignment);
45    }
46
47    let allocation = unsafe { &mut *(ptr.cast::<Allocation>()) };
48
49    allocation.marker = MARKER_USED;
50    allocation.size = size;
51
52    let ptr = unsafe { ptr.add(ALIGNMENT) };
53
54    Ok(ptr)
55}
56
57/// - A reasonably safe implementation of `free`.
58/// - This function will free a pointer allocated by `alloc`.
59/// - Caller guarantees that the provided pointer was allocated by this crate's `alloc` function.
60/// - Providing `NULL` is safe and will return `Err(DeallocationError::NullPtr)`.
61/// - Providing any other pointer causes undefined behaviour.
62/// # Errors
63/// - Returns `Err(DeallocationError)` if a safety check fails.
64pub fn free<T>(ptr: *mut T) -> Result<(), DeallocationError> {
65    if ptr.is_null() {
66        return Err(DeallocationError::NullPtr);
67    }
68
69    let header_ptr = unsafe { ptr.cast::<u8>().sub(ALIGNMENT).cast::<Allocation>() };
70
71    if !header_ptr.is_aligned() {
72        return Err(DeallocationError::ImproperAlignment);
73    }
74
75    let allocation = unsafe { &mut *header_ptr };
76
77    if allocation.marker == MARKER_FREE {
78        return Err(DeallocationError::DoubleFree);
79    } else if allocation.marker != MARKER_USED {
80        return Err(DeallocationError::InvalidAllocation);
81    }
82
83    let layout = Layout::from_size_align(allocation.size, ALIGNMENT)?;
84
85    allocation.marker = MARKER_FREE;
86
87    unsafe { std::alloc::dealloc(header_ptr.cast(), layout) };
88
89    Ok(())
90}