Skip to content

Commit 730603d

Browse files
committed
allocator::win32::{Global, Local}: fix ZST (re)allocs by banning them
relates to #32
1 parent 4aab533 commit 730603d

File tree

4 files changed

+47
-3
lines changed

4 files changed

+47
-3
lines changed

src/allocator/win32/_win32.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ mod virtual_; pub use virtual_::*;
6767
impl core::fmt::Debug for Error { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { core::fmt::Debug::fmt(&self.0, f) } }
6868
//impl core::fmt::Display for Error { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { core::fmt::Display::fmt(&self.0, f) } }
6969
impl Error { pub(crate) fn get_last() -> Self { Self(winresult::ErrorHResultOrCode::from(get_last_error())) } }
70+
impl From<crate::error::BannedZeroSizedAllocationsError > for Error { fn from(_: crate::error::BannedZeroSizedAllocationsError ) -> Self { Self(winresult::ERROR::BAD_LENGTH.into()) } } // XXX
7071
impl From<crate::error::ExcessiveAlignmentRequestedError> for Error { fn from(_: crate::error::ExcessiveAlignmentRequestedError ) -> Self { Self(winresult::ERROR::MAPPED_ALIGNMENT.into()) } } // XXX
7172
impl From<crate::error::ExcessiveSliceRequestedError > for Error { fn from(_: crate::error::ExcessiveSliceRequestedError ) -> Self { Self(winresult::ERROR::BUFFER_OVERFLOW.into()) } } // XXX
7273

src/allocator/win32/global.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,15 @@ impl Meta for Global {
4848
const MIN_ALIGN : Alignment = super::MEMORY_ALLOCATION_ALIGNMENT; // Verified through testing
4949
const MAX_ALIGN : Alignment = super::MEMORY_ALLOCATION_ALIGNMENT; // Verified through testing
5050
const MAX_SIZE : usize = usize::MAX;
51-
const ZST_SUPPORTED : bool = true;
51+
52+
/// ZST support in `Global*` is incredibly cursed:
53+
/// - `GlobalAlloc(..., 0)` "allocates" 0 bytes. This can be confirmed with `GlobalSize(zst)` (doesn't set `GetLastError()`)
54+
/// - `GlobalReAlloc(zst, 1, ...)` fails with <code>GetLastError() == ERROR_NOT_ENOUGH_MEMORY</code>.
55+
/// - `GlobalReAlloc(zst, 0, ...)` frees? Oops.
56+
///
57+
/// As such, this wrapper straight up bans such allocations.
58+
///
59+
const ZST_SUPPORTED : bool = false;
5260
}
5361

