Skip to content

Commit

Permalink
Add initial support for unsized MaybeUninit wrapper type
Browse files Browse the repository at this point in the history
This is achieved by adding a `MaybeUninit` associated type to
`KnownLayout`, whose layout is identical to `Self` except that it admits
uninitialized bytes in all positions.

For sized types, this is bound to `mem::MaybeUninit<Self>`. For
potentially unsized structs, we synthesize a doppelganger with the same
`repr`, whose leading fields are wrapped in `mem::MaybeUninit` and whose
trailing field is the `MaybeUninit` associated type of struct's original
trailing field type. This type-level recursion bottoms out at `[T]`,
whose `MaybeUninit` associated type is bound to `[mem::MaybeUninit<T>]`.

Makes progress towards #1797

SKIP_CARGO_SEMVER_CHECKS=1
  • Loading branch information
jswrenn committed Nov 21, 2024
1 parent 664b976 commit 60f0a43
Show file tree
Hide file tree
Showing 15 changed files with 759 additions and 191 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ jobs:

steps:
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
with:
fetch-depth: 2

- name: Populate cache
uses: ./.github/actions/cache
Expand Down Expand Up @@ -490,7 +492,7 @@ jobs:
if [ "${{ github.event_name }}" == "pull_request" ]; then
# Invoked from a PR - get the PR body directly
MESSAGE="${{ github.event.pull_request.body }}"
MESSAGE="$(git log -1 --pretty=%B ${{ github.event.pull_request.head.sha }})"
else
# Invoked from the merge queue - get the commit message
MESSAGE="$(git log -1 --pretty=%B ${{ github.sha }})"
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 @@ -632,14 +634,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 @@ -1257,8 +1259,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 @@ -1300,9 +1302,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 @@ -1764,9 +1766,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
142 changes: 68 additions & 74 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,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 @@ -732,6 +732,15 @@ pub unsafe trait KnownLayout {
/// This is `()` for sized types and `usize` for slice DSTs.
type PointerMetadata: PointerMetadata;

/// A maybe-uninitialized analog of `Self`
///
/// # Safety
///
/// `Self::LAYOUT` and `Self::MaybeUninit::LAYOUT` are identical.
/// `Self::MaybeUninit` admits uninitialized bytes in all positions.
#[doc(hidden)]
type MaybeUninit: ?Sized + KnownLayout<PointerMetadata = Self::PointerMetadata>;

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

type PointerMetadata = usize;

// SAFETY: `CoreMaybeUninit<T>::LAYOUT` and `T::LAYOUT` are identical
// because `CoreMaybeUninit<T>` has the same size and alignment as `T` [1].
// Consequently, `[CoreMaybeUninit<T>]::LAYOUT` and `[T]::LAYOUT` are
// identical, because they both lack a fixed-sized prefix and because they
// inherit the alignments of their inner element type (which are identical)
// [2][3].
//
// `[CoreMaybeUninit<T>]` admits uninitialized bytes at all positions
// because `CoreMaybeUninit<T>` admits uninitialized bytes at all positions
// and because the inner elements of `[CoreMaybeUninit<T>]` are laid out
// back-to-back [2][3].
//
// [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`
//
// [2] Per https://doc.rust-lang.org/1.82.0/reference/type-layout.html#slice-layout:
//
// Slices have the same layout as the section of the array they slice.
//
// [3] Per https://doc.rust-lang.org/1.82.0/reference/type-layout.html#array-layout:
//
// An array of `[T; N]` has a size of `size_of::<T>() * N` and the same
// alignment of `T`. Arrays are laid out so that the zero-based `nth`
// element of the array is offset from the start of the array by `n *
// size_of::<T>()` bytes.
type MaybeUninit = [CoreMaybeUninit<T>];

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

// SAFETY: `.cast` preserves address and provenance. The returned pointer
Expand Down Expand Up @@ -916,9 +954,11 @@ 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
T: ?Sized => *mut T,
T: ?Sized => &'_ T,
T: ?Sized => &'_ mut T,
);
impl_known_layout!(const N: usize, T => [T; N]);

Expand Down Expand Up @@ -949,6 +989,21 @@ safety_comment! {
unsafe_impl_known_layout!(T: ?Sized + KnownLayout => #[repr(T)] UnsafeCell<T>);
}

safety_comment! {
/// SAFETY:
/// - By consequence of the invariant on `T::MaybeUninit` that `T::LAYOUT`
/// and `T::MaybeUninit::LAYOUT` are equal, `T` and `T::MaybeUninit`
/// have the same:
/// - Fixed prefix size
/// - Alignment
/// - (For DSTs) trailing slice element size
/// - By consequence of the above, referents `T::MaybeUninit` and `T` have
/// the require the same kind of pointer metadata, and thus 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 @@ -2550,7 +2605,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 @@ -2611,7 +2666,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 @@ -2673,7 +2728,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 @@ -2746,7 +2801,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 Expand Up @@ -3035,72 +3090,11 @@ pub unsafe trait FromZeros: TryFromBytes {
where
Self: KnownLayout<PointerMetadata = usize>,
{
let size = match count.size_for_metadata(Self::LAYOUT) {
Some(size) => size,
None => return Err(AllocError),
};

let align = Self::LAYOUT.align.get();
// 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_zeroed(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.
unsafe { NonNull::new_unchecked(dangling) }
};

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

// 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()) })
// SAFETY: `alloc::alloc::alloc_zeroed` is a valid argument of
// `new_box`. The referent of the pointer returned by `alloc_zeroed`
// (and, consequently, the `Box` derived from it) is a valid instance of
// `Self`, because `Self` is `FromZeros`.
unsafe { crate::util::new_box(count, alloc::alloc::alloc_zeroed) }
}

#[deprecated(since = "0.8.0", note = "renamed to `FromZeros::new_box_zeroed_with_elems`")]
Expand Down Expand Up @@ -4562,7 +4556,7 @@ pub unsafe trait FromBytes: FromZeros {
Self: Sized,
R: io::Read,
{
let mut buf = MaybeUninit::<Self>::zeroed();
let mut buf = CoreMaybeUninit::<Self>::zeroed();
let ptr = Ptr::from_mut(&mut buf);
// SAFETY: `buf` consists entirely of initialized, zeroed bytes.
let ptr = unsafe { ptr.assume_validity::<invariant::Initialized>() };
Expand Down
12 changes: 12 additions & 0 deletions src/util/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,17 @@ macro_rules! impl_known_layout {

type PointerMetadata = ();

// SAFETY: `CoreMaybeUninit<T>::LAYOUT` and `T::LAYOUT` are
// identical because `CoreMaybeUninit<T>` has the same size and
// alignment as `T` [1], and `CoreMaybeUninit` admits
// uninitialized bytes in all positions.
//
// [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 = core::mem::MaybeUninit<Self>;

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

// SAFETY: `.cast` preserves address and provenance.
Expand Down Expand Up @@ -599,6 +610,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
Loading

0 comments on commit 60f0a43

Please sign in to comment.