diff --git a/src/lib.rs b/src/lib.rs index 0576b4a4a1..895ac397bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -796,6 +796,16 @@ pub unsafe trait KnownLayout { // resulting size would not fit in a `usize`. meta.size_for_metadata(Self::LAYOUT) } + + /// Run `slf`'s destructor (if any). + /// + /// # Safety + /// + /// `slf`'s destructor must not already have been run. After invoking this + /// function, re-using `slf` may be undefined behavior. + #[cfg(zerocopy_unstable)] + #[doc(hidden)] + unsafe fn drop(slf: &mut UnalignUnsized); } /// The metadata associated with a [`KnownLayout`] type. @@ -920,6 +930,57 @@ unsafe impl KnownLayout for [T] { // struct `Foo(i32, [u8])` or `(u64, Foo)`. slc.len() } + + #[cfg(feature = "alloc")] + unsafe fn drop(slf: &mut UnalignUnsized) { + let meta = KnownLayout::pointer_to_metadata(slf); + let size = meta.size_for_metadata(Self::LAYOUT).unwrap(); + let aligned = MaybeUninit::::new_boxed_uninit(meta).unwrap(); + let aligned = Box::into_raw(aligned); + // SAFETY: This invocation satisfies the safety contract of + // copy_nonoverlapping [1]: + // - `slf as *mut _ as *mut u8` is valid for reads of `size` bytes + // - `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 { + core::ptr::copy_nonoverlapping(slf as *mut _ as *mut u8, aligned as *mut u8, size); + } + // LEMMA 1: `aligned`'s referent is a bit-valid and aligned instance of + // `Self`. In its declaration, `aligned` was initialized from a + // `Box>`, which has the same alignment as `Self`. + // Then, via the preceeding `copy_nonoverlapping`, `aligned` was + // initialized with a valid instance of `Self.` + let aligned = aligned as *mut Self; + // 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 `Self`. The `Layout`s of `Self` and + // `MaybeUninit` are the same, by contract 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. + unsafe { + // drop `aligned` + let _ = Box::from_raw(aligned); + } + } + + #[cfg(not(feature = "alloc"))] + unsafe fn drop(_: &mut UnalignUnsized) { + // PME if T needs drop. + static_assert!(T=> !core::mem::needs_drop::()); + } } #[rustfmt::skip] diff --git a/src/util/macros.rs b/src/util/macros.rs index 03805ada25..9de47f51a5 100644 --- a/src/util/macros.rs +++ b/src/util/macros.rs @@ -546,6 +546,10 @@ macro_rules! impl_known_layout { #[inline(always)] fn pointer_to_metadata(_ptr: *mut Self) -> () { } + + unsafe fn drop(slf: &mut crate::UnalignUnsized) { + let _ = unsafe { slf.take() }; + } } }; }; @@ -563,7 +567,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; @@ -597,6 +601,13 @@ macro_rules! unsafe_impl_known_layout { let ptr = ptr as *mut $repr; <$repr>::pointer_to_metadata(ptr) } + + unsafe fn drop(slf: &mut UnalignUnsized) { + // SAFETY: TODO + let slf = unsafe { &mut *(slf as *mut _ as *mut UnalignUnsized<$repr>) }; + // SAFETY: TODO + unsafe { KnownLayout::drop(slf) } + } } }; }; diff --git a/src/util/mod.rs b/src/util/mod.rs index a05700cf02..bff576ef73 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()); }; diff --git a/src/wrappers.rs b/src/wrappers.rs index 04a9eb9f80..7e7c5a5c99 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -464,6 +464,92 @@ impl Display for Unalign { } } +/// A type with no alignment requirement. +#[derive(Debug)] +#[repr(C, packed)] +pub struct UnalignUnsized(ManuallyDrop) +where + T: KnownLayout; + +// SAFETY: TODO +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() {} + + type PointerMetadata = ::PointerMetadata; + + type MaybeUninit = UnalignUnsized<::MaybeUninit>; + + const LAYOUT: DstLayout = DstLayout { + // The alignment is `1`, since `Self` is `repr(packed)`. + align: unsafe { NonZeroUsize::new_unchecked(1) }, + // Otherwise, we retain the size of the inner `T`. + size_info: ::LAYOUT.size_info, + }; + + #[inline(always)] + fn raw_from_ptr_len( + bytes: NonNull, + meta: ::PointerMetadata, + ) -> NonNull { + #[allow(clippy::as_conversions)] + let ptr = ::raw_from_ptr_len(bytes, meta).as_ptr() as *mut Self; + unsafe { NonNull::new_unchecked(ptr) } + } + + #[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 drop(slf: &mut UnalignUnsized) { + let slf = slf.peel(); + // SAFETY: By contract on the caller, this method — and, by extension, + // the below invocation — is executed at most once. + unsafe { + KnownLayout::drop(slf); + } + } +} + +impl UnalignUnsized +where + T: KnownLayout, +{ + pub(crate) unsafe fn take(&mut self) -> T { + let inner = core::ptr::addr_of!(self.0) as *const _; + // SAFETY: TODO + unsafe { core::ptr::read_unaligned(inner) } + } +} + +impl UnalignUnsized> +where + T: KnownLayout, +{ + pub(crate) fn peel(&mut self) -> &mut UnalignUnsized { + // SAFETY: TODO + unsafe { &mut *(self as *mut _ as *mut UnalignUnsized) } + } +} + +impl Drop for UnalignUnsized +where + T: KnownLayout, +{ + fn drop(&mut self) { + if core::mem::needs_drop::() { + // SAFETY: By contract on the caller, this method — and, by + // extension, the below invocation — is executed at most once. + unsafe { T::drop(self) } + } + } +} + /// A wrapper type to construct uninitialized instances of `T`. /// /// `MaybeUninit` is identical to the [standard library