Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pointer] Support as_bytes on unsized types #1180

Merged
merged 1 commit into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading