diff --git a/src/lib.rs b/src/lib.rs index 68678c378c..891f571076 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -503,6 +503,36 @@ pub unsafe trait KnownLayout { /// elements in its trailing slice. #[doc(hidden)] fn raw_from_ptr_len(bytes: NonNull, meta: Self::PointerMetadata) -> NonNull; + + /// Extracts the metadata from a pointer to `Self`. + /// + /// # Safety + /// + /// `pointer_to_metadata` always returns the correct metadata stored in + /// `ptr`. + #[doc(hidden)] + fn pointer_to_metadata(ptr: NonNull) -> Self::PointerMetadata; + + /// Computes the length of the byte range addressed by `ptr`. + /// + /// Returns `None` if the resulting length would not fit in an `usize`. + /// + /// # Safety + /// + /// Callers may assume that `size_of_val_raw` always returns the correct + /// size. + /// + /// Callers may assume that, if `ptr` addresses a byte range whose length + /// fits in an `usize`, this will return `Some`. + #[doc(hidden)] + #[must_use] + #[inline(always)] + fn size_of_val_raw(ptr: NonNull) -> Option { + let meta = Self::pointer_to_metadata(ptr); + // SAFETY: `size_for_metadata` promises to only return `None` if the + // resulting size would not fit in a `usize`. + meta.size_for_metadata(Self::LAYOUT) + } } /// The metadata associated with a [`KnownLayout`] type. @@ -522,6 +552,11 @@ pub trait PointerMetadata { /// If `Self = ()`, `layout` must describe a sized type. If `Self = usize`, /// `layout` must describe a slice DST. Otherwise, `size_for_metadata` may /// panic. + /// + /// # Safety + /// + /// `size_for_metadata` promises to only return `None` if the resulting size + /// would not fit in a `usize`. fn size_for_metadata(&self, layout: DstLayout) -> Option; } @@ -581,6 +616,41 @@ unsafe impl KnownLayout for [T] { #[allow(unstable_name_collisions)] NonNull::slice_from_raw_parts(data.cast::(), elems) } + + #[inline(always)] + fn pointer_to_metadata(ptr: NonNull<[T]>) -> usize { + #[allow(clippy::as_conversions)] + let slc = ptr.as_ptr() as *const [()]; + + // SAFETY: + // - `()` has alignment 1, so `slc` is trivially aligned. + // - `slc` was derived from a non-null pointer. + // - The size is 0 regardless of the length, so it is sound to + // materialize a reference regardless of location. + // - By invariant, `self.ptr` has valid provenance. + let slc = unsafe { &*slc }; + + // This is correct because the preceding `as` cast preserves the number + // of slice elements. Per + // https://doc.rust-lang.org/nightly/reference/expressions/operator-expr.html#slice-dst-pointer-to-pointer-cast: + // + // For slice types like `[T]` and `[U]`, the raw pointer types `*const + // [T]`, `*mut [T]`, `*const [U]`, and `*mut [U]` encode the number of + // elements in this slice. Casts between these raw pointer types + // preserve the number of elements. Note that, as a consequence, such + // casts do *not* necessarily preserve the size of the pointer's + // referent (e.g., casting `*const [u16]` to `*const [u8]` will result + // in a raw pointer which refers to an object of half the size of the + // original). The same holds for `str` and any compound type whose + // unsized tail is a slice type, such as struct `Foo(i32, [u8])` or + // `(u64, Foo)`. + // + // TODO(#429), + // TODO(https://github.com/rust-lang/reference/pull/1417): Once this + // text is available on the Stable docs, cite those instead of the + // Nightly docs. + slc.len() + } } #[rustfmt::skip] diff --git a/src/macros.rs b/src/macros.rs index e844e3e55c..062cb250d5 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -555,6 +555,10 @@ macro_rules! impl_known_layout { fn raw_from_ptr_len(bytes: NonNull, _meta: ()) -> NonNull { bytes.cast::() } + + #[inline(always)] + fn pointer_to_metadata(_ptr: NonNull) -> () { + } } }; }; @@ -598,6 +602,14 @@ macro_rules! unsafe_impl_known_layout { // SAFETY: `ptr` was converted from `bytes`, which is non-null. unsafe { NonNull::new_unchecked(ptr) } } + + #[inline(always)] + fn pointer_to_metadata(ptr: NonNull) -> Self::PointerMetadata { + // SAFETY: `ptr` is non-null. + #[allow(clippy::as_conversions)] + let ptr = unsafe { NonNull::new_unchecked(ptr.as_ptr() as *mut $repr) }; + <$repr>::pointer_to_metadata(ptr) + } } }; }; diff --git a/src/pointer/mod.rs b/src/pointer/mod.rs index 94f0dd9ce1..74153da103 100644 --- a/src/pointer/mod.rs +++ b/src/pointer/mod.rs @@ -66,7 +66,7 @@ where /// Checks if the referent is zeroed. pub(crate) fn is_zeroed(ptr: Ptr<'_, T, I>) -> bool where - T: crate::Immutable, + T: crate::Immutable + crate::KnownLayout, I: invariant::Invariants, I::Aliasing: invariant::AtLeast, { diff --git a/src/pointer/ptr.rs b/src/pointer/ptr.rs index f9b3e96b7c..37ac90f7b2 100644 --- a/src/pointer/ptr.rs +++ b/src/pointer/ptr.rs @@ -985,24 +985,35 @@ mod _casts { impl<'a, T, I> Ptr<'a, T, I> where - T: 'a, + T: 'a + KnownLayout + ?Sized, I: Invariants, T: Immutable, { /// Casts this pointer-to-initialized into a pointer-to-bytes. #[allow(clippy::wrong_self_convention)] pub(crate) fn as_bytes(self) -> Ptr<'a, [u8], (I::Aliasing, Aligned, Valid)> { + let bytes = match T::size_of_val_raw(self.as_non_null()) { + Some(bytes) => bytes, + // SAFETY: `KnownLayout::size_of_val_raw` promises to always + // return `Some` so long as the resulting size fits in a + // `usize`. By invariant on `Ptr`, `self` refers to a range of + // bytes whose size fits in an `isize`, which implies that it + // also fits in a `usize`. + None => unsafe { core::hint::unreachable_unchecked() }, + }; + // SAFETY: We ensure that: // - `cast(p)` is implemented as an invocation to // `slice_from_raw_parts_mut`. // - The size of the object referenced by the resulting pointer is - // exactly equal to the size of the object referenced by `self`. + // exactly equal to the size of the object referenced by `self` as + // guaranteed by `KnownLayout::size_of_val_raw` // - `T` and `[u8]` trivially contain `UnsafeCell`s at identical // ranges [u8]`, because both are `Immutable`. let ptr: Ptr<'a, [u8], _> = unsafe { self.cast_unsized(|p: *mut T| { #[allow(clippy::as_conversions)] - core::ptr::slice_from_raw_parts_mut(p.cast::(), core::mem::size_of::()) + core::ptr::slice_from_raw_parts_mut(p.cast::(), bytes) }) }; @@ -1314,48 +1325,35 @@ mod _project { } } + impl<'a, T, I> Ptr<'a, T, I> + where + T: 'a + KnownLayout + ?Sized, + I: Invariants, + { + /// The number of trailing slice elements in the object referenced by + /// `self`. + /// + /// # Safety + /// + /// Unsafe code my rely on `trailing_slice_len` satisfying the above + /// contract. + pub(super) fn trailing_slice_len(&self) -> usize { + T::pointer_to_metadata(self.as_non_null()) + } + } + impl<'a, T, I> Ptr<'a, [T], I> where T: 'a, I: Invariants, { - /// The number of slice elements referenced by `self`. + /// The number of slice elements in the object referenced by `self`. /// /// # Safety /// /// Unsafe code my rely on `len` satisfying the above contract. pub(super) fn len(&self) -> usize { - #[allow(clippy::as_conversions)] - let slc = self.as_non_null().as_ptr() as *const [()]; - - // SAFETY: - // - `()` has alignment 1, so `slc` is trivially aligned. - // - `slc` was derived from a non-null pointer. - // - The size is 0 regardless of the length, so it is sound to - // materialize a reference regardless of location. - // - By invariant, `self.ptr` has valid provenance. - let slc = unsafe { &*slc }; - - // This is correct because the preceding `as` cast preserves the - // number of slice elements. Per - // https://doc.rust-lang.org/nightly/reference/expressions/operator-expr.html#slice-dst-pointer-to-pointer-cast: - // - // For slice types like `[T]` and `[U]`, the raw pointer types - // `*const [T]`, `*mut [T]`, `*const [U]`, and `*mut [U]` encode - // the number of elements in this slice. Casts between these raw - // pointer types preserve the number of elements. Note that, as a - // consequence, such casts do *not* necessarily preserve the size - // of the pointer's referent (e.g., casting `*const [u16]` to - // `*const [u8]` will result in a raw pointer which refers to an - // object of half the size of the original). The same holds for - // `str` and any compound type whose unsized tail is a slice type, - // such as struct `Foo(i32, [u8])` or `(u64, Foo)`. - // - // TODO(#429), - // TODO(https://github.com/rust-lang/reference/pull/1417): Once this - // text is available on the Stable docs, cite those instead of the - // Nightly docs. - slc.len() + self.trailing_slice_len() } /// Iteratively projects the elements `Ptr` from `Ptr<[T]>`. diff --git a/zerocopy-derive/src/lib.rs b/zerocopy-derive/src/lib.rs index fe3f9f966c..0155aa3bb4 100644 --- a/zerocopy-derive/src/lib.rs +++ b/zerocopy-derive/src/lib.rs @@ -197,14 +197,21 @@ fn derive_known_layout_inner(ast: &DeriveInput) -> proc_macro2::TokenStream { #[inline(always)] fn raw_from_ptr_len( bytes: ::zerocopy::macro_util::core_reexport::ptr::NonNull, - meta: <#trailing_field_ty as ::zerocopy::KnownLayout>::PointerMetadata, + meta: Self::PointerMetadata, ) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull { - use ::zerocopy::{KnownLayout}; + use ::zerocopy::KnownLayout; let trailing = <#trailing_field_ty as KnownLayout>::raw_from_ptr_len(bytes, meta); let slf = trailing.as_ptr() as *mut Self; // SAFETY: Constructed from `trailing`, which is non-null. unsafe { ::zerocopy::macro_util::core_reexport::ptr::NonNull::new_unchecked(slf) } } + + #[inline(always)] + fn pointer_to_metadata(ptr: ::zerocopy::macro_util::core_reexport::ptr::NonNull) -> Self::PointerMetadata { + // SAFETY: `ptr` is non-null. + let ptr = unsafe { ::zerocopy::macro_util::core_reexport::ptr::NonNull::new_unchecked(ptr.as_ptr() as *mut _) }; + <#trailing_field_ty>::pointer_to_metadata(ptr) + } ), ) } else { @@ -232,6 +239,12 @@ fn derive_known_layout_inner(ast: &DeriveInput) -> proc_macro2::TokenStream { ) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull { bytes.cast::() } + + #[inline(always)] + fn pointer_to_metadata( + _ptr: ::zerocopy::macro_util::core_reexport::ptr::NonNull, + ) -> () { + } ), ) };