Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.9.4] - 2026-02-06

### Changed

- `Dealloc` traits' fallible functions now treat zero-sized layouts and dangling pointers as a hard
error
- `Dealloc` traits' fallible functions are now a noop for ZSLs and dangling pointers
- All other allocation functions now treat ZSLs as an error.

## [0.9.2] - 2026-02-03

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "memapi2"
version = "0.9.2"
version = "0.9.4"
edition = "2018"
rust-version = "1.46.0"
authors = ["echohumm"]
Expand Down
52 changes: 27 additions & 25 deletions src/allocs/c_alloc.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
use {
crate::{
error::Error,
ffi::c_alloc::{c_alloc, c_dealloc, c_zalloc, grow_aligned, shrink_aligned},
helpers::null_q_dyn_zsl_check,
Alloc,
Dealloc,
Grow,
Layout,
Realloc,
Shrink,
error::Error,
ffi::c_alloc::{c_alloc, c_dealloc, c_zalloc, grow_aligned, shrink_aligned},
helpers::{null_q_dyn, null_q_dyn_zsl_check}
Shrink
},
core::{cmp::Ordering, ffi::c_void, ptr::NonNull}
};

// TODO: we should use the builtin malloc and realloc if align <= guaranteed align

#[cfg_attr(miri, track_caller)]
fn pad_then_alloc(
layout: Layout,
alloc: unsafe fn(usize, usize) -> *mut c_void
) -> Result<NonNull<u8>, Error> {
let l = tri!(do layout.to_aligned_alloc_compatible());
let padded = tri!(do layout.to_aligned_alloc_compatible());
null_q_dyn_zsl_check(
layout,
// SAFETY: we rounded up the layout's values to satisfy the requirements.
|_| unsafe { alloc(l.align(), l.size()) }
|_| unsafe { alloc(padded.align(), padded.size()) }
)
}

Expand All @@ -42,8 +41,14 @@ unsafe fn pad_then_grow(
return Err(Error::GrowSmallerNewLayout(old_layout.size(), new_layout.size()));
}

