Skip to content

Commit 10f91d4

Browse files
committed
fix Box potential UB and other issues, add Box::assume_init and helpers::is_aligned, centralize ALN constant type property
1 parent f353ff7 commit 10f91d4

11 files changed

Lines changed: 222 additions & 180 deletions

File tree

bench.sh

100755100644
File mode changed.

bin/alloc_many.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use ::core::sync::atomic::{AtomicBool, Ordering};
21
use {
2+
::core::sync::atomic::{AtomicBool, Ordering},
33
memapi2::{helpers::ptr_max_align, prelude::*},
44
std::{
55
env,
@@ -13,7 +13,8 @@ fn main() {
1313

1414
ctrlc::set_handler(move || {
1515
BREAK.store(true, Ordering::Relaxed);
16-
}).expect("failed to set ctrl-c handler");
16+
})
17+
.expect("failed to set ctrl-c handler");
1718

1819
let o = stdout();
1920
let i = stdin();
@@ -71,7 +72,9 @@ fn main() {
7172
total_bytes_allocated += layout.size() as u128;
7273
if layout.size() > max_success_size {
7374
max_success_size = layout.size();
74-
max_success_size_align = layout.align();
75+
if layout.align() > max_success_size_align {
76+
max_success_size_align = layout.align();
77+
}
7578
}
7679
if layout.align() > max_success_align {
7780
max_success_align = layout.align();

src/data/boxed.rs

Lines changed: 71 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ use {
77
alloc_mut::BasicAllocMut,
88
data::{
99
marker::UnsizedCopy,
10-
type_props::{PtrProps, SizedProps, VarSized, varsized_nonnull_from_parts}
10+
type_props::{
11+
KnownAlign,
12+
PtrProps,
13+
SizedProps,
14+
VarSized,
15+
varsized_nonnull_from_parts
16+
}
1117
}
1218
}
1319
},
@@ -16,7 +22,7 @@ use {
1622
default::Default,
1723
fmt::{Debug, Display, Formatter, Result as FmtResult},
1824
marker::{PhantomData, Send, Sized, Sync},
19-
mem::MaybeUninit,
25+
mem::{ManuallyDrop, MaybeUninit},
2026
ops::{Deref, DerefMut, Drop},
2127
panic,
2228
ptr::{self, NonNull},
@@ -25,15 +31,20 @@ use {
2531
}
2632
};
2733

34+
// TODO: box_all_unsized which adds support for trait objects and all unsized types, even those that
35+
// don't implement VarSized.
36+
2837
#[inline]
29-
fn unwrap_fail<T: ?Sized, A: BasicAllocMut, E: Display>(r: Result<Box<T, A>, E>) -> Box<T, A> {
38+
fn unwrap_fail<T: ?Sized + KnownAlign, A: BasicAllocMut, E: Display>(
39+
r: Result<Box<T, A>, E>
40+
) -> Box<T, A> {
3041
match r {
3142
Ok(b) => b,
3243
Err(e) => panic!("allocation for `Box` failed: {}", e)
3344
}
3445
}
3546

36-
pub struct Box<T: ?Sized, A: BasicAllocMut = DefaultAlloc> {
47+
pub struct Box<T: ?Sized + KnownAlign, A: BasicAllocMut = DefaultAlloc> {
3748
ptr: NonNull<T>,
3849
alloc: A,
3950
_marker: PhantomData<T>
@@ -42,10 +53,12 @@ pub struct Box<T: ?Sized, A: BasicAllocMut = DefaultAlloc> {
4253
pub enum BoxErr<E: Debug + Display + From<Error>> {
4354
AllocError(E),
4455
NullPtr,
45-
DanglingPtr(*const ())
56+
DanglingPtr(usize)
4657
}
4758

59+
// SAFETY: we own the `T` instance/as `Unique` puts it, "the data [this points to] is unaliased."
4860
unsafe impl<T: Send, A: BasicAllocMut + Send> Send for Box<T, A> {}
61+
// SAFETY: above
4962
unsafe impl<T: Sync, A: BasicAllocMut + Sync> Sync for Box<T, A> {}
5063

5164
impl<E: Debug + Display + From<Error>> Display for BoxErr<E> {
@@ -94,12 +107,13 @@ impl<T, A: BasicAllocMut> Box<T, A> {
94107
data: T,
95108
alloc: A
96109
) -> Result<Box<T, A>, BoxErr<<A as AllocDescriptor>::Error>> {
97-
let mut alloc = alloc;
98-
let ptr = tri!(wrap(BoxErr::AllocError) alloc.alloc_mut(T::LAYOUT)).cast();
110+
let mut boxed = tri!(do Box::try_new_uninit_in(alloc));
111+
// SAFETY: alloc traits always return a valid pointer.
99112
unsafe {
100-
ptr::write(ptr.as_ptr(), data);
113+
ptr::write(boxed.as_mut_ptr(), data);
101114
}
102-
Ok(Box { ptr, alloc, _marker: PhantomData })
115+
// SAFETY: we just initialized the box
116+
Ok(unsafe { boxed.assume_init() })
103117
}
104118
pub fn try_new_uninit_in(
105119
alloc: A
@@ -110,9 +124,18 @@ impl<T, A: BasicAllocMut> Box<T, A> {
110124
}
111125
}
112126

113-
impl<T: ?Sized> Box<T> {
127+
impl<T: KnownAlign, A: BasicAllocMut> Box<MaybeUninit<T>, A> {
128+
// at least for now, this has no reason to be const
129+
pub unsafe fn assume_init(self) -> Box<T, A> {
130+
let no_drop = ManuallyDrop::new(self);
131+
Box { ptr: no_drop.ptr.cast(), alloc: ptr::read(&no_drop.alloc), _marker: PhantomData }
132+
}
133+
}
134+
135+
impl<T: ?Sized + KnownAlign> Box<T> {
136+
#[::rustversion::attr(since(1.61), const)]
114137
#[allow(clippy::must_use_candidate)]
115-
pub const unsafe fn from_raw(ptr: NonNull<T>) -> Box<T> {
138+
pub unsafe fn from_raw(ptr: NonNull<T>) -> Box<T> {
116139
Box::from_raw_in(ptr, DefaultAlloc)
117140
}
118141

@@ -134,19 +157,22 @@ impl<T: ?Sized> Box<T> {
134157
where
135158
T: UnsizedCopy + VarSized
136159
{
160+
// SAFETY: caller guarantee
137161
unsafe { Box::copy_from_ptr_in(p, DefaultAlloc) }
138162
}
139163

140164
pub unsafe fn try_copy_from_ptr(p: *const T) -> Result<Box<T>, BoxErr<Error>>
141165
where
142166
T: UnsizedCopy + VarSized
143167
{
168+
// SAFETY: caller guarantee
144169
unsafe { Box::try_copy_from_ptr_in(p, DefaultAlloc) }
145170
}
146171
}
147172

148-
impl<T: ?Sized, A: BasicAllocMut> Box<T, A> {
149-
pub const unsafe fn from_raw_in(ptr: NonNull<T>, alloc: A) -> Box<T, A> {
173+
impl<T: ?Sized + KnownAlign, A: BasicAllocMut> Box<T, A> {
174+
#[::rustversion::attr(since(1.61), const)]
175+
pub unsafe fn from_raw_in(ptr: NonNull<T>, alloc: A) -> Box<T, A> {
150176
Box { ptr, alloc, _marker: PhantomData }
151177
}
152178

@@ -158,19 +184,20 @@ impl<T: ?Sized, A: BasicAllocMut> Box<T, A> {
158184
}
159185

160186
pub fn try_copy_from_ref_in(
161-
r: &T,
162-
alloc: A
187+
_r: &T,
188+
_alloc: A
163189
) -> Result<Box<T, A>, BoxErr<<A as AllocDescriptor>::Error>>
164190
where
165191
T: UnsizedCopy + VarSized
166192
{
167-
unsafe { Box::try_copy_from_ptr_in(r, alloc) }
193+
::core::todo!()
168194
}
169195

170196
pub unsafe fn copy_from_ptr_in(p: *const T, alloc: A) -> Box<T, A>
171197
where
172198
T: UnsizedCopy + VarSized
173199
{
200+
// SAFETY: caller guarantee
174201
unwrap_fail(unsafe { Box::try_copy_from_ptr_in(p, alloc) })
175202
}
176203

@@ -181,40 +208,61 @@ impl<T: ?Sized, A: BasicAllocMut> Box<T, A> {
181208
where
182209
T: UnsizedCopy + VarSized
183210
{
211+
let p_addr = p.cast::<()>() as usize;
212+
if p.is_null() {
213+
return Err(BoxErr::NullPtr);
214+
} else if p_addr == T::ALN {
215+
return Err(BoxErr::DanglingPtr(p_addr));
216+
} // we purposefully don't check for alignment here.
217+
184218
let mut alloc = alloc;
185219

220+
// SAFETY: caller guarantee
186221
let sz = unsafe { p.sz() };
187222
let ptr = tri!(wrap(BoxErr::AllocError) alloc.alloc_mut(p.layout()));
188-
223+
// SAFETY: `alloc` returns a pointer to at least the `p.sz()` bytes
189224
unsafe {
190225
ptr::copy_nonoverlapping(p.cast::<u8>(), ptr.as_ptr(), sz);
191226
}
192227
Ok(Box { ptr: varsized_nonnull_from_parts(ptr, sz), alloc, _marker: PhantomData })
193228
}
194229
}
195230

196-
impl<T: ?Sized, A: BasicAllocMut> Deref for Box<T, A> {
231+
impl<T: ?Sized + KnownAlign, A: BasicAllocMut> Deref for Box<T, A> {
197232
type Target = T;
198233

199234
fn deref(&self) -> &T {
235+
// SAFETY: `self.ptr` is always a pointer to a valid `T` unless a `from_raw` function had
236+
// its safety contract broken.
200237
unsafe { &*self.ptr.as_ptr() }
201238
}
202239
}
203240

204-
impl<T: ?Sized, A: BasicAllocMut> DerefMut for Box<T, A> {
241+
impl<T: ?Sized + KnownAlign, A: BasicAllocMut> DerefMut for Box<T, A> {
205242
fn deref_mut(&mut self) -> &mut T {
243+
// SAFETY: `self.ptr` is always a pointer to a valid `T` unless a `from_raw` function had
244+
// its safety contract broken; we own the `T`.
206245
unsafe { &mut *self.ptr.as_ptr() }
207246
}
208247
}
209248

210-
impl<T: ?Sized, A: BasicAllocMut> Drop for Box<T, A> {
249+
impl<T: ?Sized + KnownAlign, A: BasicAllocMut> Drop for Box<T, A> {
211250
fn drop(&mut self) {
251+
// SAFETY: `self.ptr` is always a pointer to a valid `T` unless a `from_raw` function had
252+
// its safety contract broken; we own the `T`
212253
unsafe {
213254
ptr::drop_in_place(self.ptr.as_ptr());
214255
}
215-
let layout = unsafe { self.ptr.layout() };
216-
unsafe {
217-
self.alloc.dealloc_mut(self.ptr.cast(), layout);
256+
// TODO: we can make PtrProps functions like layout() fallible and safe with the new
257+
// KnownAlign::ALN
258+
if self.ptr.as_ptr().cast::<()>() as usize != T::ALN {
259+
// SAFETY: the pointer is non-null, we just validated it isn't dangling, and `alloc`
260+
// only returns aligned memory.
261+
let layout = unsafe { self.ptr.layout() };
262+
// SAFETY: we allocated the pointer
263+
unsafe {
264+
self.alloc.dealloc_mut(self.ptr.cast(), layout);
265+
}
218266
}
219267
}
220268
}

src/helpers.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,17 @@ pub fn ptr_max_align(ptr: NonNull<u8>) -> usize {
8383
p & p.wrapping_neg()
8484
}
8585

86+
/// Returns `true` if `ptr` is aligned to `align`.
87+
///
88+
/// # Safety
89+
///
90+
/// This function is safe to call, but the returned value may be incorrect if `align` is not a power
91+
/// of two. An underflow may also occur if `align == 0`.
92+
#[must_use]
93+
pub unsafe fn is_aligned(ptr: *const u8, align: usize) -> bool {
94+
ptr as usize & (align - 1) == 0
95+
}
96+
8697
/// Creates a <code>[NonNull]<\[T\]></code> from a pointer and a length.
8798
///
8899
/// Note that this is only `const` on Rust versions 1.61 and above.

src/layout.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::traits::data::type_props::KnownAlign;
12
use {
23
crate::{
34
error::{ArithErr, ArithOp, LayoutErr},
@@ -96,7 +97,7 @@ impl Layout {
9697
///
9798
/// # Errors
9899
///
99-
/// <code>Err([LayoutErr::ExceedsMax]\([T::SZ](SizedProps::SZ), [T::ALN](SizedProps::ALN),
100+
/// <code>Err([LayoutErr::ExceedsMax]\([T::SZ](SizedProps::SZ), [T::ALN](KnownAlign::ALN),
100101
/// n\))</code> if the length of the computed array, in bytes, would
101102
/// exceed [`USIZE_MAX_NO_HIGH_BIT`].
102103
pub const fn array<T>(n: usize) -> Result<Layout, LayoutErr> {
@@ -114,7 +115,7 @@ impl Layout {
114115
/// # Safety
115116
///
116117
/// The caller must ensure that <code>[T::SZ](SizedProps::SZ) * n</code> rounded up to
117-
/// [`T::ALN`](SizedProps::ALN) will not exceed [`USIZE_MAX_NO_HIGH_BIT`].
118+
/// [`T::ALN`](KnownAlign::ALN) will not exceed [`USIZE_MAX_NO_HIGH_BIT`].
118119
///
119120
/// Additionally, the return value may be unexpected if <code>[T::SZ](SizedProps::SZ) * n</code>
120121
/// overflows.

src/lib.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@
3434
//!
3535
//! # Allocator implementations
3636
//! - [`DefaultAlloc`] (available unless `no_alloc` is enabled and `std` isn't)
37-
//! - [`System`](::std::alloc::System) when the `std` feature is enabled
37+
//! - `::std::alloc::System` when the `std` feature is enabled
3838
//! - [`CAlloc`](allocs::c_alloc::CAlloc) behind the `c_alloc` feature
3939
//! - [`StackAlloc`](allocs::stack_alloc::StackAlloc) behind the `stack_alloc` feature
4040
//!
4141
//! # Feature flags
4242
//! - `no_alloc` disables usage of the rust `alloc` crate; `std` crate is used instead if the `std`
4343
//! feature is enabled
44-
//! - `std`: enables `std` integration (including [`System`](::std::alloc::System))
44+
//! - `std`: enables `std` integration (including `::std::alloc::System`)
4545
//! - `os_err_reporting`: best-effort OS error reporting via `errno` (requires `std`)
4646
//! - `alloc_temp_trait`: scoped/temporary allocation trait
4747
//! - `c_alloc`: C `posix_memalign`-style allocator ([`allocs::c_alloc`])
@@ -104,12 +104,11 @@ pub mod prelude {
104104
BasicAllocMut,
105105
DeallocMut,
106106
FullAllocMut,
107-
108107
ReallocMut,
109-
},
108+
},
110109
data::{
111110
marker::UnsizedCopy,
112-
type_props::{PtrProps, SizedProps}
111+
type_props::{KnownAlign, PtrProps, SizedProps}
113112
},
114113
AllocDescriptor
115114
},

src/traits/alloc.rs

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use {
1515
}
1616
};
1717

18-
#[allow(unused_imports)] use crate::error::Cause;
18+
#[allow(unused_imports)] use crate::error::{Cause, Error};
1919

2020
/// A memory allocation interface.
2121
pub trait Alloc: AllocDescriptor + AllocMut {
@@ -32,14 +32,11 @@ pub trait Alloc: AllocDescriptor + AllocMut {
3232
/// - <code>Err([Error::AllocFailed]\(layout, cause\))</code> if allocation fails. `cause` is
3333
/// typically [`Cause::Unknown`]. If the `os_err_reporting` feature is enabled, it will be
3434
/// <code>[Cause::OSErr]\(oserr\)</code>. In this case, `oserr` will be the error from
35-
/// <code>[last_os_error]\(\).[raw_os_error]\(\)</code>.
35+
/// `::std::io::Error::last_os_error().raw_os_error()`.
3636
/// - <code>Err([Error::Other]\(err\))</code> for allocator-specific failures. If the
3737
/// `alloc_mut` feature is enabled, and using this method on a synchronization primitive
3838
/// wrapping a type which implements [`AllocMut`], a generic error message will also be
3939
/// returned if locking the primitive fails.
40-
///
41-
/// [last_os_error]: ::std::io::Error::last_os_error
42-
/// [raw_os_error]: ::std::io::Error::raw_os_error
4340
fn alloc(&self, layout: Layout) -> Result<NonNull<u8>, <Self as AllocDescriptor>::Error>;
4441

4542
/// Attempts to allocate a zeroed block of memory fitting the given [`Layout`].
@@ -55,14 +52,11 @@ pub trait Alloc: AllocDescriptor + AllocMut {
5552
/// - <code>Err([Error::AllocFailed]\(layout, cause\))</code> if allocation fails. `cause` is
5653
/// typically [`Cause::Unknown`]. If the `os_err_reporting` feature is enabled, it will be
5754
/// <code>[Cause::OSErr]\(oserr\)</code>. In this case, `oserr` will be the error from
58-
/// <code>[last_os_error]\(\).[raw_os_error]\(\)</code>.
55+
/// `::std::io::Error::last_os_error().raw_os_error()`.
5956
/// - <code>Err([Error::Other]\(err\))</code> for allocator-specific failures. If the
6057
/// `alloc_mut` feature is enabled, and using this method on a synchronization primitive
6158
/// wrapping a type which implements [`AllocMut`], a generic error message will also be
6259
/// returned if locking the primitive fails.
63-
///
64-
/// [last_os_error]: ::std::io::Error::last_os_error
65-
/// [raw_os_error]: ::std::io::Error::raw_os_error
6660
#[cfg_attr(miri, track_caller)]
6761
#[inline]
6862
fn zalloc(&self, layout: Layout) -> Result<NonNull<u8>, <Self as AllocDescriptor>::Error> {
@@ -168,16 +162,13 @@ pub trait Realloc: ReallocMut + Dealloc {
168162
/// - <code>Err([Error::AllocFailed]\(layout, cause\))</code> if allocation fails. `cause` is
169163
/// typically [`Cause::Unknown`]. If the `os_err_reporting` feature is enabled, it will be
170164
/// <code>[Cause::OSErr]\(oserr\)</code>. In this case, `oserr` will be the error from
171-
/// <code>[last_os_error]\(\).[raw_os_error]\(\)</code>.
165+
/// `::std::io::Error::last_os_error().raw_os_error()`.
172166
/// - <code>Err([Error::ReallocSmallerAlign]\(old, new\))</code> if
173167
/// <code>old_layout.[align](Layout::align)() > new_layout.[align](Layout::align)()</code>.
174168
/// - <code>Err([Error::Other]\(err\))</code> for allocator-specific failures. If the
175169
/// `alloc_mut` feature is enabled, and using this method on a synchronization primitive
176170
/// wrapping a type which implements [`ReallocMut`], a generic error message will also be
177171
/// returned if locking the primitive fails.
178-
///
179-
/// [last_os_error]: ::std::io::Error::last_os_error
180-
/// [raw_os_error]: ::std::io::Error::raw_os_error
181172
#[cfg_attr(miri, track_caller)]
182173
#[inline]
183174
unsafe fn realloc(
@@ -219,16 +210,13 @@ pub trait Realloc: ReallocMut + Dealloc {
219210
/// - <code>Err([Error::AllocFailed]\(layout, cause\))</code> if allocation fails. `cause` is
220211
/// typically [`Cause::Unknown`]. If the `os_err_reporting` feature is enabled, it will be
221212
/// <code>[Cause::OSErr]\(oserr\)</code>. In this case, `oserr` will be the error from
222-
/// <code>[last_os_error]\(\).[raw_os_error]\(\)</code>.
213+
/// `::std::io::Error::last_os_error().raw_os_error()`.
223214
/// - <code>Err([Error::ReallocSmallerAlign]\(old, new\))</code> if
224215
/// <code>old_layout.[align](Layout::align)() > new_layout.[align](Layout::align)()</code>.
225216
/// - <code>Err([Error::Other]\(err\))</code> for allocator-specific failures. If the
226217
/// `alloc_mut` feature is enabled, and using this method on a synchronization primitive
227218
/// wrapping a type which implements [`ReallocMut`], a generic error message will also be
228219
/// returned if locking the primitive fails.
229-
///
230-
/// [last_os_error]: ::std::io::Error::last_os_error
231-
/// [raw_os_error]: ::std::io::Error::raw_os_error
232220
#[cfg_attr(miri, track_caller)]
233221
#[inline]
234222
unsafe fn rezalloc(

0 commit comments

Comments
 (0)