Skip to main content

ps_alloc/
free.rs

1use crate::{
2    header::{layout_of, validate, AllocationHeader},
3    DeallocationError,
4};
5
6/// This function is the counterpart to [`crate::alloc`].
7///
8/// Every pointer produced by [`crate::alloc`] or [`crate::realloc`] must eventually be
9/// released with this function or [`crate::realloc`]. Unlike C's `free`, passing a null
10/// pointer returns an error instead of being a no-op.
11///
12/// # Errors
13///
14/// An error is returned if a safety check fails.
15///
16/// # Safety
17///
18/// Only pointers produced by [`crate::alloc`] or [`crate::realloc`] may be freed using
19/// this function; passing any other pointer causes undefined behaviour. The safety
20/// checks are best-effort: any error other than `NullPtr` indicates that undefined
21/// behaviour has already occurred, and the checks are performed non-atomically, so
22/// concurrent misuse from another thread may go undetected. Unless `ptr` is misaligned
23/// or null, **this function will dereference it**.
24pub unsafe fn free(ptr: *mut u8) -> Result<(), DeallocationError> {
25    if ptr.is_null() {
26        return Err(DeallocationError::NullPtr);
27    }
28
29    // SAFETY: the caller's obligations for `free` include those of [`validate`].
30    let header_ptr = unsafe { validate(ptr) }?;
31
32    // SAFETY: `validate` succeeded, so `header_ptr` satisfies `free_block`'s contract.
33    unsafe { free_block(header_ptr) }
34}
35
36/// Deallocates the already-validated block whose header is at `header_ptr`.
37///
38/// # Errors
39/// - `Err(LayoutError)` if the stored size does not form a valid layout.
40///
41/// # Safety
42/// `header_ptr` must point to the live, initialized header of a used allocation produced
43/// by this crate, as returned by [`validate`].
44pub(crate) unsafe fn free_block(
45    header_ptr: *mut AllocationHeader,
46) -> Result<(), DeallocationError> {
47    // SAFETY: `header_ptr` points to a live, initialized header, per this function's
48    // contract.
49    let layout = layout_of(unsafe { &*header_ptr }.get_size())?;
50
51    // SAFETY: as above; the freed marker is written before `dealloc` so a later free of
52    // the same pointer can be detected on a best-effort basis.
53    unsafe { &mut *header_ptr }.mark_as_free();
54
55    // SAFETY: `header_ptr` is the base of a live allocation, per this function's
56    // contract, and `layout` matches the layout it was allocated with, since it is
57    // reconstructed from the total size stored in the header.
58    unsafe { std::alloc::dealloc(header_ptr.cast(), layout) }
59
60    Ok(())
61}