Skip to content

Commit

Permalink
[pointer] Support as_bytes on unsized types
Browse files Browse the repository at this point in the history
  • Loading branch information
joshlf committed May 3, 2024
1 parent 91e0898 commit 014e421
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 38 deletions.
70 changes: 70 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,36 @@ pub unsafe trait KnownLayout {
/// elements in its trailing slice.
#[doc(hidden)]
fn raw_from_ptr_len(bytes: NonNull<u8>, meta: Self::PointerMetadata) -> NonNull<Self>;

/// 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>) -> 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<Self>) -> Option<usize> {
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.
Expand All @@ -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<usize>;
}

Expand Down Expand Up @@ -581,6 +616,41 @@ unsafe impl<T> KnownLayout for [T] {
#[allow(unstable_name_collisions)]
NonNull::slice_from_raw_parts(data.cast::<T>(), 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]
Expand Down
12 changes: 12 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,10 @@ macro_rules! impl_known_layout {
fn raw_from_ptr_len(bytes: NonNull<u8>, _meta: ()) -> NonNull<Self> {
bytes.cast::<Self>()
}

#[inline(always)]
fn pointer_to_metadata(_ptr: NonNull<Self>) -> () {
}
}
};
};
Expand Down Expand Up @@ -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>) -> 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)
}
}
};
};
Expand Down
2 changes: 1 addition & 1 deletion src/pointer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ where
/// Checks if the referent is zeroed.
pub(crate) fn is_zeroed<T, I>(ptr: Ptr<'_, T, I>) -> bool
where
T: crate::Immutable,
T: crate::Immutable + crate::KnownLayout,
I: invariant::Invariants<Validity = invariant::Initialized>,
I::Aliasing: invariant::AtLeast<invariant::Shared>,
{
Expand Down
68 changes: 33 additions & 35 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -985,24 +985,35 @@ mod _casts {

impl<'a, T, I> Ptr<'a, T, I>
where
T: 'a,
T: 'a + KnownLayout + ?Sized,
I: Invariants<Validity = Initialized>,
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::<u8>(), core::mem::size_of::<T>())
core::ptr::slice_from_raw_parts_mut(p.cast::<u8>(), bytes)
})
};

Expand Down Expand Up @@ -1314,48 +1325,35 @@ mod _project {
}
}

impl<'a, T, I> Ptr<'a, T, I>
where
T: 'a + KnownLayout<PointerMetadata = usize> + ?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<T>` from `Ptr<[T]>`.
Expand Down
17 changes: 15 additions & 2 deletions zerocopy-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>,
meta: <#trailing_field_ty as ::zerocopy::KnownLayout>::PointerMetadata,
meta: Self::PointerMetadata,
) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull<Self> {
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>) -> 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 {
Expand Down Expand Up @@ -232,6 +239,12 @@ fn derive_known_layout_inner(ast: &DeriveInput) -> proc_macro2::TokenStream {
) -> ::zerocopy::macro_util::core_reexport::ptr::NonNull<Self> {
bytes.cast::<Self>()
}

#[inline(always)]
fn pointer_to_metadata(
_ptr: ::zerocopy::macro_util::core_reexport::ptr::NonNull<Self>,
) -> () {
}
),
)
};
Expand Down

0 comments on commit 014e421

Please sign in to comment.