From 596521cce1145f3d1979141c88e11879c7f1a5fd Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 4 Oct 2024 18:44:37 -0700 Subject: [PATCH 1/2] Add initial support for unsized `MaybeUninit` wrapper type 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`. 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]`. Makes progress towards #1797 --- src/wrappers.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/wrappers.rs b/src/wrappers.rs index fd48236ae4..74cd5846c3 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -723,13 +723,19 @@ mod tests { } #[test] +<<<<<<< HEAD #[allow(clippy::as_conversions)] +======= +>>>>>>> Add initial support for unsized `MaybeUninit` wrapper type fn test_maybe_uninit() { // int { let input = 42; let uninit = MaybeUninit::new(input); +<<<<<<< HEAD // SAFETY: `uninit` is in an initialized state +======= +>>>>>>> Add initial support for unsized `MaybeUninit` wrapper type let output = unsafe { uninit.assume_init() }; assert_eq!(input, output); } @@ -738,7 +744,10 @@ mod tests { { let input = 42; let uninit = MaybeUninit::new(&input); +<<<<<<< HEAD // SAFETY: `uninit` is in an initialized state +======= +>>>>>>> Add initial support for unsized `MaybeUninit` wrapper type let output = unsafe { uninit.assume_init() }; assert_eq!(&input as *const _, output as *const _); assert_eq!(input, *output); @@ -748,7 +757,10 @@ mod tests { { let input = [1, 2, 3, 4]; let uninit = MaybeUninit::new(&input[..]); +<<<<<<< HEAD // SAFETY: `uninit` is in an initialized state +======= +>>>>>>> Add initial support for unsized `MaybeUninit` wrapper type let output = unsafe { uninit.assume_init() }; assert_eq!(&input[..] as *const _, output as *const _); assert_eq!(input, *output); From 9988d85d86dd4a1dfca62f1d7782dc85e5a1f860 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Sun, 6 Oct 2024 01:24:03 +0000 Subject: [PATCH 2/2] [wip] UnalignUnsized --- .github/workflows/ci.yml | 27 +++ Cargo.toml | 3 + src/layout.rs | 7 +- src/lib.rs | 46 +++++ src/pointer/mod.rs | 22 ++ src/pointer/ptr.rs | 238 ++++++++++++++++++++- src/util/macros.rs | 12 +- src/util/mod.rs | 307 +++++++++++++++++++++++++++- src/wrappers.rs | 208 +++++++++++++++++-- zerocopy-derive/src/lib.rs | 13 ++ zerocopy-derive/src/output_tests.rs | 10 + 11 files changed, 874 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d0f7ccaf7..f69e478d89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,6 +56,7 @@ jobs: # which a particular feature is supported. "zerocopy-core-error-1-81-0", "zerocopy-diagnostic-on-unimplemented-1-78-0", + "zerocopy-unsized-needs-drop-1-63-0", "zerocopy-generic-bounds-in-const-fn-1-61-0", "zerocopy-target-has-atomics-1-60-0", "zerocopy-aarch64-simd-1-59-0", @@ -93,6 +94,8 @@ jobs: features: "--all-features" - toolchain: "zerocopy-diagnostic-on-unimplemented-1-78-0" features: "--all-features" + - toolchain: "zerocopy-unsized-needs-drop-1-63-0" + features: "--all-features" - toolchain: "zerocopy-generic-bounds-in-const-fn-1-61-0" features: "--all-features" - toolchain: "zerocopy-target-has-atomics-1-60-0" @@ -117,6 +120,8 @@ jobs: toolchain: "zerocopy-core-error-1-81-0" - crate: "zerocopy-derive" toolchain: "zerocopy-diagnostic-on-unimplemented-1-78-0" + - crate: "zerocopy-derive" + toolchain: "zerocopy-unsized-needs-drop-1-63-0" - crate: "zerocopy-derive" toolchain: "zerocopy-generic-bounds-in-const-fn-1-61-0" - crate: "zerocopy-derive" @@ -212,6 +217,28 @@ jobs: target: "thumbv6m-none-eabi" - toolchain: "zerocopy-generic-bounds-in-const-fn-1-61-0" target: "wasm32-wasi" + # Exclude most targets targets from the + # `zerocopy-unsized-needs-drop-1-63-0` toolchain since the + # `zerocopy-unsized-needs-drop-1-63-0` feature is unrelated to + # compilation target. This only leaves i686 and x86_64 targets. + - toolchain: "zerocopy-unsized-needs-drop-1-63-0" + target: "arm-unknown-linux-gnueabi" + - toolchain: "zerocopy-unsized-needs-drop-1-63-0" + target: "aarch64-unknown-linux-gnu" + - toolchain: "zerocopy-unsized-needs-drop-1-63-0" + target: "powerpc-unknown-linux-gnu" + - toolchain: "zerocopy-unsized-needs-drop-1-63-0" + target: "powerpc64-unknown-linux-gnu" + - toolchain: "zerocopy-unsized-needs-drop-1-63-0" + target: "riscv64gc-unknown-linux-gnu" + - toolchain: "zerocopy-unsized-needs-drop-1-63-0" + target: "s390x-unknown-linux-gnu" + - toolchain: "zerocopy-unsized-needs-drop-1-63-0" + target: "x86_64-pc-windows-msvc" + - toolchain: "zerocopy-unsized-needs-drop-1-63-0" + target: "thumbv6m-none-eabi" + - toolchain: "zerocopy-unsized-needs-drop-1-63-0" + target: "wasm32-wasi" # Exclude `thumbv6m-none-eabi` combined with any feature that implies # the `std` feature since `thumbv6m-none-eabi` does not include a # pre-compiled std. diff --git a/Cargo.toml b/Cargo.toml index d5b9f4448d..3ce8406d3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,9 @@ zerocopy-core-error-1-81-0 = "1.81.0" # From 1.78.0, Rust supports the `#[diagnostic::on_unimplemented]` attribute. zerocopy-diagnostic-on-unimplemented-1-78-0 = "1.78.0" +# From 1.63.0, Rust supports generic types with trait bounds in `const fn`. +zerocopy-unsized-needs-drop-1-63-0 = "1.63.0" + # From 1.61.0, Rust supports generic types with trait bounds in `const fn`. zerocopy-generic-bounds-in-const-fn-1-61-0 = "1.61.0" diff --git a/src/layout.rs b/src/layout.rs index 00d107ff2a..8d200a1913 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -94,7 +94,7 @@ pub(crate) enum MetadataCastError { impl DstLayout { /// The minimum possible alignment of a type. - const MIN_ALIGN: NonZeroUsize = match NonZeroUsize::new(1) { + pub(crate) const MIN_ALIGN: NonZeroUsize = match NonZeroUsize::new(1) { Some(min_align) => min_align, None => const_unreachable!(), }; @@ -598,6 +598,11 @@ impl DstLayout { Ok((elems, split_at)) } + + /// Produces `true` if `self.align` equals 1; otherwise `false`. + pub(crate) const fn is_trivially_aligned(&self) -> bool { + matches!(self.align, DstLayout::MIN_ALIGN) + } } // TODO(#67): For some reason, on our MSRV toolchain, this `allow` isn't diff --git a/src/lib.rs b/src/lib.rs index a45c49c8fd..81d896041e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -762,6 +762,24 @@ pub unsafe trait KnownLayout { #[doc(hidden)] const LAYOUT: DstLayout; + /// Does `Self` have a non-trivial destructor? + /// + /// This defaulted implementation is appropriate for all types except + /// `UnalignUnsized` which has an explicit `Drop` implementation and is + /// thus unconditionally `mem::needs_drop`, even if `T` is not + /// `mem::needs_drop`. + /// + /// # Safety + /// + /// Unsafe code may not assume anything about the value of `NEEDS_DROP`. + const NEEDS_DROP: bool = { + #[cfg(zerocopy_unsized_needs_drop_1_63_0)] + let val = core::mem::needs_drop::(); + #[cfg(not(zerocopy_unsized_needs_drop_1_63_0))] + let val = true; + val + }; + /// SAFETY: The returned pointer has the same address and provenance as /// `bytes`. If `Self` is a DST, the returned pointer's referent has `elems` /// elements in its trailing slice. @@ -797,6 +815,34 @@ pub unsafe trait KnownLayout { // resulting size would not fit in a `usize`. meta.size_for_metadata(Self::LAYOUT) } + + /// Run the destructor of `ptr`'s referent. + /// + /// # Panics + /// + /// Implementations of this function never panic. + /// + /// # Compile-Time Assertions + /// + /// Implementations of this function must emit a post-monomorphization error + /// if `ptr`'s referent has a non-trivial drop that cannot be run. + /// + /// # Safety + /// + /// This function may only be called from the destructor (i.e., + /// `Drop::drop`) of transitive owner of `ptr`'s referent. After invoking + /// this function, it is forbidden to re-use `ptr` or its referent. + #[doc(hidden)] + #[inline] + unsafe fn destroy(ptr: MaybeAligned<'_, Self, invariant::Exclusive>) { + // SAFETY: The preconditions of `destroy_unsized` are identical to that + // of `destroy` and are ensured by the caller. + // + // This defaulted implementation works for all types, but for sized + // types, delegating to `crate::util::destroy::destroy_sized` — which + // does not allocate — is preferable. + unsafe { crate::util::destroy::destroy_unsized(ptr) } + } } /// The metadata associated with a [`KnownLayout`] type. diff --git a/src/pointer/mod.rs b/src/pointer/mod.rs index e1f8a9676b..611fa412c6 100644 --- a/src/pointer/mod.rs +++ b/src/pointer/mod.rs @@ -55,6 +55,28 @@ where unsafe { core::ptr::read_unaligned(raw) } } + /// Reads the value from `MaybeAligned`. + /// + /// # Safety + /// + /// If `T` has a non-trivial destructor, using the returned `T` (including + /// dropping it) and the original referent may cause undefined behavior. The + /// caller ensures this does not occur. + #[must_use] + #[inline] + pub(crate) unsafe fn read_unaligned_unchecked(self) -> T + where + R: AliasingSafeReason, + T: AliasingSafe + Sized, + { + let raw = self.as_non_null().as_ptr(); + // SAFETY: By invariant on `MaybeAligned`, `raw` contains + // validly-initialized data for `T`. By `T: AliasingSafe`, we are + // permitted to perform a read of `raw`'s referent. The caller ensures + // that subsequent uses of `T` do not induce UB. + unsafe { core::ptr::read_unaligned(raw) } + } + /// Views the value as an aligned reference. /// /// This is only available if `T` is [`Unaligned`]. diff --git a/src/pointer/ptr.rs b/src/pointer/ptr.rs index dfc9949b52..19df1784cf 100644 --- a/src/pointer/ptr.rs +++ b/src/pointer/ptr.rs @@ -397,7 +397,7 @@ mod _conversions { { /// Constructs a `Ptr` from an exclusive reference. #[inline] - pub(crate) fn from_mut(ptr: &'a mut T) -> Self { + pub fn from_mut(ptr: &'a mut T) -> Self { let ptr = NonNull::from(ptr); // SAFETY: // 0. If `ptr`'s referent is not zero sized, then `ptr`, by @@ -424,6 +424,93 @@ mod _conversions { } } + /// `Box` → `Ptr<'static, T>` + #[cfg(feature = "alloc")] + impl<'a, T> Ptr<'a, T, (Exclusive, Aligned, Valid)> + where + T: 'a + ?Sized, + { + /// Constructs a `Ptr` from a `Box`. + /// + /// This leaks the `Box`. + #[inline] + pub(crate) fn from_box(ptr: alloc::boxed::Box) -> Self { + let ptr = alloc::boxed::Box::into_raw(ptr); + // SAFETY: LEMMA 1: The referent of a `Box` is well-aligned and + // non-null [1]. + // + // [1] Per https://doc.rust-lang.org/1.82.0/alloc/boxed/struct.Box.html#method.into_raw: + // + // The pointer will be properly aligned and non-null. + let mut ptr = unsafe { NonNull::new_unchecked(ptr) }; + // SAFETY: `ptr` is convertible to an exclusive reference [1][2]: + // 0. `ptr` is properly aligned, by LEMMA 1. + // 1. `ptr` is non-null, by LEMMA 1. + // 2. `ptr` dereferenceable, in the sense that the memory range of + // the given size starting at the pointer are within the bounds + // of a single allocated object [3], because it derived from + // `Box::into_raw`. + // 3. The resulting `&mut T` adheres to Rust's aliasing rules. `ptr` + // is the sole reference to its referent, because the `Box` that + // owned its referent was consumed by `Box::into_raw`. Its + // referent lives for at least `'a`, because this impl is bounded + // by `T: 'a`. + // + // [1] https://doc.rust-lang.org/1.82.0/core/ptr/struct.NonNull.html#method.as_mut + // [2] https://doc.rust-lang.org/1.82.0/core/ptr/index.html#pointer-to-reference-conversion + // [3] https://doc.rust-lang.org/1.82.0/core/ptr/index.html#safety + let ptr = unsafe { ptr.as_mut() }; + Self::from_mut(ptr) + } + + /// Constructs a `Box` from a `Ptr`. + /// + /// # Safety + /// + /// `ptr` must be derived from `Ptr::from_box`. Its referent's size and + /// alignment must be equal to that of the referent of the originating + /// `Box`. + #[inline] + pub(crate) unsafe fn into_box(ptr: Self) -> alloc::boxed::Box { + #[allow(clippy::as_conversions)] + let ptr = ptr.as_mut() as *mut _; + // SAFETY: It is valid to convert from `ptr` to `Box` because if + // `ptr`'s referent is non-zero-sized, it was allocated with the + // global allocator (LEMMA 1) with `Layout` correct a referent of + // `T` (LEMMA 2) [1][2]. If `ptr`'s referent is zero-sized, it is + // valid for reads and well-aligned (LEMMA 3). Regardless of the + // size of `ptr`'s referent, it is a valid and well-aligned `T` + // (LEMMA 3). It is the sole pointer its referent (LEMMA 4). + // + // LEMMA 1: If non-zero-sized, `ptr`'s referent was allocated with + // the global allocator. By contract on the caller, `ptr` is derived + // from `Ptr::from_box`, which consumes a `Box` using the global + // allocator and returns a unique `Ptr` to its referent. + // + // LEMMA 2: If non-zero-sized, `ptr`'s referent was allocated with a + // `Layout` correct for `T`. Although the originating `Box` may not + // have had a referent of type `T`, by contract on the caller, its + // size and alignment (which are the two defining components of a + // `Layout`) are equal to that of the referent of the originating + // `Box`. + // + // LEMMA 3: If the `ptr`'s referent is zero-sized, it is valid for + // reads and well-aligned. By contract on the caller, `Ptr` is + // derived from `Box::into_raw`, whose referent is always valid for + // reads and well-aligned. Although the originating `Box` may not + // have had a referent of type `T`, `ptr`'s is a valid `T` because + // `ptr` carries `invariant::Valid`. + // + // LEMMA 4: `ptr` is the sole pointer to its referent because, by + // contract on the caller, it's derived from `Ptr::from_box` which + // consumes the originating `Box`. + // + // [1] https://doc.rust-lang.org/1.82.0/std/boxed/struct.Box.html#method.from_raw + // [2] https://doc.rust-lang.org/1.82.0/std/boxed/index.html#memory-layout + unsafe { alloc::boxed::Box::from_raw(ptr) } + } + } + /// `Ptr<'a, T>` → `&'a T` impl<'a, T, I> Ptr<'a, T, I> where @@ -815,6 +902,25 @@ mod _transitions { unsafe { self.assume_alignment::() } } + /// Attempt to recall that `self`'s referent is trivially aligned. + #[inline] + // TODO(#859): Reconsider the name of this method before making it + // public. + pub(crate) fn try_recall_trivially_aligned( + self, + ) -> Result, Self> + where + T: KnownLayout, + { + if T::LAYOUT.is_trivially_aligned() { + // SAFETY: The above check ensures that `T` has no non-trivial + // alignment requirement. + Ok(unsafe { self.assume_alignment::() }) + } else { + Err(self) + } + } + /// Assumes that `self`'s referent conforms to the validity requirement /// of `V`. /// @@ -954,6 +1060,54 @@ mod _casts { T: 'a + ?Sized, I: Invariants, { + /// Casts to a different (unsized) target type. + /// + /// Produces `Err(self)` iff the `Ptr` produced by the cast would + /// reference a strict superset of the bytes referenced by `self`. + /// + /// # Safety + /// + /// The caller promises that if `I::Aliasing` is [`Any`] or [`Shared`], + /// `UnsafeCell`s in the returned `Ptr`'s referent must exist at ranges + /// identical to those at which `UnsafeCell`s exist in `self`'s + /// referent. + /// + /// Callers may assume the documented behavior of `try_cast to be a + /// safety postcondition. + #[inline] + pub unsafe fn try_cast(self) -> Result, Self> + where + T: KnownLayout, + U: 'a + ?Sized + KnownLayout, + { + let ptr = self.as_non_null(); + let meta = T::pointer_to_metadata(ptr.as_ptr()); + if meta.size_for_metadata(U::LAYOUT) <= T::size_of_val_raw(ptr) { + // SAFETY: + // - The returned pointer addresses a subset of the bytes + // addressed by `ptr`, because of the above conditional. + // - The returned pointer has the same provenance as `p` because + // `NonNull::cast` is presumed to preserve provenance and + // `U::raw_from_ptr_len` is documented to preserve provenance. + // - By contract on the caller, if `I::Aliasing` is `Any` or + // `Shared`, `UnsafeCell`s in `*u` must exist at ranges + // identical to those at which `UnsafeCell`s exist in `*p` + Ok(unsafe { + self.cast_unsized(|ptr| { + // SAFETY: `ptr` is derived from `self`'s referent, + // which is non-null by invariant on `Ptr`. + let ptr = NonNull::new_unchecked(ptr); + let bytes = ptr.cast::(); + U::raw_from_ptr_len(bytes, meta).as_ptr() + }) + }) + } else { + // The `Ptr` produced by the cast would reference a strict + // superset of the bytes referenced by `self`. + Err(self) + } + } + /// Casts to a different (unsized) target type. /// /// # Safety @@ -1641,6 +1795,65 @@ mod _project { } } +mod _misc { + use super::*; + + impl Ptr<'_, T, I> + where + T: ?Sized, + I: Invariants, + { + /// Executes the referent's destructor. + /// + /// # Safety + /// + /// This function may only be invoked from the destructor of an + /// transitive owner `ptr`'s referent. After invoking this function, it + /// is forbidden to re-use `ptr`'s referent. + pub(crate) unsafe fn drop_in_place(self) { + let ptr = self.as_non_null().as_ptr(); + // SAFETY: This invocation satisfies `drop_in_place`'s safety + // invariants [1]: + // - `ptr` is valid for both reads and writes, because it derived + // from a `Ptr` whose referent is exclusively aliased, + // well-aligned, and valid. + // - `ptr` is well-aligned; see above. + // - `ptr` is non-null; see above. + // - `ptr`'s referent is presumed to be a library-valid + // - `ptr` is exclusively aliased and thus is the sole pointer to + // its referent. + // + // [1] https://doc.rust-lang.org/1.82.0/std/ptr/fn.drop_in_place.html#safety + unsafe { core::ptr::drop_in_place(ptr) } + } + } + + impl Ptr<'_, T, I> + where + T: ?Sized + KnownLayout, + I: Invariants, + { + /// Produces the referent's size, in bytes. + #[doc(hidden)] + #[must_use] + #[inline] + pub fn size(&self) -> usize { + use crate::PointerMetadata; + let meta = KnownLayout::pointer_to_metadata(self.as_non_null().as_ptr()); + match meta.size_for_metadata(T::LAYOUT) { + Some(size) => size, + None => { + // SAFETY: `size_for_metadata` promises to only return + // `None` if the resulting size would not fit in a `usize`. + // This is impossible here, since by invariant on `Ptr`, + // `self` references no more than `isize::MAX` bytes. + unsafe { core::hint::unreachable_unchecked() } + } + } + } + } +} + #[cfg(test)] mod tests { use core::mem::{self, MaybeUninit}; @@ -1667,6 +1880,29 @@ mod tests { } } + #[cfg(feature = "alloc")] + mod alloc { + use super::*; + + #[test] + fn sized() { + let boxed = Box::new(42); + let ptr = Ptr::from_box(boxed); + // SAFETY: `ptr` is derived from `Ptr::from_box`. + let boxed = unsafe { Ptr::into_box(ptr) }; + let _ = boxed; + } + + #[test] + fn boxed_slice() { + let boxed = vec![1, 2, 3, 4].into_boxed_slice(); + let ptr = Ptr::from_box(boxed); + // SAFETY: `ptr` is derived from `Ptr::from_box`. + let boxed = unsafe { Ptr::into_box(ptr) }; + let _ = boxed; + } + } + mod test_ptr_try_cast_into_soundness { use super::*; diff --git a/src/util/macros.rs b/src/util/macros.rs index d155d142e5..0de945825f 100644 --- a/src/util/macros.rs +++ b/src/util/macros.rs @@ -582,6 +582,14 @@ macro_rules! impl_known_layout { #[inline(always)] fn pointer_to_metadata(_ptr: *mut Self) -> () { } + + #[inline] + unsafe fn destroy(ptr: crate::MaybeAligned<'_, Self, crate::invariant::Exclusive>) { + // SAFETY: The preconditions of `destroy_sized` are + // identical to that of `destroy` and are ensured by the + // caller. + unsafe { crate::util::destroy::destroy_sized(ptr) } + } } }; }; @@ -599,7 +607,7 @@ macro_rules! impl_known_layout { /// - It must be valid to perform an `as` cast from `*mut $repr` to `*mut $ty`, /// and this operation must preserve referent size (ie, `size_of_val_raw`). macro_rules! unsafe_impl_known_layout { - ($($tyvar:ident: ?Sized + KnownLayout =>)? #[repr($repr:ty)] $ty:ty) => { + ($($tyvar:ident: ?Sized + KnownLayout =>)? #[repr($(packed,)? $repr:ty)] $ty:ty) => { const _: () = { use core::ptr::NonNull; @@ -620,7 +628,7 @@ macro_rules! unsafe_impl_known_layout { // TODO(#429): Add documentation to `NonNull::new_unchecked` // that it preserves provenance. #[inline(always)] - fn raw_from_ptr_len(bytes: NonNull, meta: <$repr as KnownLayout>::PointerMetadata) -> NonNull { + fn raw_from_ptr_len(bytes: NonNull, meta: Self::PointerMetadata) -> NonNull { #[allow(clippy::as_conversions)] let ptr = <$repr>::raw_from_ptr_len(bytes, meta).as_ptr() as *mut Self; // SAFETY: `ptr` was converted from `bytes`, which is non-null. diff --git a/src/util/mod.rs b/src/util/mod.rs index 0c4dd788ce..54556c411e 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -23,7 +23,7 @@ use core::{ use crate::{ error::AlignmentError, pointer::invariant::{self, Invariants}, - Unalign, + KnownLayout, Unalign, UnalignUnsized, }; /// A type which has the same layout as the type it wraps. @@ -293,11 +293,11 @@ unsafe impl TransparentWrapper for UnsafeCell { // SAFETY: Since we set this to `Invariant`, we make no safety claims. type UnsafeCellVariance = Invariant; - // SAFETY: Per [1] (from comment on impl), `Unalign` has the same + // SAFETY: Per [1] (from comment on impl), `UnsafeCell` has the same // representation as `T`, and thus has the same alignment as `T`. type AlignmentVariance = Covariant; - // SAFETY: Per [1], `Unalign` has the same bit validity as `T`. + // SAFETY: Per [1], `UnsafeCell` has the same bit validity as `T`. // Technically the term "representation" doesn't guarantee this, but the // subsequent sentence in the documentation makes it clear that this is the // intention. @@ -367,6 +367,45 @@ unsafe impl TransparentWrapper for Unalign { } } +// SAFETY: `UnalignUnsized` promises to have the same size as `T`. +// +// See inline comments for other safety justifications. +unsafe impl TransparentWrapper for UnalignUnsized { + type Inner = T; + + // SAFETY: `UnalignUnsized` promises to have `UnsafeCell`s covering the + // same byte ranges as `Inner = T`. + type UnsafeCellVariance = Covariant; + + // SAFETY: Since `UnalignUnsized` promises to have alignment 1 regardless + // of `T`'s alignment. Thus, an aligned pointer to `UnalignUnsized` is + // not necessarily an aligned pointer to `T`. + type AlignmentVariance = Invariant; + + // SAFETY: `UnalignUnsized` promises to have the same validity as `T`. + type ValidityVariance = Covariant; + + #[inline(always)] + fn cast_into_inner(ptr: *mut Self) -> *mut T { + // SAFETY: Per the safety comment on the impl block, `UnalignUnsized` + // has the same representation as `T`. Thus, this cast preserves size. + // + // This cast trivially preserves provenance. + #[allow(clippy::as_conversions)] + return ptr as *mut T; + } + + #[inline(always)] + fn cast_from_inner(ptr: *mut T) -> *mut Self { + // SAFETY: Per the safety comment on the impl block, `UnalignUnsized` + // has the same representation as `T`. Thus, this cast preserves size. + // + // This cast trivially preserves provenance. + #[allow(clippy::as_conversions)] + return ptr as *mut Self; + } +} + /// Implements `TransparentWrapper` for an atomic type. /// /// # Safety @@ -678,6 +717,8 @@ pub(crate) unsafe fn copy_unchecked(src: &[u8], dst: &mut [u8]) { // bytes does not overlap with the region of memory beginning at `dst` // with the same size, because `dst` is derived from an exclusive // reference. + // + // [1] https://doc.rust-lang.org/1.81.0/core/ptr/fn.copy_nonoverlapping.html#safety unsafe { core::ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), src.len()); }; @@ -822,6 +863,226 @@ where Ok(unsafe { alloc::boxed::Box::from_raw(ptr.as_ptr()) }) } +#[doc(hidden)] +pub mod destroy { + use crate::{invariant, KnownLayout, MaybeAligned}; + + /// Run `T`'s destructor. + /// + /// # Safety + /// + /// See `KnownLayout::destroy`. + #[cfg(feature = "alloc")] + #[inline] + pub unsafe fn destroy_unsized( + ptr: MaybeAligned<'_, T, invariant::Exclusive>, + ) { + use crate::MaybeUninit; + use crate::Ptr; + + // If `T` has a trivial destructor, simply return. + if !T::NEEDS_DROP { + return; + } + + match ptr.try_recall_trivially_aligned() { + // If `T` is trivially aligned, it can simply be dropped in place. + Ok(ptr) => { + // SAFETY: By contract on the caller, this function is only + // invoked from the destructor of an transitive owner `ptr`'s + // referent, and `ptr`'s referent is never subsequently + // re-accessed. + unsafe { + ptr.drop_in_place(); + } + } + // Otherwise, can destroy an arbitrarily-aligned [`[T]`] by: + // 1. allocating a well-aligned `aligned: Box>` + // 2. copying `ptr`'s referent to `aligned` + // 3. casting `aligned` to `Box<[T]>` + // 4. dropping `aligned` + Err(ptr) => { + // First, we allocate `aligned`. + let ptr = ptr.as_non_null().as_ptr(); + let meta = KnownLayout::pointer_to_metadata(ptr); + let aligned = match MaybeUninit::::new_boxed_uninit(meta) { + Ok(ptr) => ptr, + Err(_) => { + // `MaybeUninit::new_boxed_uninit` returns an `Err` on + // allocation failure. In this unfortunate case, we + // cannot run the referent's destructor. + return; + } + }; + + // Next, we copy `ptr`'s referent to `aligned`. + let aligned = Ptr::from_box(aligned); + let size = aligned.size(); + + // SAFETY: This invocation satisfies the safety contract of + // copy_nonoverlapping [1]: + // - `ptr as *mut u8` is valid for reads of `size` bytes, + // because it is derived from a `Ptr` whose referent is + // exclusively-aliased. This is sufficent, since + // `copy_nonoverlapping` does not require its source referent + // to be valid or even initialized [1]. + // - `aligned as *mut u8` is valid for writes of `size` bytes, + // because `aligned`'s referent is greater-than-or-equal in + // size to that of `slf`, because `aligned` might include + // trailing padding. + // - `src` and `dst` are, trivially, properly aligned + // - the region of memory beginning at `src` with a size of + // `size` bytes does not overlap with the region of memory + // beginning at `aligned` with the same size, because + // `aligned` is derived from a fresh allocation. + // + // [1] https://doc.rust-lang.org/1.81.0/core/ptr/fn.copy_nonoverlapping.html#safety + unsafe { + #[allow(clippy::as_conversions)] + core::ptr::copy_nonoverlapping( + ptr as *mut u8, + aligned.as_non_null().as_ptr() as *mut u8, + size, + ); + } + + // Finally, we reconstitute `aligned` as a `Box` and + // immediately drop it. + + // SAFETY: Because `aligned` carries `invariant::Exclusive`, + // there are no safety preconditions. + let aligned = match unsafe { aligned.try_cast::() } { + Ok(ptr) => ptr, + Err(_) => { + // SAFETY: By postcondition on `Ptr::try_cast`, `Err` is + // only produced if the resulting cast would reference + // more bytes than referenced the input `aligned`. This + // is impossible, since, by invariant on `MaybeUninit`, + // `T` and `MaybeUninit` are guaranteed to have the + // same size. + unsafe { core::hint::unreachable_unchecked() } + } + }; + + // SAFETY: By invariant on `MaybeUninit`, `T` and + // `MaybeUninit` are guaranteed to have the same size. + let aligned = unsafe { aligned.assume_alignment::() }; + + // SAFETY: Because we have entirely overwritten the referent of + // `aligned` with a valid `T`, the referent of `aligned` is a + // valid `T`. + let aligned = unsafe { aligned.assume_validity::() }; + + // SAFETY: This invocation satisfies the safety contract of + // `Box::from_raw` [1], because `aligned` is directly derived from + // `Box::into_raw`. By LEMMA 1, `aligned`'s referent is additionally + // a valid instance of `T`. The layouts of `T` and `MaybeUninit` + // are the same, by invariant on `MaybeUninit`. + // + // [1] Per https://doc.rust-lang.org/1.81.0/alloc/boxed/struct.Box.html#method.from_raw: + // + // It is valid to convert both ways between a `Box`` and a raw + // pointer allocated with the `Global`` allocator, given that + // the `Layout` used with the allocator is correct for the + // type. + let _ = unsafe { Ptr::into_box(aligned) }; + } + } + } + + /// Run `T`'s destructor. + /// + /// # Safety + /// + /// See `KnownLayout::destroy`. + /// + /// # Tests + /// + /// ```compile_fail,E0080 + /// use zerocopy::*; + /// use zerocopy_derive::*; + /// + /// #[derive(KnownLayout)] + /// #[repr(C, align(2))] + /// struct NeedsDrop(u16); + /// + /// impl Drop for NeedsDrop { + /// fn drop(&mut self) {} + /// } + /// + /// let mut val = NeedsDrop(42); + /// let ptr = Ptr::from_mut(&mut val).forget_aligned(); + /// let _ = unsafe { util::destroy::destroy_unsized::(ptr) }; + /// ``` + #[cfg(not(feature = "alloc"))] + #[inline] + pub unsafe fn destroy_unsized( + ptr: MaybeAligned<'_, T, invariant::Exclusive>, + ) { + // In environments without allocators, we cannot run `T`'s non-trivial + // destructor if `T` is non-trivially aligned, since it is presently + // impossible to statically allocate a well-aligned (and, thus, + // droppable) buffer of dynamic size. + // + // Rather than panic or forgetting `T` (which might be unexpected) in + // such cases, we emit a post-monomorphization error; the user can + // explicitly choose to forget their type by wrapping it in + // `ManuallyDrop`. + #[cfg(zerocopy_unsized_needs_drop_1_63_0)] + static_assert!( + T: ?Sized + KnownLayout => + !T::NEEDS_DROP || T::LAYOUT.is_trivially_aligned() + ); + // Prior to 1.63.0, `core::mem::needs_drop` requires `T: Sized`, so on + // earlier versions we cannot relax the alignment check for trivially + // droppable types. + #[cfg(not(zerocopy_unsized_needs_drop_1_63_0))] + static_assert!( + T: ?Sized + KnownLayout => T::LAYOUT.is_trivially_aligned() + ); + + // We can run the destructor of well-aligned `T`. + if let Ok(ptr) = ptr.try_recall_trivially_aligned() { + // SAFETY: By contract on the caller, this function is only invoked + // from the destructor of an transitive owner `ptr`'s referent, and + // `ptr`'s referent is never subsequently re-accessed. + unsafe { + ptr.drop_in_place(); + } + } + } + + /// Run `T`'s destructor. + /// + /// # Safety + /// + /// See `KnownLayout::destroy`. + #[inline] + pub unsafe fn destroy_sized(ptr: MaybeAligned<'_, T, invariant::Exclusive>) { + match ptr.try_recall_trivially_aligned() { + // If `T` is trivially aligned, it can simply be dropped in place. + Ok(ptr) => { + // SAFETY: By contract on the caller, this function + // is only invoked from the destructor of an + // transitive owner `ptr`'s referent, and `ptr`'s + // referent is never subsequently re-accessed. + unsafe { + ptr.drop_in_place(); + } + } + // If `T` is not trivially-aligned, read it onto the stack (so it is + // well-aligned) and drop it. + Err(ptr) => { + // SAFETY: By contract on the caller, this function is only + // invoked from the destructor of an transitive owner `ptr`'s + // referent, and `ptr`'s referent is never subsequently + // re-accessed. + let _ = unsafe { ptr.read_unaligned_unchecked::() }; + } + } + } +} + /// Since we support multiple versions of Rust, there are often features which /// have been stabilized in the most recent stable release which do not yet /// exist (stably) on our MSRV. This module provides polyfills for those @@ -1014,6 +1275,46 @@ mod tests { fn test_round_down_to_next_multiple_of_alignment_zerocopy_panic_in_const_and_vec_try_reserve() { round_down_to_next_multiple_of_alignment(0, NonZeroUsize::new(3).unwrap()); } + + #[test] + #[should_panic] + fn destroy_sized() { + use zerocopy::*; + use zerocopy_derive::*; + + #[derive(KnownLayout, FromZeros)] + #[repr(C, align(2))] + struct NeedsDrop(u8); + + impl Drop for NeedsDrop { + fn drop(&mut self) { + panic!("dropped successfully") + } + } + + let val = UnalignUnsized::<[NeedsDrop]>::new_box_zeroed_with_elems(42); + drop(val) + } + + #[test] + #[should_panic] + fn destroy_sized() { + use crate::*; + use zerocopy_derive::*; + + #[derive(KnownLayout, FromZeros)] + #[repr(align(2))] + struct NeedsDrop(u8); + + impl Drop for NeedsDrop { + fn drop(&mut self) { + panic!("dropped successfully") + } + } + + let val = UnalignUnsized::::new_box_zeroed(); + drop(val) + } } #[cfg(kani)] diff --git a/src/wrappers.rs b/src/wrappers.rs index 74cd5846c3..a51e9ccaa8 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -452,6 +452,202 @@ impl Display for Unalign { } } +/// A possibly-unsized type with no alignment requirement. +/// +/// An `UnalignUnsized` wraps a `T`, removing any alignment requirement. +/// `UnalignUnsized` has the same size and bit validity as `T`, but not +/// necessarily the same alignment [or ABI]. This is useful if a type with an +/// alignment requirement needs to be read from a chunk of memory which provides +/// no alignment guarantees. +/// +/// [or ABI]: https://github.com/google/zerocopy/issues/164 +/// +/// # Safety +/// +/// `UnalignUnsized` is guaranteed to have the same size and bit validity as +/// `T`, and to have [`UnsafeCell`]s covering the same byte ranges as `T`. +/// `UnalignUnsized` is guaranteed to have alignment 1. +#[repr(C, packed)] +pub struct UnalignUnsized(ManuallyDrop) +where + T: KnownLayout; + +// SAFETY: Mostly delegates safety to `T`, except in the cases of layout +// alignment and `destroy`. +unsafe impl KnownLayout for UnalignUnsized { + #[allow(clippy::missing_inline_in_public_items)] + #[cfg_attr(coverage_nightly, coverage(off))] + fn only_derive_is_allowed_to_implement_this_trait() {} + + // SAFETY: By invariant on `UnalignUnsized`, `T` and `UnalignUnsized` + // have the same layout (excepting alignment) and therefore the same pointer + // metadata kinds. + type PointerMetadata = ::PointerMetadata; + + // SAFETY: `UnalignUnsized` and `UnalignUnsized` have + // identical `LAYOUT`s, because `T` and `T::MaybeUninit` have identical + // layouts. + type MaybeUninit = UnalignUnsized<::MaybeUninit>; + + const NEEDS_DROP: bool = T::NEEDS_DROP; + + // SAFETY: By invariant on `UnalignUnsized`, `UnalignUnsize`'s layout has + // the same `size_info` as `T`, but an alignment of 1. + const LAYOUT: DstLayout = DstLayout { + // The alignment is `1`, since `Self` is `repr(packed)`. + align: DstLayout::MIN_ALIGN, + // Otherwise, we retain the size of the inner `T`. + size_info: ::LAYOUT.size_info, + }; + + // SAFETY: The returned pointer has the same address and provenance as + // `bytes`, aince all operations here preserve provenance. If `Self` is a + // DST, the returned pointer's referent has `elems` elements in its trailing + // slice, since (by invariant on `UnalignUnsized`), `UnalignUnsize`'s + // layout has the same `size_info` as `T` (and thus the same pointer + // metadata). + // + // TODO(#429): Add documentation to `NonNull::new_unchecked` + // that it preserves provenance. + #[inline(always)] + fn raw_from_ptr_len(bytes: NonNull, meta: Self::PointerMetadata) -> NonNull { + #[allow(clippy::as_conversions)] + let ptr = ::raw_from_ptr_len(bytes, meta).as_ptr() as *mut Self; + // SAFETY: `ptr` was converted from `bytes`, which is non-null. + unsafe { NonNull::new_unchecked(ptr) } + } + + // SAFETY: All operations preserve provenance. `UnalignUnsize`'s layout + // has the same `size_info` as `T` (and thus the same pointer metadata), and + // we assume — by contract on `KnownLayout` that `::pointer_to_metadata` + // is implemented correctly. + #[inline(always)] + fn pointer_to_metadata(ptr: *mut Self) -> Self::PointerMetadata { + #[allow(clippy::as_conversions)] + let ptr = ptr as *mut T; + ::pointer_to_metadata(ptr) + } + + #[inline(always)] + unsafe fn destroy(ptr: MaybeAligned<'_, Self, invariant::Exclusive>) { + // SAFETY: Because `ptr` carries `invariant::Exclusive`, there are no + // safety preconditions. + let ptr = match unsafe { ptr.try_cast::() } { + Ok(ptr) => ptr, + Err(_) => { + // SAFETY: By postcondition on `Ptr::try_cast`, `Err` is only + // produced if the resulting cast would reference more bytes + // than referenced the input `ptr`. This is impossible, since, + // by invariant on `UnalignUnsized`, `T` and `UnalignUnsized` + // are guaranteed to have the same size. + unsafe { core::hint::unreachable_unchecked() } + } + }; + // SAFETY: By invariant on `UnalignUnsized`, it has the same + // bit-validity as `T`. Thus, what was a valid pointer to + // `UnalignUnsized` is now a valid pointer to `T`. + let ptr = unsafe { ptr.assume_valid() }; + // SAFETY: By invariant on the caller, this function is called from the + // destructor of an transitive owner `ptr`'s referent. + unsafe { + KnownLayout::destroy(ptr); + } + } +} + +impl Drop for UnalignUnsized +where + T: KnownLayout, +{ + #[inline] + fn drop(&mut self) { + let ptr = Ptr::from_mut(self).forget_aligned(); + // SAFETY: This function is called from the owner of `ptr`'s referent. + // After `drop` completes, it it is forbidden to re-use `ptr` or its + // referent. + unsafe { Self::destroy(ptr) } + } +} + +impl Deref for UnalignUnsized { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &T { + Ptr::from_ref(self).transparent_wrapper_into_inner().bikeshed_recall_aligned().as_ref() + } +} + +impl DerefMut for UnalignUnsized { + #[inline(always)] + fn deref_mut(&mut self) -> &mut T { + Ptr::from_mut(self).transparent_wrapper_into_inner().bikeshed_recall_aligned().as_mut() + } +} + +impl PartialOrd> + for UnalignUnsized +{ + #[inline(always)] + fn partial_cmp(&self, other: &UnalignUnsized) -> Option { + PartialOrd::partial_cmp(self.deref(), other.deref()) + } +} + +impl Ord for UnalignUnsized { + #[inline(always)] + fn cmp(&self, other: &UnalignUnsized) -> Ordering { + Ord::cmp(self.deref(), other.deref()) + } +} + +impl PartialEq> + for UnalignUnsized +{ + #[inline(always)] + fn eq(&self, other: &UnalignUnsized) -> bool { + PartialEq::eq(self.deref(), other.deref()) + } +} + +impl Eq for UnalignUnsized {} + +impl Hash for UnalignUnsized { + #[inline(always)] + fn hash(&self, state: &mut H) + where + H: Hasher, + { + self.deref().hash(state); + } +} + +impl Debug for UnalignUnsized { + #[inline(always)] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Debug::fmt(self.deref(), f) + } +} + +impl Display for UnalignUnsized { + #[inline(always)] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + Display::fmt(self.deref(), f) + } +} + +safety_comment! { + /// SAFETY: + /// By invariant on `UnalignUnsized`, `UnalignUnsized` is + /// guaranteed to have the same size and bit validity as `T`. + unsafe_impl!(T: ?Sized + KnownLayout + Immutable => Immutable for UnalignUnsized); + unsafe_impl!(T: ?Sized + KnownLayout + TryFromBytes => TryFromBytes for UnalignUnsized); + unsafe_impl!(T: ?Sized + KnownLayout + FromZeros => FromZeros for UnalignUnsized); + unsafe_impl!(T: ?Sized + KnownLayout + FromBytes => FromBytes for UnalignUnsized); + unsafe_impl!(T: ?Sized + KnownLayout + IntoBytes => IntoBytes for UnalignUnsized); + unsafe_impl!(T: ?Sized + KnownLayout => Unaligned for UnalignUnsized); +} + /// A wrapper type to construct uninitialized instances of `T`. /// /// `MaybeUninit` is identical to the [standard library @@ -723,19 +919,13 @@ mod tests { } #[test] -<<<<<<< HEAD #[allow(clippy::as_conversions)] -======= ->>>>>>> Add initial support for unsized `MaybeUninit` wrapper type fn test_maybe_uninit() { // int { let input = 42; let uninit = MaybeUninit::new(input); -<<<<<<< HEAD // SAFETY: `uninit` is in an initialized state -======= ->>>>>>> Add initial support for unsized `MaybeUninit` wrapper type let output = unsafe { uninit.assume_init() }; assert_eq!(input, output); } @@ -744,10 +934,7 @@ mod tests { { let input = 42; let uninit = MaybeUninit::new(&input); -<<<<<<< HEAD // SAFETY: `uninit` is in an initialized state -======= ->>>>>>> Add initial support for unsized `MaybeUninit` wrapper type let output = unsafe { uninit.assume_init() }; assert_eq!(&input as *const _, output as *const _); assert_eq!(input, *output); @@ -757,10 +944,7 @@ mod tests { { let input = [1, 2, 3, 4]; let uninit = MaybeUninit::new(&input[..]); -<<<<<<< HEAD // SAFETY: `uninit` is in an initialized state -======= ->>>>>>> Add initial support for unsized `MaybeUninit` wrapper type let output = unsafe { uninit.assume_init() }; assert_eq!(&input[..] as *const _, output as *const _); assert_eq!(input, *output); diff --git a/zerocopy-derive/src/lib.rs b/zerocopy-derive/src/lib.rs index a94ba719be..aa8a46c2d7 100644 --- a/zerocopy-derive/src/lib.rs +++ b/zerocopy-derive/src/lib.rs @@ -347,6 +347,19 @@ fn derive_known_layout_inner(ast: &DeriveInput, _top_level: Trait) -> Result () {} + + unsafe fn destroy( + ptr: ::zerocopy::MaybeAligned< + '_, + Self, + ::zerocopy::pointer::invariant::Exclusive, + >, + ) { + // SAFETY: The preconditions of `destroy_sized` are + // identical to that of `destroy` and are ensured by the + // caller. + unsafe { ::zerocopy::util::destroy::destroy_sized(ptr) } + } ), None, ) diff --git a/zerocopy-derive/src/output_tests.rs b/zerocopy-derive/src/output_tests.rs index 6b1931f80f..5202cbf94b 100644 --- a/zerocopy-derive/src/output_tests.rs +++ b/zerocopy-derive/src/output_tests.rs @@ -125,6 +125,16 @@ fn test_known_layout() { #[inline(always)] fn pointer_to_metadata(_ptr: *mut Self) -> () {} + + unsafe fn destroy( + ptr: ::zerocopy::MaybeAligned< + '_, + Self, + ::zerocopy::pointer::invariant::Exclusive, + >, + ) { + unsafe { ::zerocopy::util::destroy::destroy_sized(ptr) } + } } } no_build }