Skip to content

Commit 2bdaf25

Browse files
authored
Merge pull request #2 from echohumm/zsl-hard-error
make ZSLs a hard error
2 parents 790fd3e + a35483d commit 2bdaf25

15 files changed

Lines changed: 179 additions & 161 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
8+
## [0.9.4] - 2026-02-06
9+
10+
### Changed
11+
12+
- `Dealloc` traits' fallible functions now treat zero-sized layouts and dangling pointers as a hard
13+
error
14+
- `Dealloc` traits' fallible functions are now a noop for ZSLs and dangling pointers
15+
- All other allocation functions now treat ZSLs as an error.
916

1017
## [0.9.2] - 2026-02-03
1118

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "memapi2"
3-
version = "0.9.2"
3+
version = "0.9.4"
44
edition = "2018"
55
rust-version = "1.46.0"
66
authors = ["echohumm"]

src/allocs/c_alloc.rs

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,29 @@
11
use {
22
crate::{
3+
error::Error,
4+
ffi::c_alloc::{c_alloc, c_dealloc, c_zalloc, grow_aligned, shrink_aligned},
5+
helpers::null_q_dyn_zsl_check,
36
Alloc,
47
Dealloc,
58
Grow,
69
Layout,
710
Realloc,
8-
Shrink,
9-
error::Error,
10-
ffi::c_alloc::{c_alloc, c_dealloc, c_zalloc, grow_aligned, shrink_aligned},
11-
helpers::{null_q_dyn, null_q_dyn_zsl_check}
11+
Shrink
1212
},
1313
core::{cmp::Ordering, ffi::c_void, ptr::NonNull}
1414
};
15-
1615
// TODO: we should use the builtin malloc and realloc if align <= guaranteed align
1716

1817
#[cfg_attr(miri, track_caller)]
1918
fn pad_then_alloc(
2019
layout: Layout,
2120
alloc: unsafe fn(usize, usize) -> *mut c_void
2221
) -> Result<NonNull<u8>, Error> {
23-
let l = tri!(do layout.to_aligned_alloc_compatible());
22+
let padded = tri!(do layout.to_aligned_alloc_compatible());
2423
null_q_dyn_zsl_check(
2524
layout,
2625
// SAFETY: we rounded up the layout's values to satisfy the requirements.
27-
|_| unsafe { alloc(l.align(), l.size()) }
26+
|_| unsafe { alloc(padded.align(), padded.size()) }
2827
)
2928
}
3029

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

