ps-alloc 0.1.0-8

a reasonably safe allocator
Documentation
use std::ptr::{copy_nonoverlapping, null_mut};

use crate::{
    header::{AllocationHeader, HEADER_SIZE},
    ReallocationError,
};

/// Reallocates memory allocated by [`crate::alloc`].
///
/// # Errors
///
/// - `AllocationError` is returned if the call to [`crate::alloc`] fails.
/// - `DeallocationError` is returned if the call to [`crate::free`] fails.
/// - `ImproperAlignment` is returned if the pointer provided was not aligned properly.
/// - `MarkerFree` is returned if `ptr` was previously freed or reallocated.
/// - `MarkerCorrupted` is returned if the marker is neither free nor used, which generally signals memory corruption, or the passing of a pointer not allocated by [`crate::alloc`].
/// - `NewAllocationFailed` is returned if allocating the new memory buffer fails. This is the only error which is fully recoverable, as your current allocation remains valid.
///
/// # Safety
///
/// See [`crate::free`] for safety information.
pub unsafe fn realloc(ptr: *mut u8, new_size: usize) -> Result<*mut u8, ReallocationError> {
    if ptr.is_null() {
        return Ok(crate::alloc(new_size)?);
    }

    if !(ptr as usize).is_multiple_of(HEADER_SIZE) {
        return Err(ReallocationError::ImproperAlignment);
    }

    let header_ptr: *mut AllocationHeader = ptr.sub(HEADER_SIZE).cast();

    if !header_ptr.is_aligned() {
        return Err(ReallocationError::ImproperAlignment);
    }

    if (*header_ptr).is_marked_as_free() {
        return Err(ReallocationError::MarkerFree);
    }

    if !(*header_ptr).is_marked_as_used() {
        return Err(ReallocationError::MarkerCorrupted);
    }

    if new_size == 0 {
        // This is equivalent to `free` and returns a nullptr.

        crate::free(ptr)?;

        return Ok(null_mut());
    }

    let Ok(new_ptr) = crate::alloc(new_size) else {
        return Err(ReallocationError::NewAllocationFailed(ptr));
    };

    let memset_len = (*header_ptr)
        // full allocation size, including header
        .get_size()
        // don't copy the header
        .saturating_sub(HEADER_SIZE)
        // truncate if needed
        .min(new_size);

    // copy data to new location
    copy_nonoverlapping(ptr, new_ptr, memset_len);

    // the error branch is unlikely here, since we've already checked the pointer's validity
    match crate::free(ptr) {
        Ok(()) => {}
        Err(err) => {
            // clean up new allocation
            crate::free(new_ptr)?;

            // pass the error
            Err(err)?;
        }
    }

    Ok(new_ptr)
}