From 8d58fffdb89b662c772e90b029c73fe415e653ed Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Sun, 6 Oct 2024 01:24:03 +0000 Subject: [PATCH] [wip] UnalignUnsized --- .github/workflows/ci.yml | 27 +++++ Cargo.toml | 3 + src/layout.rs | 7 +- src/lib.rs | 24 ++++ src/pointer/mod.rs | 22 ++++ src/pointer/ptr.rs | 103 ++++++++++++++++ src/util/macros.rs | 12 +- src/util/mod.rs | 179 ++++++++++++++++++++++++++++ src/wrappers.rs | 129 ++++++++++++++++++-- zerocopy-derive/src/lib.rs | 13 ++ zerocopy-derive/src/output_tests.rs | 10 ++ 11 files changed, 514 insertions(+), 15 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 691c243994..5e5a743dd1 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..cf0ae3605c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -797,6 +797,30 @@ 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. + 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..c29c3980cf 100644 --- a/src/pointer/ptr.rs +++ b/src/pointer/ptr.rs @@ -424,6 +424,36 @@ 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); + let ptr = unsafe { NonNull::new_unchecked(ptr) }; + unsafe { Self::new(ptr) } + } + + /// Constructs a `Box` from a `Ptr`. + /// + /// # Safety + /// + /// This must be invoked with a `Ptr` to a referent allocated by a + /// `Box`, where the size and alignments of `T` and `U` are equal. + #[inline] + pub(crate) unsafe fn into_box(ptr: Self) -> alloc::boxed::Box { + let ptr = ptr.as_mut() as *mut _; + // SAFETY: TODO + unsafe { alloc::boxed::Box::from_raw(ptr) } + } + } + /// `Ptr<'a, T>` → `&'a T` impl<'a, T, I> Ptr<'a, T, I> where @@ -815,6 +845,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 +1003,26 @@ mod _casts { T: 'a + ?Sized, I: Invariants, { + /// Casts to a different (unsized) target type. + #[inline] + pub fn try_cast(self) -> Result, Self> + where + T: KnownLayout, + U: 'a + ?Sized + KnownLayout, + { + let ptr = self.as_non_null(); + let src_size = T::size_of_val_raw(ptr); + let meta = T::pointer_to_metadata(ptr.as_ptr()); + if meta.size_for_metadata(U::LAYOUT) <= src_size { + let bytes = ptr.cast::(); + let ptr = U::raw_from_ptr_len(bytes, meta); + // SAFETY: TODO + Ok(unsafe { Ptr::new(ptr) }) + } else { + Err(self) + } + } + /// Casts to a different (unsized) target type. /// /// # Safety @@ -1641,6 +1710,40 @@ 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) } + } + } +} + #[cfg(test)] mod tests { use core::mem::{self, MaybeUninit}; 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..7cfaf66ea2 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -678,6 +678,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 +824,183 @@ 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::PointerMetadata; + use crate::Ptr; + + 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 = MaybeUninit::::new_boxed_uninit(meta).expect("unreachable"); + let aligned = Ptr::from_box(aligned); + + // Next, we copy `ptr`'s referent to `aligned`. + let size = meta.size_for_metadata(T::LAYOUT).expect("unreachable"); + // 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. + // + // LEMMA 1: `aligned`'s referent is a bit-valid and aligned instance + // of `T`. It is well-aligned, because it was initialized from a + // `Box>`, whose referent has the same alignment as + // `T`. It is valid, because because the preceeding + // `copy_nonoverlapping`, initialized its referent with a valid + // instance of `T.` + #[allow(clippy::as_conversions)] + let aligned = aligned.try_cast::().expect("unreachable"); + // SAFETY: TODO + let aligned = unsafe { aligned.assume_alignment::() }; + // SAFETY: TODO + 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`. + #[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 => + !core::mem::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 diff --git a/src/wrappers.rs b/src/wrappers.rs index 74cd5846c3..af8a19ccc7 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -452,6 +452,123 @@ 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>; + + // 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: + // - `p as *mut T` preserves provenance + // - By contract on `UnalignUnsized`, it and `T` have the same + // `size_info`, and therefore casting references the same set of + // bytes. + #[allow(clippy::as_conversions)] + let ptr = unsafe { ptr.cast_unsized(|p| p as *mut T) }; + // 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 Debug for UnalignUnsized { + #[inline(always)] + fn fmt(&self, _: &mut Formatter<'_>) -> fmt::Result { + // TODO + Ok(()) + } +} + +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) } + } +} + /// A wrapper type to construct uninitialized instances of `T`. /// /// `MaybeUninit` is identical to the [standard library @@ -723,19 +840,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 +855,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 +865,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 }