Skip to content

Commit 9fa6f18

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 9fa6f18

File tree

3 files changed

+119
-61
lines changed

3 files changed

+119
-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

+104-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,73 @@ 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+
let dangling = unsafe { mem::transmute::<usize, *mut u8>(align) };
2235+
// SAFETY: `dangling` is constructed from `Self::LAYOUT.align`,
2236+
// which is a `NonZeroUsize`, which is guaranteed to be non-zero.
2237+
//
22232238
// `Box<[T]>` does not allocate when `T` is zero-sized or when `len`
22242239
// is zero, but it does require a non-null dangling pointer for its
22252240
// allocation.
2226-
NonNull::<Self>::dangling().as_ptr()
2241+
//
2242+
// TODO(https://github.com/rust-lang/rust/issues/95228): Use
2243+
// `std::ptr::without_provenance` once it's stable. That may
2244+
// optimize better. As written, Rust may assume that this consumes
2245+
// "exposed" provenance, and thus Rust may have to assume that this
2246+
// may consume provenance from any pointer whose provenance has been
2247+
// exposed.
2248+
#[allow(fuzzy_provenance_casts)]
2249+
unsafe {
2250+
NonNull::new_unchecked(dangling)
2251+
}
22272252
};
22282253

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

22362276
/// Creates a `Vec<Self>` from zeroed bytes.
@@ -2249,19 +2289,19 @@ pub unsafe trait FromZeros: TryFromBytes {
22492289
/// actual information, but its `len()` property will report the correct
22502290
/// value.
22512291
///
2252-
/// # Panics
2292+
/// # Errors
22532293
///
2254-
/// * Panics if `size_of::<Self>() * len` overflows.
2255-
/// * Panics if allocation of `size_of::<Self>() * len` bytes fails.
2294+
/// Returns an error on allocation failure. Allocation failure is
2295+
/// guaranteed never to cause a panic or an abort.
22562296
#[must_use = "has no side effects (other than allocation)"]
22572297
#[cfg(feature = "alloc")]
22582298
#[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))]
22592299
#[inline(always)]
2260-
fn new_vec_zeroed(len: usize) -> Vec<Self>
2300+
fn new_vec_zeroed(len: usize) -> Result<Vec<Self>, AllocError>
22612301
where
22622302
Self: Sized,
22632303
{
2264-
Self::new_box_slice_zeroed(len).into()
2304+
<[Self]>::new_box_zeroed_with_elems(len).map(Into::into)
22652305
}
22662306
}
22672307

@@ -4972,7 +5012,7 @@ mod alloc_support {
49725012

49735013
#[test]
49745014
fn test_new_box_zeroed() {
4975-
assert_eq!(*u64::new_box_zeroed(), 0);
5015+
assert_eq!(u64::new_box_zeroed(), Ok(Box::new(0)));
49765016
}
49775017

49785018
#[test]
@@ -4986,28 +5026,28 @@ mod alloc_support {
49865026
// when running under Miri.
49875027
#[allow(clippy::unit_cmp)]
49885028
{
4989-
assert_eq!(*<()>::new_box_zeroed(), ());
5029+
assert_eq!(<()>::new_box_zeroed(), Ok(Box::new(())));
49905030
}
49915031
}
49925032

49935033
#[test]
4994-
fn test_new_box_slice_zeroed() {
4995-
let mut s: Box<[u64]> = u64::new_box_slice_zeroed(3);
5034+
fn test_new_box_zeroed_with_elems() {
5035+
let mut s: Box<[u64]> = <[u64]>::new_box_zeroed_with_elems(3).unwrap();
49965036
assert_eq!(s.len(), 3);
49975037
assert_eq!(&*s, &[0, 0, 0]);
49985038
s[1] = 3;
49995039
assert_eq!(&*s, &[0, 3, 0]);
50005040
}
50015041

50025042
#[test]
5003-
fn test_new_box_slice_zeroed_empty() {
5004-
let s: Box<[u64]> = u64::new_box_slice_zeroed(0);
5043+
fn test_new_box_zeroed_with_elems_empty() {
5044+
let s: Box<[u64]> = <[u64]>::new_box_zeroed_with_elems(0).unwrap();
50055045
assert_eq!(s.len(), 0);
50065046
}
50075047

50085048
#[test]
5009-
fn test_new_box_slice_zeroed_zst() {
5010-
let mut s: Box<[()]> = <()>::new_box_slice_zeroed(3);
5049+
fn test_new_box_zeroed_with_elems_zst() {
5050+
let mut s: Box<[()]> = <[()]>::new_box_zeroed_with_elems(3).unwrap();
50115051
assert_eq!(s.len(), 3);
50125052
assert!(s.get(10).is_none());
50135053
// This test exists in order to exercise unsafe code, especially
@@ -5020,22 +5060,20 @@ mod alloc_support {
50205060
}
50215061

50225062
#[test]
5023-
fn test_new_box_slice_zeroed_zst_empty() {
5024-
let s: Box<[()]> = <()>::new_box_slice_zeroed(0);
5063+
fn test_new_box_zeroed_with_elems_zst_empty() {
5064+
let s: Box<[()]> = <[()]>::new_box_zeroed_with_elems(0).unwrap();
50255065
assert_eq!(s.len(), 0);
50265066
}
50275067

50285068
#[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-
}
5069+
fn new_box_zeroed_with_elems_errors() {
5070+
assert_eq!(<[u16]>::new_box_zeroed_with_elems(usize::MAX), Err(AllocError));
50335071

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

55265564
#[cfg(feature = "alloc")]
55275565
{
5528-
assert_eq!(bool::new_box_zeroed(), Box::new(false));
5529-
assert_eq!(char::new_box_zeroed(), Box::new('\0'));
5566+
assert_eq!(bool::new_box_zeroed(), Ok(Box::new(false)));
5567+
assert_eq!(char::new_box_zeroed(), Ok(Box::new('\0')));
55305568

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']);
5569+
assert_eq!(
5570+
<[bool]>::new_box_zeroed_with_elems(3).unwrap().as_ref(),
5571+
[false, false, false]
5572+
);
5573+
assert_eq!(
5574+
<[char]>::new_box_zeroed_with_elems(3).unwrap().as_ref(),
5575+
['\0', '\0', '\0']
5576+
);
55335577

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']);
5578+
assert_eq!(bool::new_vec_zeroed(3).unwrap().as_ref(), [false, false, false]);
5579+
assert_eq!(char::new_vec_zeroed(3).unwrap().as_ref(), ['\0', '\0', '\0']);
55365580
}
55375581

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

0 commit comments

Comments
 (0)