From e8cc8eb31d3ddc7e23fac1ebaedf897e903b776a Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Sun, 6 Oct 2024 01:24:03 +0000 Subject: [PATCH] [wip] UnalignUnsized --- src/layout.rs | 7 ++- src/lib.rs | 127 +++++++++++++++++++++++++++++++++++++++++++++ src/pointer/mod.rs | 22 ++++++++ src/pointer/ptr.rs | 53 +++++++++++++++++++ src/util/macros.rs | 40 +++++++++++++- src/util/mod.rs | 2 + src/wrappers.rs | 110 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 358 insertions(+), 3 deletions(-) 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..ef84c148f6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -797,6 +797,25 @@ 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)] + unsafe fn destroy(ptr: MaybeAligned<'_, Self, invariant::Exclusive>); } /// The metadata associated with a [`KnownLayout`] type. @@ -939,6 +958,114 @@ unsafe impl KnownLayout for [T] { // struct `Foo(i32, [u8])` or `(u64, Foo)`. slc.len() } + + #[cfg(feature = "alloc")] + #[inline] + unsafe fn destroy(ptr: MaybeAligned<'_, Self, invariant::Exclusive>) { + match ptr.try_recall_trivially_aligned() { + // If `Self` 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 = Box::into_raw(aligned); + + // Next, we copy `ptr`'s referent to `aligned`. + let size = meta.size_for_metadata(Self::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 *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 `Self`. It is well-aligned, because it was + // initialized from a `Box>`, whose referent + // has the same alignment as `Self`. It is valid, because + // because the preceeding `copy_nonoverlapping`, initialized its + // referent with a valid instance of `Self.` + #[allow(clippy::as_conversions)] + 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 layouts of + // `Self` 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 { Box::from_raw(aligned) }; + } + } + } + + #[cfg(not(feature = "alloc"))] + #[inline] + unsafe fn destroy(ptr: MaybeAligned<'_, Self, 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`. + static_assert!( + Self: ?Sized + KnownLayout => + !core::mem::needs_drop::() || Self::LAYOUT.is_trivially_aligned() + ); + // Nonetheless, 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(); + } + } + } } #[rustfmt::skip] 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..bb89f828f8 100644 --- a/src/pointer/ptr.rs +++ b/src/pointer/ptr.rs @@ -815,6 +815,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`. /// @@ -1641,6 +1660,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..273f45b474 100644 --- a/src/util/macros.rs +++ b/src/util/macros.rs @@ -582,6 +582,32 @@ 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>) { + match ptr.try_recall_trivially_aligned() { + // If `Self` 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 `Self` 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::() + }; + } + } + } } }; }; @@ -599,7 +625,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 +646,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. @@ -633,6 +659,16 @@ macro_rules! unsafe_impl_known_layout { let ptr = ptr as *mut $repr; <$repr>::pointer_to_metadata(ptr) } + + #[inline] + unsafe fn destroy(_: crate::MaybeAligned<'_, Self, crate::invariant::Exclusive>) + { + // Do nothing, except static assert that `Self` is trivially + // drop. This is permissable for all `Self`s, and, in fact, + // ideal for all `unsafe_impl_known_layout` targets except + // `UnsafeCell` (which might have a non-trivial drop). + static_assert!(Self: ?Sized => !core::mem::needs_drop::()); + } } }; }; diff --git a/src/util/mod.rs b/src/util/mod.rs index a742d7cc1a..b36cc0b9c2 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 710e584e22..718de598df 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -452,6 +452,116 @@ 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. +#[derive(Debug)] +#[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 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