From 31e3709872c47e2af658af917762c0d3eaeec2b5 Mon Sep 17 00:00:00 2001 From: echohumm Date: Fri, 6 Feb 2026 12:35:12 -0700 Subject: [PATCH 1/3] make zsls a hard error --- CHANGELOG.md | 7 +++ src/allocs/c_alloc.rs | 44 +++++++------- src/error.rs | 16 ++++- src/ffi/c/calloca.c | 6 -- src/ffi/c_alloc.rs | 9 --- src/ffi/stack_alloc.rs | 7 ++- src/helpers.rs | 3 +- src/lib.rs | 12 +++- src/traits/alloc.rs | 66 +++++++++++---------- src/traits/alloc_mut.rs | 123 +++++++++++++++++++++------------------ src/traits/alloc_temp.rs | 22 ++----- src/traits/helpers.rs | 4 +- tests/alloc_mut.rs | 4 +- tests/helpers.rs | 5 +- 14 files changed, 173 insertions(+), 155 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1323020..85b5b0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### 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 ### Fixed diff --git a/src/allocs/c_alloc.rs b/src/allocs/c_alloc.rs index 1e3e975..0f940ab 100644 --- a/src/allocs/c_alloc.rs +++ b/src/allocs/c_alloc.rs @@ -12,7 +12,6 @@ use { }, 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)] @@ -20,11 +19,11 @@ fn pad_then_alloc( layout: Layout, alloc: unsafe fn(usize, usize) -> *mut c_void ) -> Result, 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()) } ) } @@ -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 + ) }) } @@ -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 @@ -110,18 +115,16 @@ impl Alloc for CAlloc { impl Dealloc for CAlloc { #[cfg_attr(miri, track_caller)] #[inline] - unsafe fn dealloc(&self, ptr: NonNull, layout: Layout) { - if layout.is_nonzero_sized() { + unsafe fn try_dealloc(&self, ptr: NonNull, 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, layout: Layout) -> Result<(), Error> { - self.dealloc(ptr, layout); - Ok(()) - } } impl Grow for CAlloc { #[cfg_attr(miri, track_caller)] @@ -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 { diff --git a/src/error.rs b/src/error.rs index 1ad5aaa..6511560 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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. @@ -45,10 +53,12 @@ impl Display for Error { AllocFailed, ArithmeticError, CaughtUnwind, + DanglingDeallocation, GrowSmallerNewLayout, InvalidLayout, Other, - ShrinkLargerNewLayout + ShrinkLargerNewLayout, + ZeroSizedLayout }; match self { @@ -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) } diff --git a/src/ffi/c/calloca.c b/src/ffi/c/calloca.c index f402295..4e85f4e 100644 --- a/src/ffi/c/calloca.c +++ b/src/ffi/c/calloca.c @@ -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; diff --git a/src/ffi/c_alloc.rs b/src/ffi/c_alloc.rs index 62af59e..2aae63c 100644 --- a/src/ffi/c_alloc.rs +++ b/src/ffi/c_alloc.rs @@ -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) }; diff --git a/src/ffi/stack_alloc.rs b/src/ffi/stack_alloc.rs index ccd44b4..8301493 100644 --- a/src/ffi/stack_alloc.rs +++ b/src/ffi/stack_alloc.rs @@ -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 [layout.size()](Layout::size) == 0 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, *mut R)>( layout: Layout, f: F ) -> Result { + // 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); diff --git a/src/helpers.rs b/src/helpers.rs index 950cfc5..9d9618f 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -365,9 +365,8 @@ pub fn null_q_dyn_zsl_check *mut T>( layout: Layout, f: F ) -> Result, 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)] diff --git a/src/lib.rs b/src/lib.rs index 4a3208d..e25f9fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -179,7 +179,7 @@ macro_rules! default_alloc_impl { #[cfg_attr(miri, track_caller)] #[inline(always)] unsafe fn dealloc(&self, ptr: core::ptr::NonNull, layout: Layout) { - if layout.is_nonzero_sized() { + if layout.is_nonzero_sized() && ptr != layout.dangling() { alloc::alloc::dealloc(ptr.as_ptr(), layout.to_stdlib()); } } @@ -191,8 +191,14 @@ macro_rules! default_alloc_impl { ptr: core::ptr::NonNull, 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"))] diff --git a/src/traits/alloc.rs b/src/traits/alloc.rs index d276d5f..4304da9 100644 --- a/src/traits/alloc.rs +++ b/src/traits/alloc.rs @@ -20,9 +20,6 @@ pub trait Alloc { /// Attempts to allocate a block of memory fitting the given [`Layout`]. /// - /// Returns a [`dangling`](ptr::dangling) pointer if [layout.size()](Layout::size) == - /// 0. - /// /// # Errors /// /// Errors are implementation-defined, refer to [`Alloc::Error`] and [`Error`]. @@ -32,6 +29,8 @@ pub trait Alloc { /// fails. `cause` is typically [`Cause::Unknown`]. If the `os_err_reporting` feature is /// enabled, it will be [Cause::OSErr]\(oserr\). In this case, `oserr` will be /// the error from [last_os_error]\(\).[raw_os_error]\(\). + /// - Err([Error::ZeroSizedLayout]) if [layout.size()](Layout::size) == + /// 0. /// /// [last_os_error]: std::io::Error::last_os_error /// [raw_os_error]: std::io::Error::raw_os_error @@ -39,9 +38,6 @@ pub trait Alloc { /// Attempts to allocate a zeroed block of memory fitting the given [`Layout`]. /// - /// Returns a [`dangling`](ptr::dangling) pointer if [layout.size()](Layout::size) == - /// 0. - /// /// # Errors /// /// Errors are implementation-defined, refer to [`Alloc::Error`] and [`Error`]. @@ -51,6 +47,8 @@ pub trait Alloc { /// fails. `cause` is typically [`Cause::Unknown`]. If the `os_err_reporting` feature is /// enabled, it will be [Cause::OSErr]\(oserr\). In this case, `oserr` will be /// the error from [last_os_error]\(\).[raw_os_error]\(\). + /// - Err([Error::ZeroSizedLayout]) if [layout.size()](Layout::size) == + /// 0. /// /// [last_os_error]: std::io::Error::last_os_error /// [raw_os_error]: std::io::Error::raw_os_error @@ -65,7 +63,8 @@ pub trait Alloc { pub trait Dealloc: Alloc { /// Deallocates a previously allocated block. /// - /// This is a noop if [layout.size()](Layout::size) == 0. + /// This is a noop if [layout.size()](Layout::size) == 0 or `ptr` is + /// [`dangling`](ptr::dangling). /// /// The default implementation simply calls [`try_dealloc`](Dealloc::try_dealloc) and panics if /// it returns an error. @@ -79,12 +78,15 @@ pub trait Dealloc: Alloc { /// # Panics /// /// This function may panic if the [`try_dealloc`](Dealloc::try_dealloc) implementation returns - /// an error, or the implementation chooses to panic for any other reason. + /// an error, or the implementation chooses to panic for any other reason. It will not panic if + /// `ptr` is [`dangling`](ptr::dangling) or [layout.size()](Layout::size) == 0. #[track_caller] #[inline] unsafe fn dealloc(&self, ptr: NonNull, layout: Layout) { - if let Err(e) = self.try_dealloc(ptr, layout) { - default_dealloc_panic(ptr, layout, e) + if layout.is_nonzero_sized() && ptr != layout.dangling() { + if let Err(e) = self.try_dealloc(ptr, layout) { + default_dealloc_panic(ptr, layout, e) + } } } @@ -92,8 +94,6 @@ pub trait Dealloc: Alloc { /// allocation library which does not provide fallible deallocation operations, this may panic, /// abort, or incorrectly return `Ok(())`. /// - /// This is a noop if [layout.size()](Layout::size) == 0. - /// /// # Safety /// /// The caller must ensure: @@ -104,8 +104,11 @@ pub trait Dealloc: Alloc { /// /// Errors are implementation-defined, refer to [`Alloc::Error`] and [`Error`]. /// - /// The standard implementations do not return any errors, as the library functions backing them - /// are infallible. + /// The standard implementations may return: + /// - Err([Error::ZeroSizedLayout]) if [layout.size()](Layout::size) == + /// 0. + /// - Err([Error::DanglingDeallocation]) if ptr == + /// [layout.dangling](Layout::dangling). /// /// However, if the `alloc_mut` feature is enabled, and using this method on a synchronization /// primitive wrapping a type which implements [`AllocMut`](crate::AllocMut), an @@ -146,6 +149,8 @@ pub trait Grow: Alloc + Dealloc { /// - Err([Error::GrowSmallerNewLayout]\([old_layout.size()](Layout::size), /// [new_layout.size()](Layout::size))\) if [old_layout.size()](Layout::size) > /// [new_layout.size()](Layout::size). + /// - Err([Error::ZeroSizedLayout]) if [layout.size()](Layout::size) == + /// 0. /// /// [last_os_error]: std::io::Error::last_os_error /// [raw_os_error]: std::io::Error::raw_os_error @@ -190,6 +195,8 @@ pub trait Grow: Alloc + Dealloc { /// - Err([Error::GrowSmallerNewLayout]\([old_layout.size()](Layout::size), /// [new_layout.size()](Layout::size))\) if [old_layout.size()](Layout::size) > /// [new_layout.size()](Layout::size). + /// - Err([Error::ZeroSizedLayout]) if [layout.size()](Layout::size) == + /// 0. /// /// [last_os_error]: std::io::Error::last_os_error /// [raw_os_error]: std::io::Error::raw_os_error @@ -211,9 +218,6 @@ pub trait Shrink: Alloc + Dealloc { /// /// On failure, the original memory will not be deallocated. /// - /// Returns a [`dangling`](ptr::dangling) pointer if [layout.size()](Layout::size) == - /// 0. - /// /// Note that the default implementation simply: /// 1. Checks that the new layout is smaller or the same size. If both layouts are the same, /// `ptr` is returned and no operation is performed. @@ -241,6 +245,8 @@ pub trait Shrink: Alloc + Dealloc { /// - Err([Error::ShrinkLargerNewLayout]\([old_layout.size()](Layout::size), /// [new_layout.size()](Layout::size))\) if [old_layout.size()](Layout::size) < /// [new_layout.size()](Layout::size). + /// - Err([Error::ZeroSizedLayout]) if [layout.size()](Layout::size) == + /// 0. /// /// [last_os_error]: std::io::Error::last_os_error /// [raw_os_error]: std::io::Error::raw_os_error @@ -277,9 +283,6 @@ pub trait Realloc: Grow + Shrink { /// /// On failure, the original memory will not be deallocated. /// - /// Returns a [`dangling`](ptr::dangling) pointer if [layout.size()](Layout::size) == - /// 0. - /// /// # Safety /// /// The caller must ensure: @@ -295,6 +298,8 @@ pub trait Realloc: Grow + Shrink { /// fails. `cause` is typically [`Cause::Unknown`]. If the `os_err_reporting` feature is /// enabled, it will be [Cause::OSErr]\(oserr\). In this case, `oserr` will be /// the error from [last_os_error]\(\).[raw_os_error]\(\). + /// - Err([Error::ZeroSizedLayout]) if [layout.size()](Layout::size) == + /// 0. /// /// [last_os_error]: std::io::Error::last_os_error /// [raw_os_error]: std::io::Error::raw_os_error @@ -316,9 +321,6 @@ pub trait Realloc: Grow + Shrink { /// /// On failure, the original memory will not be deallocated. /// - /// Returns a [`dangling`](ptr::dangling) pointer if [layout.size()](Layout::size) == - /// 0. - /// /// # Safety /// /// The caller must ensure: @@ -334,6 +336,8 @@ pub trait Realloc: Grow + Shrink { /// fails. `cause` is typically [`Cause::Unknown`]. If the `os_err_reporting` feature is /// enabled, it will be [Cause::OSErr]\(oserr\). In this case, `oserr` will be /// the error from [last_os_error]\(\).[raw_os_error]\(\). + /// - Err([Error::ZeroSizedLayout]) if [layout.size()](Layout::size) == + /// 0. /// /// [last_os_error]: std::io::Error::last_os_error /// [raw_os_error]: std::io::Error::raw_os_error @@ -497,18 +501,16 @@ impl Alloc for std::alloc::System { } #[cfg(all(feature = "std", not(feature = "no_alloc")))] impl Dealloc for std::alloc::System { - #[cfg_attr(miri, track_caller)] - #[inline] - unsafe fn dealloc(&self, ptr: NonNull, layout: Layout) { - if layout.is_nonzero_sized() { + unsafe fn try_dealloc(&self, ptr: NonNull, layout: Layout) -> Result<(), Error> { + if layout.is_zero_sized() { + Err(Error::ZeroSizedLayout) + } else if ptr == layout.dangling() { + Err(Error::DanglingDeallocation) + } else { alloc::alloc::GlobalAlloc::dealloc(self, ptr.as_ptr(), layout.to_stdlib()); + Ok(()) } } - - unsafe fn try_dealloc(&self, ptr: NonNull, layout: Layout) -> Result<(), Error> { - self.dealloc(ptr, layout); - Ok(()) - } } #[cfg(all(feature = "std", not(feature = "no_alloc")))] impl Grow for std::alloc::System {} diff --git a/src/traits/alloc_mut.rs b/src/traits/alloc_mut.rs index 51dd999..787943f 100644 --- a/src/traits/alloc_mut.rs +++ b/src/traits/alloc_mut.rs @@ -6,7 +6,7 @@ use { Layout, Realloc, Shrink, - error::{Cause, Error}, + error::Error, traits::helpers::{ Bytes, alloc_mut::{grow_mut, ralloc_mut, shrink_unchecked_mut}, @@ -29,18 +29,18 @@ pub trait AllocMut { /// Attempts to allocate a block of memory fitting the given [`Layout`]. /// - /// Returns a [`dangling`](ptr::dangling) pointer if [layout.size()](Layout::size) == - /// 0. - /// /// # Errors /// /// Errors are implementation-defined, refer to [`AllocMut::Error`] and [`Error`]. /// /// The standard implementations may return: /// - Err([Error::AllocFailed](Error::AllocFailed)(layout, cause)) if allocation - /// fails. `cause` is typically [`Cause::Unknown`]. If the `os_err_reporting` feature is - /// enabled, it will be [Cause::OSErr]\(oserr\). In this case, `oserr` will be - /// the error from [last_os_error]\(\).[raw_os_error]\(\). + /// fails. `cause` is typically [`Cause::Unknown`](crate::error::Cause::Unknown). If the + /// `os_err_reporting` feature is enabled, it will be + /// [Cause::OSErr](crate::error::Cause::OSErr)(oserr). In this case, `oserr` will + /// be the error from [last_os_error]\(\).[raw_os_error]\(\). + /// - Err([Error::ZeroSizedLayout]) if [layout.size()](Layout::size) == + /// 0. /// /// [last_os_error]: std::io::Error::last_os_error /// [raw_os_error]: std::io::Error::raw_os_error @@ -48,18 +48,18 @@ pub trait AllocMut { /// Attempts to allocate a zeroed block of memory fitting the given [`Layout`]. /// - /// Returns a [`dangling`](ptr::dangling) pointer if [layout.size()](Layout::size) == - /// 0. - /// /// # Errors /// /// Errors are implementation-defined, refer to [`AllocMut::Error`] and [`Error`]. /// /// The standard implementations may return: /// - Err([Error::AllocFailed](Error::AllocFailed)(layout, cause)) if allocation - /// fails. `cause` is typically [`Cause::Unknown`]. If the `os_err_reporting` feature is - /// enabled, it will be [Cause::OSErr]\(oserr\). In this case, `oserr` will be - /// the error from [last_os_error]\(\).[raw_os_error]\(\). + /// fails. `cause` is typically [`Cause::Unknown`](crate::error::Cause::Unknown). If the + /// `os_err_reporting` feature is enabled, it will be + /// [Cause::OSErr](crate::error::Cause::OSErr)(oserr). In this case, `oserr` will + /// be the error from [last_os_error]\(\).[raw_os_error]\(\). + /// - Err([Error::ZeroSizedLayout]) if [layout.size()](Layout::size) == + /// 0. /// /// [last_os_error]: std::io::Error::last_os_error /// [raw_os_error]: std::io::Error::raw_os_error @@ -70,15 +70,16 @@ pub trait AllocMut { } } -/// A memory allocation interface which may require mutable access to itself to perform operations -/// and can also deallocate memory. +/// A memory allocation interface which may require mutable access to itself and can also deallocate +/// memory. /// /// All types which are [`Dealloc`] are also [`DeallocMut`], making this more generic than /// [`Dealloc`]. pub trait DeallocMut: AllocMut { /// Deallocates a previously allocated block. /// - /// This is a noop if [layout.size()](Layout::size) == 0. + /// This is a noop if [layout.size()](Layout::size) == 0 or `ptr` is + /// [`dangling`](ptr::dangling). /// /// The default implementation simply calls [`try_dealloc_mut`](DeallocMut::try_dealloc_mut) and /// panics if it returns an error. @@ -93,12 +94,15 @@ pub trait DeallocMut: AllocMut { /// /// This function may panic if the [`try_dealloc_mut`](DeallocMut::try_dealloc_mut) /// implementation returns an error, or the implementation chooses to panic for any other - /// reason. + /// reason. It will not panic if `ptr` is [`dangling`](ptr::dangling) or + /// [layout.size()](Layout::size) == 0. #[track_caller] #[inline] unsafe fn dealloc_mut(&mut self, ptr: NonNull, layout: Layout) { - if let Err(e) = self.try_dealloc_mut(ptr, layout) { - default_dealloc_panic(ptr, layout, e) + if layout.is_nonzero_sized() && ptr != layout.dangling() { + if let Err(e) = self.try_dealloc_mut(ptr, layout) { + default_dealloc_panic(ptr, layout, e) + } } } @@ -106,8 +110,6 @@ pub trait DeallocMut: AllocMut { /// allocation library which does not provide fallible deallocation operations, this may panic, /// abort, or incorrectly return `Ok(())`. /// - /// This is a noop if [layout.size()](Layout::size) == 0. - /// /// # Safety /// /// The caller must ensure: @@ -118,8 +120,11 @@ pub trait DeallocMut: AllocMut { /// /// Errors are implementation-defined, refer to [`AllocMut::Error`] and [`Error`]. /// - /// The standard implementations do not return any errors, as the library functions backing them - /// are infallible. + /// The standard implementations may return: + /// - Err([Error::ZeroSizedLayout]) if [layout.size()](Layout::size) == + /// 0. + /// - Err([Error::DanglingDeallocation]) if ptr == + /// [layout.dangling](Layout::dangling). /// /// However, if using this method through a synchronization primitive wrapping a type which /// implements [`DeallocMut`], an [`Error::Other`] wrapping a generic error message will be @@ -160,15 +165,18 @@ pub trait GrowMut: AllocMut + DeallocMut { /// /// The standard implementations may return: /// - Err([Error::AllocFailed](Error::AllocFailed)(layout, cause)) if allocation - /// fails. `cause` is typically [`Cause::Unknown`]. If the `os_err_reporting` feature is - /// enabled, it will be [Cause::OSErr]\(oserr\). In this case, `oserr` will be - /// the error from [last_os_error]\(\).[raw_os_error]\(\). - /// - /// [last_os_error]: std::io::Error::last_os_error - /// [raw_os_error]: std::io::Error::raw_os_error + /// fails. `cause` is typically [`Cause::Unknown`](crate::error::Cause::Unknown). If the + /// `os_err_reporting` feature is enabled, it will be + /// [Cause::OSErr](crate::error::Cause::OSErr)(oserr). In this case, `oserr` will + /// be the error from [last_os_error]\(\).[raw_os_error]\(\). /// - Err([Error::GrowSmallerNewLayout]\([old_layout.size()](Layout::size), /// [new_layout.size()](Layout::size))\) if [old_layout.size()](Layout::size) > /// [new_layout.size()](Layout::size). + /// - Err([Error::ZeroSizedLayout]) if [new_layout.size()](Layout::size) == + /// 0. + /// + /// [last_os_error]: std::io::Error::last_os_error + /// [raw_os_error]: std::io::Error::raw_os_error #[cfg_attr(miri, track_caller)] #[inline] unsafe fn grow_mut( @@ -204,15 +212,18 @@ pub trait GrowMut: AllocMut + DeallocMut { /// /// The standard implementations may return: /// - Err([Error::AllocFailed](Error::AllocFailed)(layout, cause)) if allocation - /// fails. `cause` is typically [`Cause::Unknown`]. If the `os_err_reporting` feature is - /// enabled, it will be [Cause::OSErr]\(oserr\). In this case, `oserr` will be - /// the error from [last_os_error]\(\).[raw_os_error]\(\). - /// - /// [last_os_error]: std::io::Error::last_os_error - /// [raw_os_error]: std::io::Error::raw_os_error + /// fails. `cause` is typically [`Cause::Unknown`](crate::error::Cause::Unknown). If the + /// `os_err_reporting` feature is enabled, it will be + /// [Cause::OSErr](crate::error::Cause::OSErr)(oserr). In this case, `oserr` will + /// be the error from [last_os_error]\(\).[raw_os_error]\(\). /// - Err([Error::GrowSmallerNewLayout]\([old_layout.size()](Layout::size), /// [new_layout.size()](Layout::size))\) if [old_layout.size()](Layout::size) > /// [new_layout.size()](Layout::size). + /// - Err([Error::ZeroSizedLayout]) if [new_layout.size()](Layout::size) == + /// 0. + /// + /// [last_os_error]: std::io::Error::last_os_error + /// [raw_os_error]: std::io::Error::raw_os_error #[cfg_attr(miri, track_caller)] #[inline] unsafe fn zgrow_mut( @@ -234,9 +245,6 @@ pub trait ShrinkMut: AllocMut + DeallocMut { /// /// On failure, the original memory will not be deallocated. /// - /// Returns a [`dangling`](ptr::dangling) pointer if [new_layout.size()](Layout::size) == - /// 0. - /// /// Note that the default implementation simply: /// 1. Checks that the new layout is smaller or the same size. If both layouts are the same, /// `ptr` is returned and no operation is performed. @@ -258,15 +266,18 @@ pub trait ShrinkMut: AllocMut + DeallocMut { /// /// The standard implementations may return: /// - Err([Error::AllocFailed](Error::AllocFailed)(layout, cause)) if allocation - /// fails. `cause` is typically [`Cause::Unknown`]. If the `os_err_reporting` feature is - /// enabled, it will be [Cause::OSErr]\(oserr\). In this case, `oserr` will be - /// the error from [last_os_error]\(\).[raw_os_error]\(\). - /// - /// [last_os_error]: std::io::Error::last_os_error - /// [raw_os_error]: std::io::Error::raw_os_error + /// fails. `cause` is typically [`Cause::Unknown`](crate::error::Cause::Unknown). If the + /// `os_err_reporting` feature is enabled, it will be + /// [Cause::OSErr](crate::error::Cause::OSErr)(oserr). In this case, `oserr` will + /// be the error from [last_os_error]\(\).[raw_os_error]\(\). /// - Err([Error::ShrinkLargerNewLayout]\([old_layout.size()](Layout::size), /// [new_layout.size()](Layout::size))\) if [old_layout.size()](Layout::size) < /// [new_layout.size()](Layout::size). + /// - Err([Error::ZeroSizedLayout]) if [new_layout.size()](Layout::size) == + /// 0. + /// + /// [last_os_error]: std::io::Error::last_os_error + /// [raw_os_error]: std::io::Error::raw_os_error #[cfg_attr(miri, track_caller)] unsafe fn shrink_mut( &mut self, @@ -304,9 +315,6 @@ pub trait ReallocMut: GrowMut + ShrinkMut { /// /// On failure, the original memory will not be deallocated. /// - /// Returns a [`dangling`](ptr::dangling) pointer if [new_layout.size()](Layout::size) == - /// 0. - /// /// # Safety /// /// The caller must ensure: @@ -319,9 +327,12 @@ pub trait ReallocMut: GrowMut + ShrinkMut { /// /// The standard implementations may return: /// - Err([Error::AllocFailed](Error::AllocFailed)(layout, cause)) if allocation - /// fails. `cause` is typically [`Cause::Unknown`]. If the `os_err_reporting` feature is - /// enabled, it will be [Cause::OSErr]\(oserr\). In this case, `oserr` will be - /// the error from [last_os_error]\(\).[raw_os_error]\(\). + /// fails. `cause` is typically [`Cause::Unknown`](crate::error::Cause::Unknown). If the + /// `os_err_reporting` feature is enabled, it will be + /// [Cause::OSErr](crate::error::Cause::OSErr)(oserr). In this case, `oserr` will + /// be the error from [last_os_error]\(\).[raw_os_error]\(\). + /// - Err([Error::ZeroSizedLayout]) if [new_layout.size()](Layout::size) == + /// 0. /// /// [last_os_error]: std::io::Error::last_os_error /// [raw_os_error]: std::io::Error::raw_os_error @@ -343,9 +354,6 @@ pub trait ReallocMut: GrowMut + ShrinkMut { /// /// On failure, the original memory will not be deallocated. /// - /// Returns a [`dangling`](ptr::dangling) pointer if [new_layout.size()](Layout::size) == - /// 0. - /// /// # Safety /// /// The caller must ensure: @@ -358,9 +366,12 @@ pub trait ReallocMut: GrowMut + ShrinkMut { /// /// The standard implementations may return: /// - Err([Error::AllocFailed](Error::AllocFailed)(layout, cause)) if allocation - /// fails. `cause` is typically [`Cause::Unknown`]. If the `os_err_reporting` feature is - /// enabled, it will be [Cause::OSErr]\(oserr\). In this case, `oserr` will be - /// the error from [last_os_error]\(\).[raw_os_error]\(\). + /// fails. `cause` is typically [`Cause::Unknown`](crate::error::Cause::Unknown). If the + /// `os_err_reporting` feature is enabled, it will be + /// [Cause::OSErr](crate::error::Cause::OSErr)(oserr). In this case, `oserr` will + /// be the error from [last_os_error]\(\).[raw_os_error]\(\). + /// - Err([Error::ZeroSizedLayout]) if [new_layout.size()](Layout::size) == + /// 0. /// /// [last_os_error]: std::io::Error::last_os_error /// [raw_os_error]: std::io::Error::raw_os_error diff --git a/src/traits/alloc_temp.rs b/src/traits/alloc_temp.rs index 6a09c18..d6f6e53 100644 --- a/src/traits/alloc_temp.rs +++ b/src/traits/alloc_temp.rs @@ -16,9 +16,6 @@ pub trait AllocTemp { /// Attempts to allocate a block of memory fitting the given [`Layout`], and calls `with_mem` on /// the returned pointer on success. /// - /// The pointer will be [`dangling`](ptr::dangling) if [layout.size()](Layout::size) == - /// 0. - /// /// # Errors /// /// Errors are implementation-defined, refer to [`AllocTemp::Error`] and [`Error`]. @@ -28,6 +25,8 @@ pub trait AllocTemp { /// fails. `cause` is typically [`Cause::Unknown`]. If the `os_err_reporting` feature is /// enabled, it will be [Cause::OSErr]\(oserr\). In this case, `oserr` will be /// the error from [last_os_error]\(\).[raw_os_error]\(\). + /// - Err([Error::ZeroSizedLayout]) if [layout.size()](Layout::size) == + /// 0. /// - Err([Error::CaughtUnwind]) if the `catch_unwind` feature is enabled and an /// unwind occurs in a function which is not allowed to unwind. /// @@ -36,11 +35,7 @@ pub trait AllocTemp { /// /// # Safety /// - /// The caller must ensure `with_mem` properly handles the case where - /// [layout.size()](Layout::size) == 0 and it receives a - /// [`dangling`](ptr::dangling) pointer. - /// - /// Other safety preconditions are implementation defined. + /// Safety preconditions are implementation defined. unsafe fn alloc_temp) -> R>( &self, layout: Layout, @@ -50,9 +45,6 @@ pub trait AllocTemp { /// Attempts to allocate a block of zeroed memory fitting the given [`Layout`], and calls /// `with_mem` on the returned pointer on success. /// - /// The pointer will be [`dangling`](ptr::dangling) if [layout.size()](Layout::size) == - /// 0. - /// /// # Errors /// /// Errors are implementation-defined, refer to [`AllocTemp::Error`] and [`Error`]. @@ -62,6 +54,8 @@ pub trait AllocTemp { /// fails. `cause` is typically [`Cause::Unknown`]. If the `os_err_reporting` feature is /// enabled, it will be [Cause::OSErr]\(oserr\). In this case, `oserr` will be /// the error from [last_os_error]\(\).[raw_os_error]\(\). + /// - Err([Error::ZeroSizedLayout]) if [layout.size()](Layout::size) == + /// 0. /// - Err([Error::CaughtUnwind]) if the `catch_unwind` feature is enabled and an /// unwind occurs in a function which is not allowed to unwind. /// @@ -70,11 +64,7 @@ pub trait AllocTemp { /// /// # Safety /// - /// The caller must ensure `with_mem` properly handles the case where - /// [layout.size()](Layout::size) == 0 and it receives a - /// [`dangling`](ptr::dangling) pointer. - /// - /// Other safety preconditions are implementation defined. + /// Safety preconditions are implementation defined. #[cfg_attr(miri, track_caller)] unsafe fn zalloc_temp) -> R>( &self, diff --git a/src/traits/helpers.rs b/src/traits/helpers.rs index c8470f1..56c542b 100644 --- a/src/traits/helpers.rs +++ b/src/traits/helpers.rs @@ -52,8 +52,8 @@ pub unsafe fn grow_unchecked + ?Sized, E: From + Debug Bytes::Zeroed => tri!(do a.zalloc(new_layout)) }; - ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr(), old_size); if old_size != 0 { + ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr(), old_size); tri!(do a.try_dealloc(ptr, old_layout)); } @@ -69,8 +69,8 @@ pub unsafe fn shrink_unchecked + ?Sized, E: From + D ) -> Result, E> { let new_ptr = tri!(do a.alloc(new_layout)); - ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr(), new_layout.size()); if old_layout.is_nonzero_sized() { + ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr(), new_layout.size()); tri!(do a.try_dealloc(ptr, old_layout)); } diff --git a/tests/alloc_mut.rs b/tests/alloc_mut.rs index 0f75c1e..551ac85 100644 --- a/tests/alloc_mut.rs +++ b/tests/alloc_mut.rs @@ -26,9 +26,7 @@ impl AllocMut for MutOnlyAlloc { impl DeallocMut for MutOnlyAlloc { #[inline] unsafe fn try_dealloc_mut(&mut self, ptr: NonNull, layout: Layout) -> Result<(), Error> { - if layout.is_nonzero_sized() { - dealloc(ptr.as_ptr(), layout.to_stdlib()); - } + dealloc(ptr.as_ptr(), layout.to_stdlib()); Ok(()) } } diff --git a/tests/helpers.rs b/tests/helpers.rs index 9041082..1ceaa16 100644 --- a/tests/helpers.rs +++ b/tests/helpers.rs @@ -141,8 +141,9 @@ fn is_multiple_of_zero_rhs() { #[test] fn null_q_dyn_zsl_check_zero_layout() { let layout = Layout::new::<()>(); - let ptr = null_q_dyn_zsl_check(layout, |_| -> *mut u8 { panic!("unexpected alloc") }).unwrap(); - assert_eq!(ptr, layout.dangling()); + let ptr = + null_q_dyn_zsl_check(layout, |_| -> *mut u8 { panic!("unexpected alloc") }).unwrap_err(); + assert_eq!(ptr, Error::ZeroSizedLayout); } #[test] From c02733013e93e883bfacd026f43cfd78900ed978 Mon Sep 17 00:00:00 2001 From: echohumm Date: Fri, 6 Feb 2026 12:38:28 -0700 Subject: [PATCH 2/3] release this branch under a temporary semver metadata thing --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- src/allocs/c_alloc.rs | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85b5b0b..7ec02ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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.2+zsl-hard-error] - 2026-02-06 ### Changed diff --git a/Cargo.toml b/Cargo.toml index c37dc2e..930a062 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "memapi2" -version = "0.9.2" +version = "0.9.3+zsl-hard-error" edition = "2018" rust-version = "1.46.0" authors = ["echohumm"] diff --git a/src/allocs/c_alloc.rs b/src/allocs/c_alloc.rs index 0f940ab..3de6c59 100644 --- a/src/allocs/c_alloc.rs +++ b/src/allocs/c_alloc.rs @@ -1,14 +1,14 @@ 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} }; From a35483d2c4cb36d26f983fff28251187917cca41 Mon Sep 17 00:00:00 2001 From: echohumm Date: Fri, 6 Feb 2026 13:21:25 -0700 Subject: [PATCH 3/3] update version name --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ec02ca..2c99bf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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). -## [0.9.2+zsl-hard-error] - 2026-02-06 +## [0.9.4] - 2026-02-06 ### Changed diff --git a/Cargo.toml b/Cargo.toml index 930a062..34abcaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "memapi2" -version = "0.9.3+zsl-hard-error" +version = "0.9.4" edition = "2018" rust-version = "1.46.0" authors = ["echohumm"]