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] SizeEq supports raw pointer transmutes #2427

Merged
merged 1 commit into from
Mar 8, 2025
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
82 changes: 43 additions & 39 deletions src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
// This file may not be copied, modified, or distributed except according to
// those terms.

use core::mem::MaybeUninit as CoreMaybeUninit;
use core::{cell::UnsafeCell, mem::MaybeUninit as CoreMaybeUninit, ptr::NonNull};

use super::*;

Expand Down Expand Up @@ -223,16 +223,32 @@ safety_comment! {
//
// String slices are a UTF-8 representation of characters that have the same
// layout as slices of type `[u8]`.
unsafe impl pointer::SizeEq<str> for [u8] {}
unsafe impl pointer::SizeEq<str> for [u8] {
fn cast_from_raw(s: NonNull<str>) -> NonNull<[u8]> {
cast!(s)
}
}
// SAFETY: See previous safety comment.
unsafe impl pointer::SizeEq<[u8]> for str {}
unsafe impl pointer::SizeEq<[u8]> for str {
fn cast_from_raw(bytes: NonNull<[u8]>) -> NonNull<str> {
cast!(bytes)
}
}

macro_rules! unsafe_impl_try_from_bytes_for_nonzero {
($($nonzero:ident[$prim:ty]),*) => {
$(
unsafe_impl!(=> TryFromBytes for $nonzero; |n| {
unsafe impl pointer::SizeEq<$nonzero> for Unalign<$prim> {}
unsafe impl pointer::SizeEq<Unalign<$prim>> for $nonzero {}
unsafe impl pointer::SizeEq<$nonzero> for Unalign<$prim> {
fn cast_from_raw(n: NonNull<$nonzero>) -> NonNull<Unalign<$prim>> {
cast!(n)
}
}
unsafe impl pointer::SizeEq<Unalign<$prim>> for $nonzero {
fn cast_from_raw(p: NonNull<Unalign<$prim>>) -> NonNull<$nonzero> {
cast!(p)
}
}

let n = n.transmute::<Unalign<$prim>, invariant::Valid, _>();
$nonzero::new(n.read_unaligned().into_inner()).is_some()
Expand Down Expand Up @@ -470,6 +486,7 @@ mod atomics {
macro_rules! unsafe_impl_transmute_from_for_atomic {
($($($tyvar:ident)? => $atomic:ty [$prim:ty]),*) => {
const _: () = {
use core::{cell::UnsafeCell, ptr::NonNull};
use crate::pointer::{TransmuteFrom, SizeEq, invariant::Valid};

$(
Expand All @@ -485,10 +502,18 @@ mod atomics {

// SAFETY: THe caller promised that `$atomic` and `$prim`
// have the same size.
unsafe impl<$($tyvar)?> SizeEq<$atomic> for $prim {}
unsafe impl<$($tyvar)?> SizeEq<$atomic> for $prim {
fn cast_from_raw(a: NonNull<$atomic>) -> NonNull<$prim> {
cast!(a)
}
}
// SAFETY: THe caller promised that `$atomic` and `$prim`
// have the same size.
unsafe impl<$($tyvar)?> SizeEq<$prim> for $atomic {}
unsafe impl<$($tyvar)?> SizeEq<$prim> for $atomic {
fn cast_from_raw(p: NonNull<$prim>) -> NonNull<$atomic> {
cast!(p)
}
}
// SAFETY: The caller promised that `$atomic` and `$prim`
// have the same size. `UnsafeCell<T>` has the same size as
// `T` [1].
Expand All @@ -498,9 +523,17 @@ mod atomics {
// `UnsafeCell<T>` has the same in-memory representation as
// its inner type `T`. A consequence of this guarantee is that
// it is possible to convert between `T` and `UnsafeCell<T>`.
unsafe impl<$($tyvar)?> SizeEq<$atomic> for core::cell::UnsafeCell<$prim> {}
unsafe impl<$($tyvar)?> SizeEq<$atomic> for UnsafeCell<$prim> {
fn cast_from_raw(a: NonNull<$atomic>) -> NonNull<UnsafeCell<$prim>> {
cast!(a)
}
}
// SAFETY: See previous safety comment.
unsafe impl<$($tyvar)?> SizeEq<core::cell::UnsafeCell<$prim>> for $atomic {}
unsafe impl<$($tyvar)?> SizeEq<UnsafeCell<$prim>> for $atomic {
fn cast_from_raw(p: NonNull<UnsafeCell<$prim>>) -> NonNull<$atomic> {
cast!(p)
}
}

// SAFETY: The caller promised that `$atomic` and `$prim`
// have the same bit validity. `UnsafeCell<T>` has the same
Expand Down Expand Up @@ -812,36 +845,7 @@ safety_comment! {
unsafe_impl!(T: ?Sized + Immutable => Immutable for ManuallyDrop<T>);
}

// SAFETY: See inline safety comment justifying that the implementation of
// `is_bit_valid`is sound.
unsafe impl<T: ?Sized + TryFromBytes> TryFromBytes for ManuallyDrop<T> {
#[allow(clippy::missing_inline_in_public_items)]
fn only_derive_is_allowed_to_implement_this_trait() {}

#[inline(always)]
fn is_bit_valid<A: crate::pointer::invariant::Reference>(
candidate: Maybe<'_, Self, A>,
) -> bool {
// SAFETY: `ManuallyDrop<T>` and `T` have the same size [1], so this
// cast preserves size. It also preserves provenance.
//
// [1] Per https://doc.rust-lang.org/1.85.0/std/mem/struct.ManuallyDrop.html:
//
// `ManuallyDrop<T>` is guaranteed to have the same layout and bit
// validity as `T`
let c: Maybe<'_, T, A> = unsafe { candidate.cast_unsized(cast!()) };

// SAFETY: `ManuallyDrop<T>` and `T` have the same bit validity [1], so
// this is a sound implementation of `ManuallyDrop::is_bit_valid`.
//
// [1] Per https://doc.rust-lang.org/1.85.0/std/mem/struct.ManuallyDrop.html:
//
// `ManuallyDrop<T>` is guaranteed to have the same layout and bit
// validity as `T`
<T as TryFromBytes>::is_bit_valid(c)
}
}

impl_for_transmute_from!(T: ?Sized + TryFromBytes => TryFromBytes for ManuallyDrop<T>[<T>]);
impl_for_transmute_from!(T: ?Sized + FromZeros => FromZeros for ManuallyDrop<T>[<T>]);
impl_for_transmute_from!(T: ?Sized + FromBytes => FromBytes for ManuallyDrop<T>[<T>]);
impl_for_transmute_from!(T: ?Sized + IntoBytes => IntoBytes for ManuallyDrop<T>[<T>]);
Expand Down
11 changes: 0 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -805,17 +805,6 @@ pub unsafe trait KnownLayout {
// resulting size would not fit in a `usize`.
meta.size_for_metadata(Self::LAYOUT)
}

#[doc(hidden)]
#[must_use]
#[inline(always)]
fn cast_from_raw<P: KnownLayout<PointerMetadata = Self::PointerMetadata> + ?Sized>(
ptr: NonNull<P>,
) -> NonNull<Self> {
let data = ptr.cast::<u8>();
let meta = P::pointer_to_metadata(ptr.as_ptr());
Self::raw_from_ptr_len(data, meta)
}
}

/// The metadata associated with a [`KnownLayout`] type.
Expand Down
28 changes: 1 addition & 27 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,12 +393,8 @@ mod _conversions {
{
pub(crate) fn transmute<U, V, R>(self) -> Ptr<'a, U, (I::Aliasing, Unaligned, V)>
where
T: KnownLayout,
V: Validity,
U: TransmuteFromPtr<T, I::Aliasing, I::Validity, V, R>
+ KnownLayout<PointerMetadata = T::PointerMetadata>
+ SizeEq<T>
+ ?Sized,
U: TransmuteFromPtr<T, I::Aliasing, I::Validity, V, R> + SizeEq<T> + ?Sized,
{
// SAFETY:
// - This cast preserves address and provenance
Expand All @@ -416,28 +412,6 @@ mod _conversions {
unsafe { self.transmute_unchecked(|t: NonNull<T>| U::cast_from_raw(t)) }
}

pub(crate) fn transmute_sized<U, V, R>(self) -> Ptr<'a, U, (I::Aliasing, Unaligned, V)>
where
T: Sized,
V: Validity,
U: TransmuteFromPtr<T, I::Aliasing, I::Validity, V, R> + SizeEq<T>,
{
// SAFETY:
// - This cast preserves address and provenance
// - `U: SizeEq<T>` guarantees that this cast preserves the number
// of bytes in the referent
// - If aliasing is `Shared`, then by `U: TransmuteFromPtr<T>`, at
// least one of the following holds:
// - `T: Immutable` and `U: Immutable`, in which case it is
// trivially sound for shared code to operate on a `&T` and `&U`
// at the same time, as neither can perform interior mutation
// - It is directly guaranteed that it is sound for shared code to
// operate on these references simultaneously
// - By `U: TransmuteFromPtr<T, I::Aliasing, I::Validity, V>`, it is
// sound to perform this transmute.
unsafe { self.transmute_unchecked(cast!()) }
}

#[doc(hidden)]
#[inline(always)]
#[must_use]
Expand Down
40 changes: 22 additions & 18 deletions src/pointer/transmute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use core::{
cell::UnsafeCell,
mem::{ManuallyDrop, MaybeUninit},
num::Wrapping,
ptr::NonNull,
};

use crate::{pointer::invariant::*, FromBytes, Immutable, IntoBytes, Unalign};
Expand Down Expand Up @@ -227,20 +228,8 @@ macro_rules! unsafe_impl_invariants_eq {
};
}

// SAFETY: See bounds.
unsafe impl<T> SizeEq<MaybeUninit<T>> for Wrapping<T>
where
T: SizeEq<Wrapping<T>>,
T: SizeEq<MaybeUninit<T>>,
{
}
// SAFETY: See bounds.
unsafe impl<T> SizeEq<Wrapping<T>> for MaybeUninit<T>
where
T: SizeEq<Wrapping<T>>,
T: SizeEq<MaybeUninit<T>>,
{
}
impl_transitive_transmute_from!(T => MaybeUninit<T> => T => Wrapping<T>);
impl_transitive_transmute_from!(T => Wrapping<T> => T => MaybeUninit<T>);

// SAFETY: `ManuallyDrop<T>` has the same size and bit validity as `T` [1], and
// implements `Deref<Target = T>` [2]. Thus, it is already possible for safe
Expand Down Expand Up @@ -298,12 +287,18 @@ pub unsafe trait TransmuteFrom<Src: ?Sized, SV, DV>: SizeEq<Src> {}
/// - If `T: ?Sized` and `Self: ?Sized`, then it must be the case that, given
/// any `t: *mut T`, `t as *mut Self` produces a pointer which addresses the
/// same number of bytes as `t`.
pub unsafe trait SizeEq<T: ?Sized> {}
pub unsafe trait SizeEq<T: ?Sized> {
fn cast_from_raw(t: NonNull<T>) -> NonNull<Self>;
}

// SAFETY: `T` trivially has the same size and vtable kind as `T`, and since
// pointer `*mut T -> *mut T` pointer casts are no-ops, this cast trivially
// preserves referent size (when `T: ?Sized`).
unsafe impl<T: ?Sized> SizeEq<T> for T {}
unsafe impl<T: ?Sized> SizeEq<T> for T {
fn cast_from_raw(t: NonNull<T>) -> NonNull<T> {
t
}
}

// SAFETY: Since `Src: IntoBytes`, the set of valid `Src`'s is the set of
// initialized bit patterns, which is exactly the set allowed in the referent of
Expand Down Expand Up @@ -436,6 +431,15 @@ unsafe impl<T> TransmuteFrom<T, Uninit, Valid> for MaybeUninit<T> {}
//
// `MaybeUninit<T>` is guaranteed to have the same size, alignment, and ABI as
// `T`
unsafe impl<T> SizeEq<T> for MaybeUninit<T> {}
unsafe impl<T> SizeEq<T> for MaybeUninit<T> {
fn cast_from_raw(t: NonNull<T>) -> NonNull<MaybeUninit<T>> {
cast!(t)
}
}

// SAFETY: See previous safety comment.
unsafe impl<T> SizeEq<MaybeUninit<T>> for T {}
unsafe impl<T> SizeEq<MaybeUninit<T>> for T {
fn cast_from_raw(t: NonNull<MaybeUninit<T>>) -> NonNull<T> {
cast!(t)
}
}
74 changes: 65 additions & 9 deletions src/util/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ macro_rules! impl_for_transmute_from {
// SAFETY: This macro ensures that `$repr` and `Self` have the same
// size and bit validity. Thus, a bit-valid instance of `$repr` is
// also a bit-valid instance of `Self`.
<$repr as TryFromBytes>::is_bit_valid(candidate.transmute_sized())
<$repr as TryFromBytes>::is_bit_valid(candidate.transmute())
}
};
(
Expand Down Expand Up @@ -694,13 +694,19 @@ macro_rules! static_assert_dst_is_not_zst {

macro_rules! cast {
() => {
// SAFETY: `NonNull::as_ptr` returns a non-null pointer, so the argument
// to `NonNull::new_unchecked` is also non-null.
|p| {
#[allow(clippy::as_conversions)]
return core::ptr::NonNull::new_unchecked(core::ptr::NonNull::as_ptr(p) as *mut _);
// SAFETY: `NonNull::as_ptr` returns a non-null pointer, so the
// argument to `NonNull::new_unchecked` is also non-null.
#[allow(clippy::as_conversions, unused_unsafe)]
#[allow(clippy::undocumented_unsafe_blocks)] // Clippy false positive
return unsafe {
core::ptr::NonNull::new_unchecked(core::ptr::NonNull::as_ptr(p) as *mut _)
};
}
};
($p:ident) => {
cast!()($p)
};
}

/// Implements `TransmuteFrom` and `SizeEq` for `T` and `$wrapper<T>`.
Expand All @@ -712,17 +718,27 @@ macro_rules! cast {
macro_rules! unsafe_impl_for_transparent_wrapper {
(T $(: ?$optbound:ident)? => $wrapper:ident<T>) => {
const _: () = {
use core::ptr::NonNull;
use crate::pointer::{TransmuteFrom, SizeEq, invariant::Valid};

// SAFETY: The caller promises that `T` and `$wrapper<T>` have the
// same bit validity.
unsafe impl<T $(: ?$optbound)?> TransmuteFrom<T, Valid, Valid> for $wrapper<T> {}
// SAFETY: See previous safety comment.
unsafe impl<T $(: ?$optbound)?> TransmuteFrom<$wrapper<T>, Valid, Valid> for T {}
// SAFETY: The caller promises that `T` and `$wrapper<T>` satisfy
// `SizeEq`.
unsafe impl<T $(: ?$optbound)?> SizeEq<T> for $wrapper<T> {}
unsafe impl<T $(: ?$optbound)?> SizeEq<T> for $wrapper<T> {
fn cast_from_raw(t: NonNull<T>) -> NonNull<$wrapper<T>> {
cast!(t)
}
}
// SAFETY: See previous safety comment.
unsafe impl<T $(: ?$optbound)?> SizeEq<$wrapper<T>> for T {}
unsafe impl<T $(: ?$optbound)?> SizeEq<$wrapper<T>> for T {
fn cast_from_raw(t: NonNull<$wrapper<T>>) -> NonNull<T> {
cast!(t)
}
}
};

// So that this macro must be invoked inside `safety_comment!` or else
Expand All @@ -732,17 +748,57 @@ macro_rules! unsafe_impl_for_transparent_wrapper {
};
}

macro_rules! impl_transitive_transmute_from {
($($tyvar:ident $(: ?$optbound:ident)?)? => $t:ty => $u:ty => $v:ty) => {
const _: () = {
use core::ptr::NonNull;
use crate::pointer::{TransmuteFrom, SizeEq, invariant::Valid};

// SAFETY: Since `$u: SizeEq<$t>` and `$v: SizeEq<U>`, this impl is
// transitively sound.
unsafe impl<$($tyvar $(: ?$optbound)?)?> SizeEq<$t> for $v
where
$u: SizeEq<$t>,
$v: SizeEq<$u>,
{
fn cast_from_raw(t: NonNull<$t>) -> NonNull<$v> {
cast!(t)
}
}

// SAFETY: Since `$u: TransmuteFrom<$t, Valid, Valid>`, it is sound
// to transmute a bit-valid `$t` to a bit-valid `$u`. Since `$v:
// TransmuteFrom<$u, Valid, Valid>`, it is sound to transmute that
// bit-valid `$u` to a bit-valid `$v`.
unsafe impl<$($tyvar $(: ?$optbound)?)?> TransmuteFrom<$t, Valid, Valid> for $v
where
$u: TransmuteFrom<$t, Valid, Valid>,
$v: TransmuteFrom<$u, Valid, Valid>,
{}
};
};
}

macro_rules! impl_size_eq {
($t:ty, $u:ty) => {
const _: () = {
use crate::pointer::SizeEq;
use core::ptr::NonNull;

static_assert!(=> mem::size_of::<$t>() == mem::size_of::<$u>());

// SAFETY: We've asserted that their sizes are equal.
unsafe impl SizeEq<$t> for $u {}
unsafe impl SizeEq<$t> for $u {
fn cast_from_raw(t: NonNull<$t>) -> NonNull<$u> {
cast!(t)
}
}
// SAFETY: We've asserted that their sizes are equal.
unsafe impl SizeEq<$u> for $t {}
unsafe impl SizeEq<$u> for $t {
fn cast_from_raw(u: NonNull<$u>) -> NonNull<$t> {
cast!(u)
}
}
};
};
}
Loading