45-
null_q_dyn_zsl_check(new_padded, |l| {
46-
grow_aligned(ptr.as_ptr().cast(), old_padded.size(), l.align(), l.size(), alloc)
44+
null_q_dyn_zsl_check(new_layout, |_| {
45+
grow_aligned(
46+
ptr.as_ptr().cast(),
47+
old_padded.size(),
48+
new_padded.align(),
49+
new_padded.size(),
50+
alloc
51+
)
4752
})
4853
}
4954

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

60-
null_q_dyn_zsl_check(new_padded, |l| {
65+
null_q_dyn_zsl_check(new_layout, |_| {
6166
let old_ptr = ptr.as_ptr().cast();
6267
let old_size = old_padded.size();
6368
let old_align = old_padded.align();
6469

65-
let size = l.size();
66-
let align = l.align();
70+
let size = new_padded.size();
71+
let align = new_padded.align();
6772

6873
match old_size.cmp(&new_padded.size()) {
6974
// SAFETY: caller guarantees that `old_ptr` and `old_size` are valid, we just
@@ -110,18 +115,16 @@ impl Alloc for CAlloc {
110115
impl Dealloc for CAlloc {
111116
#[cfg_attr(miri, track_caller)]
112117
#[inline]
113-
unsafe fn dealloc(&self, ptr: NonNull<u8>, layout: Layout) {
114-
if layout.is_nonzero_sized() {
118+
unsafe fn try_dealloc(&self, ptr: NonNull<u8>, layout: Layout) -> Result<(), Error> {
119+
if layout.is_zero_sized() {
120+
Err(Error::ZeroSizedLayout)
121+
} else if ptr == layout.dangling() {
122+
Err(Error::DanglingDeallocation)
123+
} else {
115124
c_dealloc(ptr.as_ptr().cast());
125+
Ok(())
116126
}
117127
}
118-
119-
#[cfg_attr(miri, track_caller)]
120-
#[inline]
121-
unsafe fn try_dealloc(&self, ptr: NonNull<u8>, layout: Layout) -> Result<(), Error> {
122-
self.dealloc(ptr, layout);
123-
Ok(())
124-
}
125128
}
126129
impl Grow for CAlloc {
127130
#[cfg_attr(miri, track_caller)]
@@ -158,10 +161,9 @@ impl Shrink for CAlloc {
158161
return Err(Error::ShrinkLargerNewLayout(old_layout.size(), new_layout.size()));
159162
}
160163

161-
null_q_dyn(
162-
shrink_aligned(ptr.as_ptr().cast(), new_padded.align(), new_padded.size()),
163-
new_padded
164-
)
164+
null_q_dyn_zsl_check(new_layout, |_| {
165+
shrink_aligned(ptr.as_ptr().cast(), new_padded.align(), new_padded.size())
166+
})
165167
}
166168
}
167169
impl Realloc for CAlloc {

src/error.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ pub enum Error {
2626
AllocFailed(Layout, Cause),
2727
/// The layout computed with the given size and alignment is invalid; see the contained reason.
2828
InvalidLayout(usize, usize, LayoutErr),
29+
/// A zero-sized allocation was requested. This is treated as an error as several allocators do
30+
/// not support such requests or respond to them strangely.
31+
///
32+
/// In most reasonable cases, [`layout.dangling()`](Layout::dangling) can and should be used
33+
/// instead.
34+
ZeroSizedLayout,
35+
/// An attempt was made to deallocate a dangling pointer.
36+
DanglingDeallocation,
2937
/// Attempted to grow to a smaller size.
3038
GrowSmallerNewLayout(usize, usize),
3139
/// Attempted to shrink to a larger size.
@@ -45,10 +53,12 @@ impl Display for Error {
4553
AllocFailed,
4654
ArithmeticError,
4755
CaughtUnwind,
56+
DanglingDeallocation,
4857
GrowSmallerNewLayout,
4958
InvalidLayout,
5059
Other,
51-
ShrinkLargerNewLayout
60+
ShrinkLargerNewLayout,
61+
ZeroSizedLayout
5262
};
5363

5464
match self {
@@ -64,6 +74,10 @@ impl Display for Error {
6474
"computed invalid layout:\n\tsize: {}\n\talign: {}\n\treason: {}",
6575
sz, aln, e
6676
),
77+
ZeroSizedLayout => {
78+
write!(f, "received a zero-sized layout")
79+
}
80+
DanglingDeallocation => write!(f, "attempted to deallocate a dangling pointer"),
6781
GrowSmallerNewLayout(old, new) => {
6882
write!(f, "attempted to grow from a size of {} to a smaller size of {}", old, new)
6983
}

src/ffi/c/calloca.c

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,6 @@ void c_alloca(
5757
void* closure,
5858
void* out
5959
) {
60-
if (size == 0) {
61-
/* return dangling for zsl */
62-
callback(closure, (uint8_t*)(uintptr_t)align, out);
63-
return;
64-
}
65-
6660
/* determine whether we need extra space for padding */
6761
size_t alloc_size = size;
6862
size_t align_m_1;

src/ffi/c_alloc.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -171,15 +171,6 @@ pub unsafe fn shrink_aligned(
171171
align: usize,
172172
size: usize // a memset-ing alloc here is useless, as it will just be overwritten anyway.
173173
) -> *mut c_void {
174-
// fast path if size is 0, just free and return dangling
175-
if size == 0 {
176-
// SAFETY: caller guarantees that `old_ptr` is valid
177-
unsafe {
178-
c_dealloc(old_ptr);
179-
}
180-
return align as *mut c_void;
181-
}
182-
183174
// allocate new aligned memory
184175
// SAFETY: requirements are passed on to the caller
185176
let ptr = unsafe { c_alloc(align, size) };

src/ffi/stack_alloc.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,16 @@ thread_local! {
3434
/// - If `layout.size() == 0`, `f` must treat the pointer as a [`dangling`](core::ptr::dangling)
3535
/// pointer.
3636
/// - `f` must initialize the value behind its second parameter before returning.
37-
/// - `f` must properly handle the case where <code>[layout.size()](Layout::size) == 0</code> and it
38-
/// receives a [`dangling`](core::ptr::dangling) pointer.
3937
/// - On Rust versions below `1.71` with `catch_unwind` disabled, `f` must never unwind.
4038
pub unsafe fn with_alloca<R, F: FnOnce(NonNull<u8>, *mut R)>(
4139
layout: Layout,
4240
f: F
4341
) -> Result<R, Error> {
42+
// TODO: maybe i should just make F take a Result instead and not skip running it on error
43+
if layout.size() == 0 {
44+
return Err(Error::ZeroSizedLayout);
45+
}
46+
4447
let mut ret = MaybeUninit::uninit();
4548
let mut closure = ManuallyDrop::new(f);
4649

src/helpers.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -365,9 +365,8 @@ pub fn null_q_dyn_zsl_check<T, F: Fn(Layout) -> *mut T>(
365365
layout: Layout,
366366
f: F
367367
) -> Result<NonNull<u8>, Error> {
368-
if layout.is_zero_sized() { Ok(layout.dangling()) } else { null_q_dyn(f(layout), layout) }
368+
if layout.is_zero_sized() { Err(Error::ZeroSizedLayout) } else { null_q_dyn(f(layout), layout) }
369369
}
370-
371370
// TODO: lower const msrv and generally improve these. will require some testing regarding effects
372371
// of current and alternative implementations on provenance
373372
#[rustversion::since(1.75)]

src/lib.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ macro_rules! default_alloc_impl {
179179
#[cfg_attr(miri, track_caller)]
180180
#[inline(always)]
181181
unsafe fn dealloc(&self, ptr: core::ptr::NonNull<u8>, layout: Layout) {
182-
if layout.is_nonzero_sized() {
182+
if layout.is_nonzero_sized() && ptr != layout.dangling() {
183183
alloc::alloc::dealloc(ptr.as_ptr(), layout.to_stdlib());
184184
}
185185
}
@@ -191,8 +191,14 @@ macro_rules! default_alloc_impl {
191191
ptr: core::ptr::NonNull<u8>,
192192
layout: Layout
193193
) -> Result<(), crate::error::Error> {
194-
self.dealloc(ptr, layout);
195-
Ok(())
194+
if layout.is_zero_sized() {
195+
Err(crate::error::Error::ZeroSizedLayout)
196+
} else if ptr == layout.dangling() {
197+
Err(crate::error::Error::DanglingDeallocation)
198+
} else {
199+
alloc::alloc::dealloc(ptr.as_ptr(), layout.to_stdlib());
200+
Ok(())
201+
}
196202
}
197203
}
198204
#[cfg(not(feature = "no_alloc"))]

0 commit comments

Comments
 (0)