Skip to content

Commit b2aab26

Browse files
committed
FromZeros boxed slice method supports slice DSTs
Change methods that allocate to return a new error, `AllocError`, on allocation failure rather than panicking or aborting. Makes progress on #29
1 parent 02dc876 commit b2aab26

File tree

3 files changed

+120
-61
lines changed

3 files changed

+120
-61
lines changed

src/error.rs

+14
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,20 @@ impl<Src, Dst: ?Sized + TryFromBytes> TryReadError<Src, Dst> {
570570
}
571571
}
572572

573+
/// The error type of a failed allocation.
574+
///
575+
/// This type is intended to be deprecated in favor of the standard library's
576+
/// [`AllocError`] type once it is stabilized. When that happens, this type will
577+
/// be replaced by a type alias to the standard library type. We do not intend
578+
/// to treat this as a breaking change; users who wish to avoid breakage should
579+
/// avoid writing code which assumes that this is *not* such an alias. For
580+
/// example, implementing the same trait for both types will result in an impl
581+
/// conflict once this type is an alias.
582+
///
583+
/// [`AllocError`]: https://doc.rust-lang.org/alloc/alloc/struct.AllocError.html
584+
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
585+
pub struct AllocError;
586+
573587
#[cfg(test)]
574588
mod tests {
575589
use super::*;

src/impls.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1020,7 +1020,7 @@ mod tests {
10201020
impl<T: FromBytes> TryFromBytesTestable for T {
10211021
fn with_passing_test_cases<F: Fn(Box<Self>)>(f: F) {
10221022
// Test with a zeroed value.
1023-
f(Self::new_box_zeroed());
1023+
f(Self::new_box_zeroed().unwrap());
10241024

10251025
let ffs = {
10261026
let mut t = Self::new_zeroed();

src/lib.rs

+105-60
Original file line numberDiff line numberDiff line change
@@ -2113,14 +2113,15 @@ pub unsafe trait FromZeros: TryFromBytes {
21132113
/// Note that `Box<Self>` can be converted to `Arc<Self>` and other
21142114
/// container types without reallocation.
21152115
///
2116-
/// # Panics
2116+
/// # Errors
21172117
///
2118-
/// Panics if allocation of `size_of::<Self>()` bytes fails.
2118+
/// Returns an error on allocation failure. Allocation failure is guaranteed
2119+
/// never to cause a panic or an abort.
21192120
#[must_use = "has no side effects (other than allocation)"]
21202121
#[cfg(any(feature = "alloc", test))]
21212122
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
21222123
#[inline]
2123-
fn new_box_zeroed() -> Box<Self>
2124+
fn new_box_zeroed() -> Result<Box<Self>, AllocError>
21242125
where
21252126
Self: Sized,
21262127
{
@@ -2146,22 +2147,18 @@ pub unsafe trait FromZeros: TryFromBytes {
21462147
// [2] Per https://doc.rust-lang.org/std/ptr/struct.NonNull.html#method.dangling:
21472148
//
21482149
// Creates a new `NonNull` that is dangling, but well-aligned.
2149-
unsafe {
2150-
return Box::from_raw(NonNull::dangling().as_ptr());
2151-
}
2150+
return Ok(unsafe { Box::from_raw(NonNull::dangling().as_ptr()) });
21522151
}
21532152

21542153
// TODO(#429): Add a "SAFETY" comment and remove this `allow`.
21552154
#[allow(clippy::undocumented_unsafe_blocks)]
21562155
let ptr = unsafe { alloc::alloc::alloc_zeroed(layout).cast::<Self>() };
21572156
if ptr.is_null() {
2158-
alloc::alloc::handle_alloc_error(layout);
2157+
return Err(AllocError);
21592158
}
21602159
// TODO(#429): Add a "SAFETY" comment and remove this `allow`.
21612160
#[allow(clippy::undocumented_unsafe_blocks)]
2162-
unsafe {
2163-
Box::from_raw(ptr)
2164-
}
2161+
Ok(unsafe { Box::from_raw(ptr) })
21652162
}
21662163

21672164
/// Creates a `Box<[Self]>` (a boxed slice) from zeroed bytes.
@@ -2181,22 +2178,24 @@ pub unsafe trait FromZeros: TryFromBytes {
21812178
/// actual information, but its `len()` property will report the correct
21822179
/// value.
21832180
///
2184-
/// # Panics
2181+
/// # Errors
21852182
///
2186-
/// * Panics if `size_of::<Self>() * len` overflows.
2187-
/// * Panics if allocation of `size_of::<Self>() * len` bytes fails.
2183+
/// Returns an error on allocation failure. Allocation failure is
2184+
/// guaranteed never to cause a panic or an abort.
21882185
#[must_use = "has no side effects (other than allocation)"]
21892186
#[cfg(feature = "alloc")]
21902187
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
21912188
#[inline]
2192-
fn new_box_slice_zeroed(len: usize) -> Box<[Self]>
2189+
fn new_box_zeroed_with_elems(count: usize) -> Result<Box<Self>, AllocError>
21932190
where
2194-
Self: Sized,
2191+
Self: KnownLayout<PointerMetadata = usize>,
21952192
{
2196-
let size = mem::size_of::<Self>()
2197-
.checked_mul(len)
2198-
.expect("mem::size_of::<Self>() * len overflows `usize`");
2199-
let align = mem::align_of::<Self>();
2193+
let size = match count.size_for_metadata(Self::LAYOUT) {
2194+
Some(size) => size,
2195+
None => return Err(AllocError),
2196+
};
2197+
2198+
let align = Self::LAYOUT.align.get();
22002199
// On stable Rust versions <= 1.64.0, `Layout::from_size_align` has a
22012200
// bug in which sufficiently-large allocations (those which, when
22022201
// rounded up to the alignment, overflow `isize`) are not rejected,
@@ -2205,32 +2204,74 @@ pub unsafe trait FromZeros: TryFromBytes {
22052204
// TODO(#67): Once our MSRV is > 1.64.0, remove this assertion.
22062205
#[allow(clippy::as_conversions)]
22072206
let max_alloc = (isize::MAX as usize).saturating_sub(align);
2208-
assert!(size <= max_alloc);
2207+
if size > max_alloc {
2208+
return Err(AllocError);
2209+
}
2210+
22092211
// TODO(https://github.com/rust-lang/rust/issues/55724): Use
22102212
// `Layout::repeat` once it's stabilized.
2211-
let layout =
2212-
Layout::from_size_align(size, align).expect("total allocation size overflows `isize`");
2213+
let layout = Layout::from_size_align(size, align).or(Err(AllocError))?;
22132214

22142215
let ptr = if layout.size() != 0 {
22152216
// TODO(#429): Add a "SAFETY" comment and remove this `allow`.
22162217
#[allow(clippy::undocumented_unsafe_blocks)]
2217-
let ptr = unsafe { alloc::alloc::alloc_zeroed(layout).cast::<Self>() };
2218-
if ptr.is_null() {
2219-
alloc::alloc::handle_alloc_error(layout);
2218+
let ptr = unsafe { alloc::alloc::alloc_zeroed(layout) };
2219+
match NonNull::new(ptr) {
2220+
Some(ptr) => ptr,
2221+
None => return Err(AllocError),
22202222
}
2221-
ptr
22222223
} else {
2224+
let align = Self::LAYOUT.align.get();
2225+
// We use `transmute` instead of an `as` cast since Miri (with
2226+
// strict provenance enabled) notices and complains that an `as`
2227+
// cast creates a pointer with no provenance. Miri isn't smart
2228+
// enough to realize that we're only executing this branch when
2229+
// we're constructing a zero-sized `Box`, which doesn't require
2230+
// provenance.
2231+
//
2232+
// SAFETY: any initialized bit sequence is a bit-valid `*mut u8`.
2233+
// All bits of a `usize` are initialized.
2234+
#[allow(clippy::useless_transmute)]
2235+
let dangling = unsafe { mem::transmute::<usize, *mut u8>(align) };
2236+
// SAFETY: `dangling` is constructed from `Self::LAYOUT.align`,
2237+
// which is a `NonZeroUsize`, which is guaranteed to be non-zero.
2238+
//
22232239
// `Box<[T]>` does not allocate when `T` is zero-sized or when `len`
22242240
// is zero, but it does require a non-null dangling pointer for its
22252241
// allocation.
2226-
NonNull::<Self>::dangling().as_ptr()
2242+
//
2243+
// TODO(https://github.com/rust-lang/rust/issues/95228): Use
2244+
// `std::ptr::without_provenance` once it's stable. That may
2245+
// optimize better. As written, Rust may assume that this consumes
2246+
// "exposed" provenance, and thus Rust may have to assume that this
2247+
// may consume provenance from any pointer whose provenance has been
2248+
// exposed.
2249+
#[allow(fuzzy_provenance_casts)]
2250+
unsafe {
2251+
NonNull::new_unchecked(dangling)
2252+
}
22272253
};
22282254

2229-
// TODO(#429): Add a "SAFETY" comment and remove this `allow`.
2255+
let ptr = Self::raw_from_ptr_len(ptr, count);
2256+
2257+
// TODO(#429): Add a "SAFETY" comment and remove this `allow`. Make sure
2258+
// to include a justification that `ptr.as_ptr()` is validly-aligned in
2259+
// the ZST case (in which we manually construct a dangling pointer).
22302260
#[allow(clippy::undocumented_unsafe_blocks)]
2231-
unsafe {
2232-
Box::from_raw(slice::from_raw_parts_mut(ptr, len))
2233-
}
2261+
Ok(unsafe { Box::from_raw(ptr.as_ptr()) })
2262+
}
2263+
2264+
#[deprecated(since = "0.8.0", note = "renamed to `FromZeros::new_box_zeroed_with_elems`")]
2265+
#[doc(hidden)]
2266+
#[cfg(feature = "alloc")]
2267+
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
2268+
#[must_use = "has no side effects (other than allocation)"]
2269+
#[inline(always)]
2270+
fn new_box_slice_zeroed(len: usize) -> Result<Box<[Self]>, AllocError>
2271+
where
2272+
Self: Sized,
2273+
{
2274+
<[Self]>::new_box_zeroed_with_elems(len)
22342275
}
22352276

22362277
/// Creates a `Vec<Self>` from zeroed bytes.
@@ -2249,19 +2290,19 @@ pub unsafe trait FromZeros: TryFromBytes {
22492290
/// actual information, but its `len()` property will report the correct
22502291
/// value.
22512292
///
2252-
/// # Panics
2293+
/// # Errors
22532294
///
2254-
/// * Panics if `size_of::<Self>() * len` overflows.
2255-
/// * Panics if allocation of `size_of::<Self>() * len` bytes fails.
2295+
/// Returns an error on allocation failure. Allocation failure is
2296+
/// guaranteed never to cause a panic or an abort.
22562297
#[must_use = "has no side effects (other than allocation)"]
22572298
#[cfg(feature = "alloc")]
22582299
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
22592300
#[inline(always)]
2260-
fn new_vec_zeroed(len: usize) -> Vec<Self>
2301+
fn new_vec_zeroed(len: usize) -> Result<Vec<Self>, AllocError>
22612302
where
22622303
Self: Sized,
22632304
{
2264-
Self::new_box_slice_zeroed(len).into()
2305+
<[Self]>::new_box_zeroed_with_elems(len).map(Into::into)
22652306
}
22662307
}
22672308

@@ -4972,7 +5013,7 @@ mod alloc_support {
49725013

49735014
#[test]
49745015
fn test_new_box_zeroed() {
4975-
assert_eq!(*u64::new_box_zeroed(), 0);
5016+
assert_eq!(u64::new_box_zeroed(), Ok(Box::new(0)));
49765017
}
49775018

49785019
#[test]
@@ -4986,28 +5027,28 @@ mod alloc_support {
49865027
// when running under Miri.
49875028
#[allow(clippy::unit_cmp)]
49885029
{
4989-
assert_eq!(*<()>::new_box_zeroed(), ());
5030+
assert_eq!(<()>::new_box_zeroed(), Ok(Box::new(())));
49905031
}
49915032
}
49925033

49935034
#[test]
4994-
fn test_new_box_slice_zeroed() {
4995-
let mut s: Box<[u64]> = u64::new_box_slice_zeroed(3);
5035+
fn test_new_box_zeroed_with_elems() {
5036+
let mut s: Box<[u64]> = <[u64]>::new_box_zeroed_with_elems(3).unwrap();
49965037
assert_eq!(s.len(), 3);
49975038
assert_eq!(&*s, &[0, 0, 0]);
49985039
s[1] = 3;
49995040
assert_eq!(&*s, &[0, 3, 0]);
50005041
}
50015042

50025043
#[test]
5003-
fn test_new_box_slice_zeroed_empty() {
5004-
let s: Box<[u64]> = u64::new_box_slice_zeroed(0);
5044+
fn test_new_box_zeroed_with_elems_empty() {
5045+
let s: Box<[u64]> = <[u64]>::new_box_zeroed_with_elems(0).unwrap();
50055046
assert_eq!(s.len(), 0);
50065047
}
50075048

50085049
#[test]
5009-
fn test_new_box_slice_zeroed_zst() {
5010-
let mut s: Box<[()]> = <()>::new_box_slice_zeroed(3);
5050+
fn test_new_box_zeroed_with_elems_zst() {
5051+
let mut s: Box<[()]> = <[()]>::new_box_zeroed_with_elems(3).unwrap();
50115052
assert_eq!(s.len(), 3);
50125053
assert!(s.get(10).is_none());
50135054
// This test exists in order to exercise unsafe code, especially
@@ -5020,22 +5061,20 @@ mod alloc_support {
50205061
}
50215062

50225063
#[test]
5023-
fn test_new_box_slice_zeroed_zst_empty() {
5024-
let s: Box<[()]> = <()>::new_box_slice_zeroed(0);
5064+
fn test_new_box_zeroed_with_elems_zst_empty() {
5065+
let s: Box<[()]> = <[()]>::new_box_zeroed_with_elems(0).unwrap();
50255066
assert_eq!(s.len(), 0);
50265067
}
50275068

50285069
#[test]
5029-
#[should_panic(expected = "mem::size_of::<Self>() * len overflows `usize`")]
5030-
fn test_new_box_slice_zeroed_panics_mul_overflow() {
5031-
let _ = u16::new_box_slice_zeroed(usize::MAX);
5032-
}
5070+
fn new_box_zeroed_with_elems_errors() {
5071+
assert_eq!(<[u16]>::new_box_zeroed_with_elems(usize::MAX), Err(AllocError));
50335072

5034-
#[test]
5035-
#[should_panic(expected = "assertion failed: size <= max_alloc")]
5036-
fn test_new_box_slice_zeroed_panics_isize_overflow() {
50375073
let max = usize::try_from(isize::MAX).unwrap();
5038-
let _ = u16::new_box_slice_zeroed((max / mem::size_of::<u16>()) + 1);
5074+
assert_eq!(
5075+
<[u16]>::new_box_zeroed_with_elems((max / mem::size_of::<u16>()) + 1),
5076+
Err(AllocError)
5077+
);
50395078
}
50405079
}
50415080
}
@@ -5525,14 +5564,20 @@ mod tests {
55255564

55265565
#[cfg(feature = "alloc")]
55275566
{
5528-
assert_eq!(bool::new_box_zeroed(), Box::new(false));
5529-
assert_eq!(char::new_box_zeroed(), Box::new('\0'));
5567+
assert_eq!(bool::new_box_zeroed(), Ok(Box::new(false)));
5568+
assert_eq!(char::new_box_zeroed(), Ok(Box::new('\0')));
55305569

5531-
assert_eq!(bool::new_box_slice_zeroed(3).as_ref(), [false, false, false]);
5532-
assert_eq!(char::new_box_slice_zeroed(3).as_ref(), ['\0', '\0', '\0']);
5570+
assert_eq!(
5571+
<[bool]>::new_box_zeroed_with_elems(3).unwrap().as_ref(),
5572+
[false, false, false]
5573+
);
5574+
assert_eq!(
5575+
<[char]>::new_box_zeroed_with_elems(3).unwrap().as_ref(),
5576+
['\0', '\0', '\0']
5577+
);
55335578

5534-
assert_eq!(bool::new_vec_zeroed(3).as_ref(), [false, false, false]);
5535-
assert_eq!(char::new_vec_zeroed(3).as_ref(), ['\0', '\0', '\0']);
5579+
assert_eq!(bool::new_vec_zeroed(3).unwrap().as_ref(), [false, false, false]);
5580+
assert_eq!(char::new_vec_zeroed(3).unwrap().as_ref(), ['\0', '\0', '\0']);
55365581
}
55375582

55385583
let mut string = "hello".to_string();

0 commit comments

Comments
 (0)