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}