Skip to main content

ps_alloc/
realloc.rs

1use std::ptr::null_mut;
2
3use crate::{
4    free::free_block,
5    header::{initialize_block, layout_of, total_size, validate, AllocationHeader},
6    AllocationError, DeallocationError, ReallocationError,
7};
8
9/// Reallocates memory allocated by [`crate::alloc`].
10///
11/// Mirrors C's `realloc`:
12///
13/// - `realloc(null, new_size)` is equivalent to [`crate::alloc`]`(new_size)`.
14/// - `realloc(ptr, 0)` frees `ptr` and returns a null pointer.
15/// - Otherwise, the allocation is resized, in place when possible, and its contents are
16///   preserved up to the lesser of the old and new sizes. A `new_size` that rounds up to
17///   the allocation's current total size returns `ptr` unchanged.
18///
19/// On success, the old pointer must be considered invalid: it must not be dereferenced
20/// or passed to [`crate::free`] or this function again. Only the returned pointer may be
21/// used.
22///
23/// # Errors
24///
25/// The old pointer remains valid whenever an error is returned.
26///
27/// - `AllocationError` is returned if `ptr` is null and the allocation fails, or if
28///   `new_size` overflows the size computation or does not form a valid layout.
29/// - `DeallocationError` is returned if the size stored in the header does not form a
30///   valid layout, which signals memory corruption.
31/// - `ImproperAlignment` is returned if the pointer provided was not aligned properly.
32/// - `MarkerFree` is returned if `ptr` was previously freed or reallocated.
33/// - `MarkerCorrupted` is returned if the marker is neither free nor used, which
34///   generally signals memory corruption, or the passing of a pointer not allocated by
35///   [`crate::alloc`].
36/// - `NewAllocationFailed` is returned if the allocator failed to resize the allocation.
37///
38/// # Safety
39///
40/// See [`crate::free`] for safety information. Additionally, best-effort detection of
41/// stale pointers after a successful `realloc` is only possible when the block has
42/// moved; after an in-place resize, the old pointer aliases the live allocation and
43/// misuse cannot be detected.
44pub unsafe fn realloc(ptr: *mut u8, new_size: usize) -> Result<*mut u8, ReallocationError> {
45    if ptr.is_null() {
46        return Ok(crate::alloc(new_size)?);
47    }
48
49    // SAFETY: the caller's obligations for `realloc` include those of [`validate`].
50    let header_ptr = unsafe { validate(ptr) }?;
51
52    if new_size == 0 {
53        // This is equivalent to `free` and returns a nullptr.
54
55        // SAFETY: `validate` succeeded, so `header_ptr` satisfies `free_block`'s contract.
56        unsafe { free_block(header_ptr) }?;
57
58        return Ok(null_mut());
59    }
60
61    // SAFETY: `validate` succeeded, so `header_ptr` points to a live, initialized header.
62    let old_total = unsafe { &*header_ptr }.get_size();
63
64    let new_total = total_size(new_size)?;
65
66    if new_total == old_total {
67        // the allocation already has the requested capacity
68        return Ok(ptr);
69    }
70
71    // reconstructing the old layout can only fail if the header was corrupted
72    let old_layout = layout_of(old_total).map_err(DeallocationError::from)?;
73
74    // pre-validate the new layout so the raw realloc call below cannot be handed an
75    // invalid size
76    layout_of(new_total).map_err(AllocationError::from)?;
77
78    // SAFETY: as above; the freed marker is written before the allocator is invoked so
79    // that, if the block moves, the abandoned memory retains the freed marker for
80    // best-effort stale-pointer detection.
81    unsafe { &mut *header_ptr }.mark_as_free();
82
83    // SAFETY: `header_ptr` is the base of an allocation created with `old_layout`, and
84    // `new_total` is nonzero and forms a valid layout, as checked above.
85    let new_base = unsafe { std::alloc::realloc(header_ptr.cast(), old_layout, new_total) };
86
87    if new_base.is_null() {
88        // the old block is untouched on failure; restore its header
89        // SAFETY: `header_ptr` still points to the live header of the old block.
90        unsafe { header_ptr.write(AllocationHeader::new(old_total)) };
91
92        return Err(ReallocationError::NewAllocationFailed);
93    }
94
95    // SAFETY: `new_base` is non-null, sufficiently aligned, and valid for writes of
96    // `new_total` bytes.
97    Ok(unsafe { initialize_block(new_base, new_total) })
98}