Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial support for unsized MaybeUninit wrapper type #2055

Merged
merged 1 commit into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
joshlf marked this conversation as resolved.
Show resolved Hide resolved
/// `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
Loading