Skip to content

Commit

Permalink
Add KnownLayout::MaybeUninit type
Browse files Browse the repository at this point in the history
Closes #1797
  • Loading branch information
joshlf authored and jswrenn committed Oct 8, 2024
1 parent fa7d56d commit d5c2b36
Show file tree
Hide file tree
Showing 8 changed files with 1,357 additions and 1,019 deletions.
1 change: 1 addition & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ fn main() {
println!(
"cargo:rustc-check-cfg=cfg(__ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS)"
);
println!("cargo:rustc-check-cfg=cfg(zerocopy_unstable)");
println!("cargo:rustc-check-cfg=cfg(coverage_nightly)");
}

Expand Down
30 changes: 16 additions & 14 deletions src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
// This file may not be copied, modified, or distributed except according to
// those terms.

use core::mem::MaybeUninit as CoreMaybeUninit;

use super::*;

safety_comment! {
Expand Down Expand Up @@ -630,14 +632,14 @@ safety_comment! {
/// SAFETY:
/// `TryFromBytes` (with no validator), `FromZeros`, `FromBytes`:
/// `MaybeUninit<T>` has no restrictions on its contents.
unsafe_impl!(T => TryFromBytes for MaybeUninit<T>);
unsafe_impl!(T => FromZeros for MaybeUninit<T>);
unsafe_impl!(T => FromBytes for MaybeUninit<T>);
unsafe_impl!(T => TryFromBytes for CoreMaybeUninit<T>);
unsafe_impl!(T => FromZeros for CoreMaybeUninit<T>);
unsafe_impl!(T => FromBytes for CoreMaybeUninit<T>);
}

impl_for_transparent_wrapper!(T: Immutable => Immutable for MaybeUninit<T>);
impl_for_transparent_wrapper!(T: Unaligned => Unaligned for MaybeUninit<T>);
assert_unaligned!(MaybeUninit<()>, MaybeUninit<u8>);
impl_for_transparent_wrapper!(T: Immutable => Immutable for CoreMaybeUninit<T>);
impl_for_transparent_wrapper!(T: Unaligned => Unaligned for CoreMaybeUninit<T>);
assert_unaligned!(CoreMaybeUninit<()>, CoreMaybeUninit<u8>);

impl_for_transparent_wrapper!(T: ?Sized + Immutable => Immutable for ManuallyDrop<T>);
impl_for_transparent_wrapper!(T: ?Sized + TryFromBytes => TryFromBytes for ManuallyDrop<T>);
Expand Down Expand Up @@ -1254,8 +1256,8 @@ mod tests {
ManuallyDrop<UnsafeCell<()>>,
ManuallyDrop<[UnsafeCell<u8>]>,
ManuallyDrop<[UnsafeCell<bool>]>,
MaybeUninit<NotZerocopy>,
MaybeUninit<UnsafeCell<()>>,
CoreMaybeUninit<NotZerocopy>,
CoreMaybeUninit<UnsafeCell<()>>,
Wrapping<UnsafeCell<()>>
);

Expand Down Expand Up @@ -1297,9 +1299,9 @@ mod tests {
Option<FnManyArgs>,
Option<extern "C" fn()>,
Option<ECFnManyArgs>,
MaybeUninit<u8>,
MaybeUninit<NotZerocopy>,
MaybeUninit<UnsafeCell<()>>,
CoreMaybeUninit<u8>,
CoreMaybeUninit<NotZerocopy>,
CoreMaybeUninit<UnsafeCell<()>>,
ManuallyDrop<UnsafeCell<()>>,
ManuallyDrop<[UnsafeCell<u8>]>,
ManuallyDrop<[UnsafeCell<bool>]>,
Expand Down Expand Up @@ -1761,9 +1763,9 @@ mod tests {
assert_impls!(ManuallyDrop<[UnsafeCell<u8>]>: KnownLayout, TryFromBytes, FromZeros, FromBytes, IntoBytes, Unaligned, !Immutable);
assert_impls!(ManuallyDrop<[UnsafeCell<bool>]>: KnownLayout, TryFromBytes, FromZeros, IntoBytes, Unaligned, !Immutable, !FromBytes);

assert_impls!(MaybeUninit<u8>: KnownLayout, Immutable, TryFromBytes, FromZeros, FromBytes, Unaligned, !IntoBytes);
assert_impls!(MaybeUninit<NotZerocopy>: KnownLayout, TryFromBytes, FromZeros, FromBytes, !Immutable, !IntoBytes, !Unaligned);
assert_impls!(MaybeUninit<UnsafeCell<()>>: KnownLayout, TryFromBytes, FromZeros, FromBytes, Unaligned, !Immutable, !IntoBytes);
assert_impls!(CoreMaybeUninit<u8>: KnownLayout, Immutable, TryFromBytes, FromZeros, FromBytes, Unaligned, !IntoBytes);
assert_impls!(CoreMaybeUninit<NotZerocopy>: KnownLayout, TryFromBytes, FromZeros, FromBytes, !Immutable, !IntoBytes, !Unaligned);
assert_impls!(CoreMaybeUninit<UnsafeCell<()>>: KnownLayout, TryFromBytes, FromZeros, FromBytes, Unaligned, !Immutable, !IntoBytes);

assert_impls!(Wrapping<u8>: KnownLayout, Immutable, TryFromBytes, FromZeros, FromBytes, IntoBytes, Unaligned);
// This test is important because it allows us to test our hand-rolled
Expand Down
52 changes: 46 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ use core::{
fmt::{self, Debug, Display, Formatter},
hash::Hasher,
marker::PhantomData,
mem::{self, ManuallyDrop, MaybeUninit},
mem::{self, ManuallyDrop, MaybeUninit as CoreMaybeUninit},
num::{
NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128,
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, Wrapping,
Expand Down Expand Up @@ -724,6 +724,22 @@ pub unsafe trait KnownLayout {
/// This is `()` for sized types and `usize` for slice DSTs.
type PointerMetadata: PointerMetadata;

/// A maybe-uninitialized analog of `Self`
///
/// # Safety
///
/// Users may assume that:
/// - `Self` and `Self::MaybeUninit` have the same:
/// - Fixed prefix size
/// - Alignment
/// - (For DSTs) trailing slice element size
/// - It is valid to perform an `as` cast from `*mut Self` and `*mut
/// Self::MaybeUninit`, and this operation preserves referent size (ie,
/// `size_of_val_raw`).
#[cfg(zerocopy_unstable)]
#[doc(hidden)]
type MaybeUninit: ?Sized + KnownLayout<PointerMetadata = Self::PointerMetadata>;

/// The layout of `Self`.
///
/// # Safety
Expand Down Expand Up @@ -856,6 +872,17 @@ unsafe impl<T> KnownLayout for [T] {

type PointerMetadata = usize;

// SAFETY: `CoreMaybeUninit<T>` has the same size and alignment as `T`.
// Consequently, `[CoreMaybeUninit<T>]` has the same size and alignment as
// `[T]`, becuase `CoreMaybeUninit<T>` has the same size and alignment as
// `T` [1].
//
// [1] Per https://doc.rust-lang.org/1.81.0/std/mem/union.MaybeUninit.html#layout-1:
//
// `MaybeUninit<T>` is guaranteed to have the same size, alignment, and ABI as
// `T`
type MaybeUninit = [CoreMaybeUninit<T>];

const LAYOUT: DstLayout = DstLayout::for_slice::<T>();

// SAFETY: `.cast` preserves address and provenance. The returned pointer
Expand Down Expand Up @@ -908,7 +935,7 @@ impl_known_layout!(
T => Option<T>,
T: ?Sized => PhantomData<T>,
T => Wrapping<T>,
T => MaybeUninit<T>,
T => CoreMaybeUninit<T>,
T: ?Sized => *const T,
T: ?Sized => *mut T
);
Expand Down Expand Up @@ -941,6 +968,19 @@ safety_comment! {
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T)] UnsafeCell<T>);
}

safety_comment! {
/// SAFETY:
/// - By invariant on `KnownLayout::MaybeUninit`, `T` and `T::MaybeUninit`
/// have the same:
/// - Fixed prefix size
/// - Alignment
/// - (For DSTs) trailing slice element size
/// - By invariant on `KnownLayout`, it is valid to perform an `as` cast
/// from `*mut T` and `*mut T::MaybeUninit`, and this operation preserves
/// referent size (ie, `size_of_val_raw`).
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T::MaybeUninit)] MaybeUninit<T>);
}

/// Analyzes whether a type is [`FromZeros`].
///
/// This derive analyzes, at compile time, whether the annotated type satisfies
Expand Down Expand Up @@ -2539,7 +2579,7 @@ pub unsafe trait TryFromBytes {
where
Self: Sized,
{
let candidate = match MaybeUninit::<Self>::read_from_bytes(source) {
let candidate = match CoreMaybeUninit::<Self>::read_from_bytes(source) {
Ok(candidate) => candidate,
Err(e) => {
return Err(TryReadError::Size(e.with_dst()));
Expand Down Expand Up @@ -2600,7 +2640,7 @@ pub unsafe trait TryFromBytes {
where
Self: Sized,
{
let (candidate, suffix) = match MaybeUninit::<Self>::read_from_prefix(source) {
let (candidate, suffix) = match CoreMaybeUninit::<Self>::read_from_prefix(source) {
Ok(candidate) => candidate,
Err(e) => {
return Err(TryReadError::Size(e.with_dst()));
Expand Down Expand Up @@ -2662,7 +2702,7 @@ pub unsafe trait TryFromBytes {
where
Self: Sized,
{
let (prefix, candidate) = match MaybeUninit::<Self>::read_from_suffix(source) {
let (prefix, candidate) = match CoreMaybeUninit::<Self>::read_from_suffix(source) {
Ok(candidate) => candidate,
Err(e) => {
return Err(TryReadError::Size(e.with_dst()));
Expand Down Expand Up @@ -2735,7 +2775,7 @@ fn swap<T, U>((t, u): (T, U)) -> (U, T) {
#[inline(always)]
unsafe fn try_read_from<S, T: TryFromBytes>(
source: S,
mut candidate: MaybeUninit<T>,
mut candidate: CoreMaybeUninit<T>,
) -> Result<T, TryReadError<S, T>> {
// We use `from_mut` despite not mutating via `c_ptr` so that we don't need
// to add a `T: Immutable` bound.
Expand Down
3 changes: 3 additions & 0 deletions src/util/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,8 @@ macro_rules! impl_known_layout {

type PointerMetadata = ();

type MaybeUninit = core::mem::MaybeUninit<Self>;

const LAYOUT: crate::DstLayout = crate::DstLayout::for_type::<$ty>();

// SAFETY: `.cast` preserves address and provenance.
Expand Down Expand Up @@ -572,6 +574,7 @@ macro_rules! unsafe_impl_known_layout {
fn only_derive_is_allowed_to_implement_this_trait() {}

type PointerMetadata = <$repr as KnownLayout>::PointerMetadata;
type MaybeUninit = <$repr as KnownLayout>::MaybeUninit;

const LAYOUT: DstLayout = <$repr as KnownLayout>::LAYOUT;

Expand Down
117 changes: 116 additions & 1 deletion src/wrappers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// This file may not be copied, modified, or distributed except according to
// those terms.

use core::hash::Hash;
use core::{fmt, hash::Hash};

use super::*;

Expand Down Expand Up @@ -464,6 +464,121 @@ impl<T: Unaligned + Display> Display for Unalign<T> {
}
}

/// A wrapper type to construct uninitialized instances of `T`.
///
/// `MaybeUninit` is identical to the [standard library
/// `MaybeUninit`][core-maybe-uninit] type except that it supports unsized
/// types.
///
/// # Layout
///
/// The same layout guarantees and caveats apply to `MaybeUninit<T>` as apply to
/// the [standard library `MaybeUninit`][core-maybe-uninit] with one exception:
/// for `T: !Sized`, there is no single value for `T`'s size. Instead, for such
/// types, the following are guaranteed:
/// - Every [valid size][valid-size] for `T` is a valid size for
/// `MaybeUninit<T>` and vice versa
/// - Given `t: *const T` and `m: *const MaybeUninit<T>` with identical fat
/// pointer metadata, `t` and `m` address the same number of bytes (and
/// likewise for `*mut`)
///
/// [core-maybe-uninit]: core::mem::MaybeUninit
/// [valid-size]: crate::KnownLayout#what-is-a-valid-size
#[repr(transparent)]
#[cfg(zerocopy_unstable)]
pub struct MaybeUninit<T: ?Sized + KnownLayout>(T::MaybeUninit);

#[cfg(zerocopy_unstable)]
impl<T: ?Sized + KnownLayout> MaybeUninit<T> {
/// Creates a `Box<MaybeUninit<T>>`.
///
/// This function is useful for allocating large, uninit values on the heap,
/// without ever creating a temporary instance of `Self` on the stack.
///
/// # Errors
///
/// Returns an error on allocation failure. Allocation failure is guaranteed
/// never to cause a panic or an abort.
#[cfg(feature = "alloc")]
pub fn new_boxed_uninit(meta: T::PointerMetadata) -> Result<Box<Self>, AllocError> {
let align = Self::LAYOUT.align.get();
let size = match meta.size_for_metadata(Self::LAYOUT) {
Some(size) => size,
None => return Err(AllocError),
};
// On stable Rust versions <= 1.64.0, `Layout::from_size_align` has a
// bug in which sufficiently-large allocations (those which, when
// rounded up to the alignment, overflow `isize`) are not rejected,
// which can cause undefined behavior. See #64 for details.
//
// TODO(#67): Once our MSRV is > 1.64.0, remove this assertion.
#[allow(clippy::as_conversions)]
let max_alloc = (isize::MAX as usize).saturating_sub(align);
if size > max_alloc {
return Err(AllocError);
}

// TODO(https://github.com/rust-lang/rust/issues/55724): Use
// `Layout::repeat` once it's stabilized.
let layout = Layout::from_size_align(size, align).or(Err(AllocError))?;

let ptr = if layout.size() != 0 {
// TODO(#429): Add a "SAFETY" comment and remove this `allow`.
#[allow(clippy::undocumented_unsafe_blocks)]
let ptr = unsafe { alloc::alloc::alloc(layout) };
match NonNull::new(ptr) {
Some(ptr) => ptr,
None => return Err(AllocError),
}
} else {
let align = Self::LAYOUT.align.get();
// We use `transmute` instead of an `as` cast since Miri (with
// strict provenance enabled) notices and complains that an `as`
// cast creates a pointer with no provenance. Miri isn't smart
// enough to realize that we're only executing this branch when
// we're constructing a zero-sized `Box`, which doesn't require
// provenance.
//
// SAFETY: any initialized bit sequence is a bit-valid `*mut u8`.
// All bits of a `usize` are initialized.
#[allow(clippy::useless_transmute)]
let dangling = unsafe { mem::transmute::<usize, *mut u8>(align) };
// SAFETY: `dangling` is constructed from `Self::LAYOUT.align`,
// which is a `NonZeroUsize`, which is guaranteed to be non-zero.
//
// `Box<[T]>` does not allocate when `T` is zero-sized or when `len`
// is zero, but it does require a non-null dangling pointer for its
// allocation.
//
// TODO(https://github.com/rust-lang/rust/issues/95228): Use
// `std::ptr::without_provenance` once it's stable. That may
// optimize better. As written, Rust may assume that this consumes
// "exposed" provenance, and thus Rust may have to assume that this
// may consume provenance from any pointer whose provenance has been
// exposed.
#[allow(fuzzy_provenance_casts)]
unsafe {
NonNull::new_unchecked(dangling)
}
};

let ptr = Self::raw_from_ptr_len(ptr, meta);

// TODO(#429): Add a "SAFETY" comment and remove this `allow`. Make sure
// to include a justification that `ptr.as_ptr()` is validly-aligned in
// the ZST case (in which we manually construct a dangling pointer).
#[allow(clippy::undocumented_unsafe_blocks)]
Ok(unsafe { Box::from_raw(ptr.as_ptr()) })
}
}

#[cfg(zerocopy_unstable)]
impl<T: ?Sized + KnownLayout> fmt::Debug for MaybeUninit<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad(core::any::type_name::<Self>())
}
}

#[cfg(test)]
mod tests {
use core::panic::AssertUnwindSafe;
Expand Down
4 changes: 2 additions & 2 deletions tools/cargo-zerocopy/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,9 @@ fn install_toolchain_or_exit(versions: &Versions, name: &str) -> Result<(), Erro
fn get_rustflags(name: &str) -> &'static str {
// See #1792 for context on zerocopy_derive_union_into_bytes.
if name == "nightly" {
"--cfg __ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS --cfg zerocopy_derive_union_into_bytes "
"--cfg __ZEROCOPY_INTERNAL_USE_ONLY_NIGHTLY_FEATURES_IN_TESTS --cfg zerocopy_derive_union_into_bytes --cfg zerocopy_unstable "
} else {
"--cfg zerocopy_derive_union_into_bytes "
"--cfg zerocopy_derive_union_into_bytes --cfg zerocopy_unstable "
}
}

Expand Down
Loading

0 comments on commit d5c2b36

Please sign in to comment.