From 3abac09f5e3fae84f366f3faebb181b94e8a5dfc Mon Sep 17 00:00:00 2001 From: Josh Liebow-Feeser Date: Thu, 15 Oct 2026 14:43:59 -0400 Subject: [PATCH] Expose wrapper validity impls for independent verification Move the transparent-wrapper and transitive TransmuteFrom impls out of helper macros so each impl can carry its own checked invariant marker. Keep their original generic bounds. The macros continue to generate casts and SizeEq impls, and their names and safety requirements now describe that narrower responsibility. State the equal intermediate sizes in the transitive safety arguments: TransmuteFrom promises nothing when its own endpoint sizes differ. This refactor changes no executable operation and adds no verification claims. gherrit-pr-id: Gd7mcbq7u5yus3mis4yyvjdxx6qo4trg6 Agent-authored-by: AI agent acting on joshlf's behalf --- zerocopy/src/pointer/transmute.rs | 121 ++++++++++++++++++++++++++---- zerocopy/src/util/macros.rs | 32 ++------ 2 files changed, 115 insertions(+), 38 deletions(-) diff --git a/zerocopy/src/pointer/transmute.rs b/zerocopy/src/pointer/transmute.rs index 7aabb149de..114e5bf0f3 100644 --- a/zerocopy/src/pointer/transmute.rs +++ b/zerocopy/src/pointer/transmute.rs @@ -241,8 +241,35 @@ macro_rules! unsafe_impl_shared_compatible { }}; } -impl_transitive_transmute_from!(T => MaybeUninit => T => Wrapping); -impl_transitive_transmute_from!(T => Wrapping => T => MaybeUninit); +impl_transitive_size_eq!(T => MaybeUninit => T => Wrapping); + +// FIXME: The pinned extractor makes MaybeUninit opaque without retaining its +// storage. These two conditional impls need that model before they can carry +// invariant_proof markers. +// +// SAFETY: The standard library guarantees that `MaybeUninit` and +// `Wrapping` have the same size as `T`. Thus both conditional bounds apply: +// `T: TransmuteFrom, Safe, Safe>` preserves bit validity, and +// `Wrapping: TransmuteFrom` preserves it again. +unsafe impl TransmuteFrom, Safe, Safe> for Wrapping +where + T: TransmuteFrom, Safe, Safe>, + Wrapping: TransmuteFrom, +{ +} + +impl_transitive_size_eq!(T => Wrapping => T => MaybeUninit); + +// SAFETY: All three types have the same size, as justified above. Thus both +// conditional bounds apply: `T: TransmuteFrom, Safe, Safe>` preserves +// bit validity, and `MaybeUninit: TransmuteFrom` preserves it +// again. +unsafe impl TransmuteFrom, Safe, Safe> for MaybeUninit +where + T: TransmuteFrom, Safe, Safe>, + MaybeUninit: TransmuteFrom, +{ +} // SAFETY: `ManuallyDrop` has the same size and bit validity as `T` [1], and // implements `Deref` [2]. Thus, it is already possible for safe @@ -336,9 +363,9 @@ impl SizeEq for T { type CastFrom = cast::IdCast; } -// 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 -// any `Initialized` `Ptr`. +// SAFETY: `Src: IntoBytes` guarantees that every valid representation of +// `Src` is entirely initialized. If the referent widths agree, those same +// bytes satisfy `Initialized` for `Dst`. unsafe impl TransmuteFrom for Dst where Src: IntoBytes + ?Sized, @@ -347,8 +374,8 @@ where } // SAFETY: Since `Dst: FromBytes`, any initialized bit pattern may appear in the -// referent of a `Ptr`. This is exactly equal to the set of -// bit patterns which may appear in the referent of any `Initialized` `Ptr`. +// referent of a `Ptr`. If the referent widths agree, +// `Initialized` source bytes therefore satisfy `Safe` for `Dst`. unsafe impl TransmuteFrom for Dst where Src: ?Sized, @@ -393,14 +420,31 @@ where // // `ManuallyDrop` is guaranteed to have the same layout and bit validity as // `T` +unsafe impl TransmuteFrom for ManuallyDrop {} + +// SAFETY: By the preceding safety proof, `ManuallyDrop` and `T` have the +// same bit validity. +unsafe impl TransmuteFrom, Safe, Safe> for T {} + +// SAFETY: By the preceding safety proof, `ManuallyDrop` and `T` have the +// same size in both cast directions. #[allow(clippy::multiple_unsafe_ops_per_block)] -const _: () = unsafe { unsafe_impl_for_transparent_wrapper!(pub T: ?Sized => ManuallyDrop) }; +const _: () = + unsafe { unsafe_impl_transparent_wrapper_casts_and_size_eq!(pub T: ?Sized => ManuallyDrop) }; // SAFETY: // - `Unalign` promises to have the same size as `T`. // - `Unalign` promises to have the same validity as `T`. +unsafe impl TransmuteFrom for Unalign {} + +// SAFETY: By the preceding safety proof, `Unalign` and `T` have the +// same bit validity. +unsafe impl TransmuteFrom, Safe, Safe> for T {} + +// SAFETY: By the preceding safety proof, `Unalign` and `T` have the +// same size in both cast directions. #[allow(clippy::multiple_unsafe_ops_per_block)] -const _: () = unsafe { unsafe_impl_for_transparent_wrapper!(pub T => Unalign) }; +const _: () = unsafe { unsafe_impl_transparent_wrapper_casts_and_size_eq!(pub T => Unalign) }; // SAFETY: `Unalign` promises to have the same size and validity as `T`. // Given `u: &Unalign`, it is already possible to obtain `let t = // u.try_deref().unwrap()`. Because `Unalign` has the same size as `T`, the @@ -432,8 +476,16 @@ const _: () = unsafe { unsafe_impl_shared_compatible!(T => T, Unalign) }; // #[repr(transparent)] // pub struct Wrapping(pub T); // ``` +unsafe impl TransmuteFrom for Wrapping {} + +// SAFETY: By the preceding safety proof, `Wrapping` and `T` have the +// same bit validity. +unsafe impl TransmuteFrom, Safe, Safe> for T {} + +// SAFETY: By the preceding safety proof, `Wrapping` and `T` have the +// same size in both cast directions. #[allow(clippy::multiple_unsafe_ops_per_block)] -const _: () = unsafe { unsafe_impl_for_transparent_wrapper!(pub T => Wrapping) }; +const _: () = unsafe { unsafe_impl_transparent_wrapper_casts_and_size_eq!(pub T => Wrapping) }; // SAFETY: By the preceding safety proof, `Wrapping` and `T` have the same // layout and bit validity. Since a `Wrapping`'s `T` field is `pub`, given @@ -454,8 +506,17 @@ const _: () = unsafe { unsafe_impl_shared_compatible!(T => T, Wrapping) }; // `UnsafeCell` 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`. +unsafe impl TransmuteFrom for UnsafeCell {} + +// SAFETY: By the preceding safety proof, `UnsafeCell` and `T` have the +// same bit validity. +unsafe impl TransmuteFrom, Safe, Safe> for T {} + +// SAFETY: By the preceding safety proof, `UnsafeCell` and `T` have the +// same size in both cast directions. #[allow(clippy::multiple_unsafe_ops_per_block)] -const _: () = unsafe { unsafe_impl_for_transparent_wrapper!(pub T: ?Sized => UnsafeCell) }; +const _: () = + unsafe { unsafe_impl_transparent_wrapper_casts_and_size_eq!(pub T: ?Sized => UnsafeCell) }; // SAFETY: // - `Cell` has the same size as `T` [1]. @@ -476,11 +537,43 @@ const _: () = unsafe { unsafe_impl_for_transparent_wrapper!(pub T: ?Sized => Uns // `UnsafeCell` 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`. +unsafe impl TransmuteFrom for Cell {} + +// SAFETY: By the preceding safety proof, `Cell` and `T` have the +// same bit validity. +unsafe impl TransmuteFrom, Safe, Safe> for T {} + +// SAFETY: By the preceding safety proof, `Cell` and `T` have the +// same size in both cast directions. #[allow(clippy::multiple_unsafe_ops_per_block)] -const _: () = unsafe { unsafe_impl_for_transparent_wrapper!(pub T: ?Sized => Cell) }; +const _: () = + unsafe { unsafe_impl_transparent_wrapper_casts_and_size_eq!(pub T: ?Sized => Cell) }; + +impl_transitive_size_eq!(T: ?Sized => Cell => T => UnsafeCell); + +// SAFETY: `Cell` and `UnsafeCell` have the same size as `T`, as justified +// above. Thus both conditional bounds apply: `T: TransmuteFrom, Safe, +// Safe>` preserves bit validity, and `UnsafeCell: TransmuteFrom` preserves it again. +unsafe impl TransmuteFrom, Safe, Safe> for UnsafeCell +where + T: TransmuteFrom, Safe, Safe>, + UnsafeCell: TransmuteFrom, +{ +} + +impl_transitive_size_eq!(T: ?Sized => UnsafeCell => T => Cell); -impl_transitive_transmute_from!(T: ?Sized => Cell => T => UnsafeCell); -impl_transitive_transmute_from!(T: ?Sized => UnsafeCell => T => Cell); +// SAFETY: All three types have the same size, as justified above. Thus both +// conditional bounds apply: `T: TransmuteFrom, Safe, Safe>` +// preserves bit validity, and `Cell: TransmuteFrom` preserves +// it again. +unsafe impl TransmuteFrom, Safe, Safe> for Cell +where + T: TransmuteFrom, Safe, Safe>, + Cell: TransmuteFrom, +{ +} // SAFETY: `MaybeUninit` has no validity requirements. Currently this is not // explicitly guaranteed, but it's obvious from `MaybeUninit`'s documentation diff --git a/zerocopy/src/util/macros.rs b/zerocopy/src/util/macros.rs index a12e7b4001..f106537058 100644 --- a/zerocopy/src/util/macros.rs +++ b/zerocopy/src/util/macros.rs @@ -743,26 +743,20 @@ macro_rules! define_cast { }; } -/// Implements `TransmuteFrom` and `SizeEq` for `T` and `$wrapper`. +/// Implements casts and `SizeEq` for `T` and `$wrapper`. /// /// # Safety /// -/// `T` and `$wrapper` must have the same bit validity, and must have the -/// same size in the sense of `CastExact` (specifically, both a -/// `T`-to-`$wrapper` cast and a `$wrapper`-to-`T` cast must be -/// size-preserving). -macro_rules! unsafe_impl_for_transparent_wrapper { +/// `T` and `$wrapper` must have the same size in the sense of `CastExact` +/// (specifically, both a `T`-to-`$wrapper` cast and a `$wrapper`-to-`T` +/// cast must be size-preserving). +macro_rules! unsafe_impl_transparent_wrapper_casts_and_size_eq { ($vis:vis T $(: ?$optbound:ident)? => $wrapper:ident) => {{ crate::util::macros::__unsafe(); - use crate::pointer::{TransmuteFrom, cast::{CastExact, TransitiveProject}, SizeEq, invariant::Safe}; + use crate::pointer::{cast::{CastExact, TransitiveProject}, SizeEq}; use crate::wrappers::ReadOnly; - // SAFETY: The caller promises that `T` and `$wrapper` have the same - // bit validity. - unsafe impl TransmuteFrom for $wrapper {} - // SAFETY: See previous safety comment. - unsafe impl TransmuteFrom<$wrapper, Safe, Safe> for T {} // SAFETY: The caller promises that a `T` to `$wrapper` cast is // size-preserving. define_cast!(unsafe { $vis CastToWrapper = T => $wrapper }); @@ -815,10 +809,10 @@ macro_rules! unsafe_impl_for_transparent_wrapper { }}; } -macro_rules! impl_transitive_transmute_from { +macro_rules! impl_transitive_size_eq { ($($tyvar:ident $(: ?$optbound:ident)?)? => $t:ty => $u:ty => $v:ty) => { const _: () = { - use crate::pointer::{TransmuteFrom, SizeEq, invariant::Safe}; + use crate::pointer::SizeEq; impl<$($tyvar $(: ?$optbound)?)?> SizeEq<$t> for $v where @@ -831,16 +825,6 @@ macro_rules! impl_transitive_transmute_from { <$v as SizeEq<$u>>::CastFrom >; } - - // SAFETY: Since `$u: TransmuteFrom<$t, Safe, Safe>`, it is sound to - // transmute a bit-valid `$t` to a bit-valid `$u`. Since `$v: - // TransmuteFrom<$u, Safe, Safe>`, it is sound to transmute that - // bit-valid `$u` to a bit-valid `$v`. - unsafe impl<$($tyvar $(: ?$optbound)?)?> TransmuteFrom<$t, Safe, Safe> for $v - where - $u: TransmuteFrom<$t, Safe, Safe>, - $v: TransmuteFrom<$u, Safe, Safe>, - {} }; }; }