null_q_dyn_zsl_check(new_padded, |l| {
grow_aligned(ptr.as_ptr().cast(), old_padded.size(), l.align(), l.size(), alloc)
null_q_dyn_zsl_check(new_layout, |_| {
grow_aligned(
ptr.as_ptr().cast(),
old_padded.size(),
new_padded.align(),
new_padded.size(),
alloc
)
})
}

Expand All @@ -57,13 +62,13 @@ unsafe fn pad_then_realloc(
let old_padded = tri!(do old_layout.to_aligned_alloc_compatible());
let new_padded = tri!(do new_layout.to_aligned_alloc_compatible());

null_q_dyn_zsl_check(new_padded, |l| {
null_q_dyn_zsl_check(new_layout, |_| {
let old_ptr = ptr.as_ptr().cast();
let old_size = old_padded.size();
let old_align = old_padded.align();

let size = l.size();
let align = l.align();
let size = new_padded.size();
let align = new_padded.align();

match old_size.cmp(&new_padded.size()) {
// SAFETY: caller guarantees that `old_ptr` and `old_size` are valid, we just
Expand Down Expand Up @@ -110,18 +115,16 @@ impl Alloc for CAlloc {
impl Dealloc for CAlloc {
#[cfg_attr(miri, track_caller)]
#[inline]
unsafe fn dealloc(&self, ptr: NonNull<u8>, layout: Layout) {
if layout.is_nonzero_sized() {
unsafe fn try_dealloc(&self, ptr: NonNull<u8>, layout: Layout) -> Result<(), Error> {
if layout.is_zero_sized() {
Err(Error::ZeroSizedLayout)
} else if ptr == layout.dangling() {
Err(Error::DanglingDeallocation)
} else {
c_dealloc(ptr.as_ptr().cast());
Ok(())
}
}

#[cfg_attr(miri, track_caller)]
#[inline]
unsafe fn try_dealloc(&self, ptr: NonNull<u8>, layout: Layout) -> Result<(), Error> {
self.dealloc(ptr, layout);
Ok(())
}
}
impl Grow for CAlloc {
#[cfg_attr(miri, track_caller)]
Expand Down Expand Up @@ -158,10 +161,9 @@ impl Shrink for CAlloc {
return Err(Error::ShrinkLargerNewLayout(old_layout.size(), new_layout.size()));
}

null_q_dyn(
shrink_aligned(ptr.as_ptr().cast(), new_padded.align(), new_padded.size()),
new_padded
)
null_q_dyn_zsl_check(new_layout, |_| {
shrink_aligned(ptr.as_ptr().cast(), new_padded.align(), new_padded.size())
})
}
}
impl Realloc for CAlloc {
Expand Down
16 changes: 15 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ pub enum Error {
AllocFailed(Layout, Cause),
/// The layout computed with the given size and alignment is invalid; see the contained reason.
InvalidLayout(usize, usize, LayoutErr),
/// A zero-sized allocation was requested. This is treated as an error as several allocators do
/// not support such requests or respond to them strangely.
///
/// In most reasonable cases, [`layout.dangling()`](Layout::dangling) can and should be used
/// instead.
ZeroSizedLayout,
/// An attempt was made to deallocate a dangling pointer.
DanglingDeallocation,
/// Attempted to grow to a smaller size.
GrowSmallerNewLayout(usize, usize),
/// Attempted to shrink to a larger size.
Expand All @@ -45,10 +53,12 @@ impl Display for Error {
AllocFailed,
ArithmeticError,
CaughtUnwind,
DanglingDeallocation,
GrowSmallerNewLayout,
InvalidLayout,
Other,
ShrinkLargerNewLayout
ShrinkLargerNewLayout,
ZeroSizedLayout
};

match self {
Expand All @@ -64,6 +74,10 @@ impl Display for Error {
"computed invalid layout:\n\tsize: {}\n\talign: {}\n\treason: {}",
sz, aln, e
),
ZeroSizedLayout => {
write!(f, "received a zero-sized layout")
}
DanglingDeallocation => write!(f, "attempted to deallocate a dangling pointer"),
GrowSmallerNewLayout(old, new) => {
write!(f, "attempted to grow from a size of {} to a smaller size of {}", old, new)
}
Expand Down
6 changes: 0 additions & 6 deletions src/ffi/c/calloca.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,6 @@ void c_alloca(
void* closure,
void* out
) {
if (size == 0) {
/* return dangling for zsl */
callback(closure, (uint8_t*)(uintptr_t)align, out);
return;
}

/* determine whether we need extra space for padding */
size_t alloc_size = size;
size_t align_m_1;
Expand Down
9 changes: 0 additions & 9 deletions src/ffi/c_alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,15 +171,6 @@ pub unsafe fn shrink_aligned(
align: usize,
size: usize // a memset-ing alloc here is useless, as it will just be overwritten anyway.
) -> *mut c_void {
// fast path if size is 0, just free and return dangling
if size == 0 {
// SAFETY: caller guarantees that `old_ptr` is valid
unsafe {
c_dealloc(old_ptr);
}
return align as *mut c_void;
}

// allocate new aligned memory
// SAFETY: requirements are passed on to the caller
let ptr = unsafe { c_alloc(align, size) };
Expand Down
7 changes: 5 additions & 2 deletions src/ffi/stack_alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,16 @@ thread_local! {
/// - If `layout.size() == 0`, `f` must treat the pointer as a [`dangling`](core::ptr::dangling)
/// pointer.
/// - `f` must initialize the value behind its second parameter before returning.
/// - `f` must properly handle the case where <code>[layout.size()](Layout::size) == 0</code> and it
/// receives a [`dangling`](core::ptr::dangling) pointer.
/// - On Rust versions below `1.71` with `catch_unwind` disabled, `f` must never unwind.
pub unsafe fn with_alloca<R, F: FnOnce(NonNull<u8>, *mut R)>(
layout: Layout,
f: F
) -> Result<R, Error> {
// TODO: maybe i should just make F take a Result instead and not skip running it on error
if layout.size() == 0 {
return Err(Error::ZeroSizedLayout);
}

let mut ret = MaybeUninit::uninit();
let mut closure = ManuallyDrop::new(f);

Expand Down
3 changes: 1 addition & 2 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,9 +365,8 @@ pub fn null_q_dyn_zsl_check<T, F: Fn(Layout) -> *mut T>(
layout: Layout,
f: F
) -> Result<NonNull<u8>, Error> {
if layout.is_zero_sized() { Ok(layout.dangling()) } else { null_q_dyn(f(layout), layout) }
if layout.is_zero_sized() { Err(Error::ZeroSizedLayout) } else { null_q_dyn(f(layout), layout) }
}

// TODO: lower const msrv and generally improve these. will require some testing regarding effects
// of current and alternative implementations on provenance
#[rustversion::since(1.75)]
Expand Down
12 changes: 9 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ macro_rules! default_alloc_impl {
#[cfg_attr(miri, track_caller)]
#[inline(always)]
unsafe fn dealloc(&self, ptr: core::ptr::NonNull<u8>, layout: Layout) {
if layout.is_nonzero_sized() {
if layout.is_nonzero_sized() && ptr != layout.dangling() {
alloc::alloc::dealloc(ptr.as_ptr(), layout.to_stdlib());
}
}
Expand All @@ -191,8 +191,14 @@ macro_rules! default_alloc_impl {
ptr: core::ptr::NonNull<u8>,
layout: Layout
) -> Result<(), crate::error::Error> {
self.dealloc(ptr, layout);
Ok(())
if layout.is_zero_sized() {
Err(crate::error::Error::ZeroSizedLayout)
} else if ptr == layout.dangling() {
Err(crate::error::Error::DanglingDeallocation)
} else {
alloc::alloc::dealloc(ptr.as_ptr(), layout.to_stdlib());
Ok(())
}
}
}
#[cfg(not(feature = "no_alloc"))]
Expand Down
Loading
Loading