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}