5462
impl ZstSupported for Global {}
@@ -75,12 +83,14 @@ unsafe impl Stateless for Global {}
7583
// SAFETY: per above
7684
unsafe impl thin::Alloc for Global {
7785
fn alloc_uninit(&self, size: usize) -> Result<AllocNN, Self::Error> {
86+
if size == 0 { Err(error::BannedZeroSizedAllocationsError)? } // see ZST_SUPPORTED rant above
7887
// SAFETY: per above
7988
let alloc = unsafe { GlobalAlloc(0, size) };
8089
NonNull::new(alloc.cast()).ok_or_else(Error::get_last)
8190
}
8291

8392
fn alloc_zeroed(&self, size: usize) -> Result<AllocNN0, Self::Error> {
93+
if size == 0 { Err(error::BannedZeroSizedAllocationsError)? } // see ZST_SUPPORTED rant above
8494
// SAFETY: ✔️ `GMEM_ZEROINIT` should ensure the newly allocated memory is zeroed.
8595
let alloc = unsafe { GlobalAlloc(GMEM_ZEROINIT, size) };
8696
NonNull::new(alloc.cast()).ok_or_else(Error::get_last)
@@ -106,12 +116,14 @@ unsafe impl thin::Realloc for Global {
106116
const CAN_REALLOC_ZEROED : bool = true;
107117

108118
unsafe fn realloc_uninit(&self, ptr: AllocNN, new_size: usize) -> Result<AllocNN, Self::Error> {
119+
if new_size == 0 { Err(error::BannedZeroSizedAllocationsError)? } // see ZST_SUPPORTED rant above
109120
// SAFETY: ✔️ `ptr` belongs to `self` per thin::Realloc's documented safety preconditions - and thus was allocated with `Global{,Re}Alloc` - which should be safe to `GlobalReAlloc`.
110121
let alloc = unsafe { GlobalReAlloc(ptr.as_ptr().cast(), new_size, 0) };
111122
NonNull::new(alloc.cast()).ok_or_else(Error::get_last)
112123
}
113124

114125
unsafe fn realloc_zeroed(&self, ptr: AllocNN, new_size: usize) -> Result<AllocNN, Self::Error> {
126+
if new_size == 0 { Err(error::BannedZeroSizedAllocationsError)? } // see ZST_SUPPORTED rant above
115127
// SAFETY: ✔️ `GMEM_ZEROINIT` should ensure the newly reallocated memory is zeroed.
116128
// SAFETY: ✔️ `ptr` belongs to `self` per thin::Realloc's documented safety preconditions - and thus was allocated with `Global{,Re}Alloc` - which should be safe to `GlobalReAlloc`.
117129
let alloc = unsafe { GlobalReAlloc(ptr.as_ptr().cast(), new_size, GMEM_ZEROINIT) };
@@ -178,13 +190,17 @@ unsafe impl thin::SizeOfDebug for Global {
178190
#[test] fn thin_alignment() { thin::test::alignment(Global) }
179191
#[test] fn thin_edge_case_sizes() { thin::test::edge_case_sizes(Global) }
180192
#[test] fn thin_nullable() { thin::test::nullable(Global) }
181-
#[test] fn thin_size() { thin::test::size_over_alloc(Global) }
193+
#[test] fn thin_size() { thin::test::size_exact_alloc(Global) }
182194
#[test] fn thin_uninit() { unsafe { thin::test::uninit_alloc_unsound(Global) } }
195+
#[test] fn thin_uninit_realloc() { thin::test::uninit_realloc(Global) }
183196
#[test] fn thin_zeroed() { thin::test::zeroed_alloc(Global) }
197+
#[test] fn thin_zeroed_realloc() { thin::test::zeroed_realloc(Global) }
184198
#[test] fn thin_zst_support() { thin::test::zst_supported_accurate(Global) }
185199

186200
#[test] fn fat_alignment() { fat::test::alignment(Global) }
187201
#[test] fn fat_edge_case_sizes() { fat::test::edge_case_sizes(Global) }
188202
#[test] fn fat_uninit() { unsafe { fat::test::uninit_alloc_unsound(Global) } }
203+
#[test] fn fat_uninit_realloc() { fat::test::uninit_realloc(Global) }
189204
#[test] fn fat_zeroed() { fat::test::zeroed_alloc(Global) }
205+
#[test] fn fat_zeroed_realloc() { fat::test::zeroed_realloc(Global) }
190206
#[test] fn fat_zst_support() { fat::test::zst_supported_accurate(Global) }

src/allocator/win32/local.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,15 @@ impl Meta for Local {
4848
const MIN_ALIGN : Alignment = super::MEMORY_ALLOCATION_ALIGNMENT; // Verified through testing
4949
const MAX_ALIGN : Alignment = super::MEMORY_ALLOCATION_ALIGNMENT; // Verified through testing
5050
const MAX_SIZE : usize = usize::MAX;
51-
const ZST_SUPPORTED : bool = true;
51+
52+
/// ZST support in `Local*` is incredibly cursed:
53+
/// - `LocalAlloc(..., 0)` "allocates" 0 bytes. This can be confirmed with `LocalSize(zst)` (doesn't set `GetLastError()`)
54+
/// - `LocalReAlloc(zst, 1, ...)` fails with <code>GetLastError() == ERROR_NOT_ENOUGH_MEMORY</code>.
55+
/// - `LocalReAlloc(zst, 0, ...)` frees? Oops.
56+
///
57+
/// As such, this wrapper straight up bans such allocations.
58+
///
59+
const ZST_SUPPORTED : bool = false;
5260
}
5361

5462
impl ZstSupported for Local {}
@@ -75,12 +83,14 @@ unsafe impl Stateless for Local {}
7583
// SAFETY: per above
7684
unsafe impl thin::Alloc for Local {
7785
fn alloc_uninit(&self, size: usize) -> Result<AllocNN, Self::Error> {
86+
if size == 0 { Err(error::BannedZeroSizedAllocationsError)? } // see ZST_SUPPORTED rant
7887
// SAFETY: per above
7988
let alloc = unsafe { LocalAlloc(0, size) };
8089
NonNull::new(alloc.cast()).ok_or_else(Error::get_last)
8190
}
8291

8392
fn alloc_zeroed(&self, size: usize) -> Result<AllocNN0, Self::Error> {
93+
if size == 0 { Err(error::BannedZeroSizedAllocationsError)? } // see ZST_SUPPORTED rant
8494
// SAFETY: ✔️ `LMEM_ZEROINIT` should ensure the newly allocated memory is zeroed.
8595
let alloc = unsafe { LocalAlloc(LMEM_ZEROINIT, size) };
8696
NonNull::new(alloc.cast()).ok_or_else(Error::get_last)
@@ -106,12 +116,14 @@ unsafe impl thin::Realloc for Local {
106116
const CAN_REALLOC_ZEROED : bool = true;
107117

108118
unsafe fn realloc_uninit(&self, ptr: AllocNN, new_size: usize) -> Result<AllocNN, Self::Error> {
119+
if new_size == 0 { Err(error::BannedZeroSizedAllocationsError)? } // see ZST_SUPPORTED rant
109120
// SAFETY: ✔️ `ptr` belongs to `self` per thin::Realloc's documented safety preconditions - and thus was allocated with `Local{,Re}Alloc` - which should be safe to `LocalReAlloc`.
110121
let alloc = unsafe { LocalReAlloc(ptr.as_ptr().cast(), new_size, 0) };
111122
NonNull::new(alloc.cast()).ok_or_else(Error::get_last)
112123
}
113124

114125
unsafe fn realloc_zeroed(&self, ptr: AllocNN, new_size: usize) -> Result<AllocNN, Self::Error> {
126+
if new_size == 0 { Err(error::BannedZeroSizedAllocationsError)? } // see ZST_SUPPORTED rant
115127
// SAFETY: ✔️ `LMEM_ZEROINIT` should ensure the newly reallocated memory is zeroed.
116128
// SAFETY: ✔️ `ptr` belongs to `self` per thin::Realloc's documented safety preconditions - and thus was allocated with `Local{,Re}Alloc` - which should be safe to `LocalReAlloc`.
117129
let alloc = unsafe { LocalReAlloc(ptr.as_ptr().cast(), new_size, LMEM_ZEROINIT) };
@@ -180,11 +192,15 @@ unsafe impl thin::SizeOfDebug for Local {
180192
#[test] fn thin_nullable() { thin::test::nullable(Local) }
181193
#[test] fn thin_size() { thin::test::size_exact_alloc(Local) }
182194
#[test] fn thin_uninit() { unsafe { thin::test::uninit_alloc_unsound(Local) } }
195+
#[test] fn thin_uninit_realloc() { thin::test::uninit_realloc(Local) }
183196
#[test] fn thin_zeroed() { thin::test::zeroed_alloc(Local) }
197+
#[test] fn thin_zeroed_realloc() { thin::test::zeroed_realloc(Local) }
184198
#[test] fn thin_zst_support() { thin::test::zst_supported_accurate(Local) }
185199

186200
#[test] fn fat_alignment() { fat::test::alignment(Local) }
187201
#[test] fn fat_edge_case_sizes() { fat::test::edge_case_sizes(Local) }
188202
#[test] fn fat_uninit() { unsafe { fat::test::uninit_alloc_unsound(Local) } }
203+
#[test] fn fat_uninit_realloc() { fat::test::uninit_realloc(Local) }
189204
#[test] fn fat_zeroed() { fat::test::zeroed_alloc(Local) }
205+
#[test] fn fat_zeroed_realloc() { fat::test::zeroed_realloc(Local) }
190206
#[test] fn fat_zst_support() { fat::test::zst_supported_accurate(Local) }

src/error.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ use core::fmt::{self, Debug, Display, Formatter};
77

88

99

10+
/// The allocator explicitly rejected a zero-sized allocation, because they behave inconsistently.
11+
///
12+
/// This may be because:
13+
/// - A platform specific allocator fails on some but not all platforms.
14+
/// - Allocating 0 bytes succeeds, but reallocating to 0 bytes fails.
15+
#[derive(Clone, Copy, Debug)] pub struct BannedZeroSizedAllocationsError;
16+
1017
/// More alignment was requested than the allocator could support.
1118
#[derive(Clone, Copy, Debug)] pub struct ExcessiveAlignmentRequestedError {
1219
pub requested: Alignment,
@@ -18,11 +25,15 @@ use core::fmt::{self, Debug, Display, Formatter};
1825
pub requested: usize,
1926
}
2027

28+
impl Display for BannedZeroSizedAllocationsError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "requested a 0-sized allocation, but those have inconsistent behavior with this allocator") } }
2129
impl Display for ExcessiveAlignmentRequestedError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "requested {:?} alignment, but a maximum of {:?} is supported", self.requested, self.supported) } }
2230
impl Display for ExcessiveSliceRequestedError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "requested {} elements, but that would result in a LayoutError", self.requested) } }
31+
impl From<BannedZeroSizedAllocationsError > for () { fn from(_: BannedZeroSizedAllocationsError) -> Self {} }
2332
impl From<ExcessiveAlignmentRequestedError> for () { fn from(_: ExcessiveAlignmentRequestedError) -> Self {} }
2433
impl From<ExcessiveSliceRequestedError > for () { fn from(_: ExcessiveSliceRequestedError) -> Self {} }
34+
#[cfg(feature = "std")] impl std::error::Error for BannedZeroSizedAllocationsError { fn description(&self) -> &str { "requested a 0-sized allocation, but those have inconsistent behavior with this allocator" } }
2535
#[cfg(feature = "std")] impl std::error::Error for ExcessiveAlignmentRequestedError { fn description(&self) -> &str { "requested more alignment than was supported" } }
2636
#[cfg(feature = "std")] impl std::error::Error for ExcessiveSliceRequestedError { fn description(&self) -> &str { "requested too many elements" } }
37+
#[cfg(allocator_api = "*")] impl From<BannedZeroSizedAllocationsError > for core::alloc::AllocError { fn from(_: BannedZeroSizedAllocationsError ) -> Self { core::alloc::AllocError } }
2738
#[cfg(allocator_api = "*")] impl From<ExcessiveAlignmentRequestedError> for core::alloc::AllocError { fn from(_: ExcessiveAlignmentRequestedError) -> Self { core::alloc::AllocError } }
2839
#[cfg(allocator_api = "*")] impl From<ExcessiveSliceRequestedError > for core::alloc::AllocError { fn from(_: ExcessiveSliceRequestedError ) -> Self { core::alloc::AllocError } }

0 commit comments

Comments
 (0)