From eb8f34813d80e007f9f0841194042b1960f94023 Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Thu, 22 Jan 2026 20:23:55 +0000 Subject: [PATCH] Clarify semantics of size-preserving transmutes Makes progress on #2701, #1940, #1852 gherrit-pr-id: G64ec124d566c828ea61e6edf831a10338aa4c879 --- src/pointer/transmute.rs | 90 +++++++++++++++++++--------------------- src/util/macros.rs | 22 ++-------- 2 files changed, 46 insertions(+), 66 deletions(-) diff --git a/src/pointer/transmute.rs b/src/pointer/transmute.rs index 5d724fd844..402c20550b 100644 --- a/src/pointer/transmute.rs +++ b/src/pointer/transmute.rs @@ -35,7 +35,7 @@ use crate::{ /// /// Given `src: Ptr<'a, Src, (A, _, SV)>`, if the referent of `src` is /// `DV`-valid for `Dst`, then it is sound to transmute `src` into `dst: Ptr<'a, -/// Dst, (A, Unaligned, DV)>` by preserving pointer address and metadata. +/// Dst, (A, Unaligned, DV)>` using `>::CastFrom::project`. /// /// ## Pre-conditions /// @@ -45,11 +45,16 @@ use crate::{ /// - Forwards transmutation: Either of the following hold: /// - So long as `dst` is active, no mutation of `dst`'s referent is allowed /// except via `dst` itself -/// - The set of `DV`-valid `Dst`s is a superset of the set of `SV`-valid -/// `Src`s +/// - The set of `DV`-valid referents of `dst` is a superset of the set of +/// `SV`-valid referents of `src` (NOTE: this condition effectively bans +/// shrinking or overwriting transmutes, which cannot satisfy this +/// condition) /// - Reverse transmutation: Either of the following hold: /// - `dst` does not permit mutation of its referent -/// - The set of `DV`-valid `Dst`s is a subset of the set of `SV`-valid `Src`s +/// - The set of `DV`-valid referents of `dst` is a subset of the set of +/// `SV`-valid referents of `src` (NOTE: this condition effectively bans +/// shrinking or overwriting transmutes, which cannot satisfy this +/// condition) /// - No safe code, given access to `src` and `dst`, can cause undefined /// behavior: Any of the following hold: /// - `A` is `Exclusive` @@ -64,30 +69,17 @@ use crate::{ /// - `src`'s referent is `DV`-valid for `Dst` /// - `Dst: SizeEq` /// -/// We are trying to prove that it is sound to perform a pointer address- and -/// metadata-preserving transmute from `src` to a `dst: Ptr<'a, Dst, (A, +/// We are trying to prove that it is sound to cast using `>::CastFrom::project` from `src` to a `dst: Ptr<'a, Dst, (A, /// Unaligned, DV)>`. We need to prove that such a transmute does not violate /// any of `src`'s invariants, and that it satisfies all invariants of the /// destination `Ptr` type. /// -/// First, all of `src`'s `PtrInner` invariants are upheld. `src`'s address and -/// metadata are unchanged, so: -/// - If its referent is not zero sized, then it still has valid provenance for -/// its referent, which is still entirely contained in some Rust allocation, -/// `A` -/// - If its referent is not zero sized, `A` is guaranteed to live for at least -/// `'a` +/// First, by `SizeEq::CastFrom: Cast`, `src`'s address is unchanged, so it +/// still satisfies its alignment. Since `dst`'s alignment is `Unaligned`, it +/// trivially satisfies its alignment. /// -/// Since `Dst: SizeEq`, and since `dst` has the same address and metadata -/// as `src`, `dst` addresses the same byte range as `src`. `dst` also has the -/// same lifetime as `src`. Therefore, all of the `PtrInner` invariants -/// mentioned above also hold for `dst`. -/// -/// Second, since `src`'s address is unchanged, it still satisfies its -/// alignment. Since `dst`'s alignment is `Unaligned`, it trivially satisfies -/// its alignment. -/// -/// Third, aliasing is either `Exclusive` or `Shared`: +/// Second, aliasing is either `Exclusive` or `Shared`: /// - If it is `Exclusive`, then both `src` and `dst` satisfy `Exclusive` /// aliasing trivially: since `src` and `dst` have the same lifetime, `src` is /// inaccessible so long as `dst` is alive, and no other live `Ptr`s or @@ -98,22 +90,22 @@ use crate::{ /// - It is explicitly sound for safe code to operate on a `&Src` and a `&Dst` /// pointing to the same byte range at the same time. /// -/// Fourth, `src`'s validity is satisfied. By invariant, `src`'s referent began +/// Third, `src`'s validity is satisfied. By invariant, `src`'s referent began /// as an `SV`-valid `Src`. It is guaranteed to remain so, as either of the /// following hold: /// - `dst` does not permit mutation of its referent. -/// - The set of `DV`-valid `Dst`s is a superset of the set of `SV`-valid -/// `Src`s. Thus, any value written via `dst` is guaranteed to be `SV`-valid -/// for `Src`. +/// - The set of `DV`-valid referents of `dst` is a subset of the set of +/// `SV`-valid referents of `src`. Thus, any value written via `dst` is +/// guaranteed to be an `SV`-valid referent of `src`. /// -/// Fifth, `dst`'s validity is satisfied. It is a given of this proof that the +/// Fourth, `dst`'s validity is satisfied. It is a given of this proof that the /// referent is `DV`-valid for `Dst`. It is guaranteed to remain so, as either /// of the following hold: /// - So long as `dst` is active, no mutation of the referent is allowed except /// via `dst` itself. -/// - The set of `DV`-valid `Dst`s is a superset of the set of `SV`-valid -/// `Src`s. Thus, any value written via `src` is guaranteed to be a `DV`-valid -/// `Dst`. +/// - The set of `DV`-valid referents of `dst` is a superset of the set of +/// `SV`-valid referents of `src`. Thus, any value written via `src` is +/// guaranteed to be a `DV`-valid referent of `dst`. pub unsafe trait TryTransmuteFromPtr: SizeEq { @@ -131,12 +123,14 @@ pub enum BecauseMutationCompatible {} // exists, no mutation is permitted except via that `Ptr` // - Aliasing is `Shared`, `Src: Immutable`, and `Dst: Immutable`, in which // case no mutation is possible via either `Ptr` -// - `Dst: TransmuteFrom`. Since `Dst: SizeEq`, this bound -// guarantees that the set of `DV`-valid `Dst`s is a supserset of the set of -// `SV`-valid `Src`s. -// - Reverse transmutation: `Src: TransmuteFrom`. Since `Dst: -// SizeEq`, this guarantees that the set of `DV`-valid `Dst`s is a subset -// of the set of `SV`-valid `Src`s. +// - Since the underlying cast is performed using `> +// ::CastFrom::project`, `dst` addresses the same bytes as `src`. By `Dst: +// TransmuteFrom`, the set of `DV`-valid referents of `dst` is a +// supserset of the set of `SV`-valid referents of `src`. +// - Reverse transmutation: Since the underlying cast is performed using `>::CastFrom::project`, `dst` addresses the same bytes as +// `src`. By `Src: TransmuteFrom`, the set of `DV`-valid +// referents of `dst` is a subset of the set of `SV`-valid referents of `src`. // - No safe code, given access to `src` and `dst`, can cause undefined // behavior: By `Dst: MutationCompatible`, at least one of // the following holds: @@ -287,18 +281,20 @@ where /// DV>` conveys no safety guarantee. pub unsafe trait TransmuteFrom {} +/// Carries the ability to perform a size-preserving cast or conversion from a +/// raw pointer to `Src` to a raw pointer to `Self`. +/// +/// The cast/conversion is carried by the associated [`CastFrom`] type, and +/// may be a no-op cast (without updating pointer metadata) or a conversion +/// which updates pointer metadata. +/// /// # Safety /// -/// `T` and `Self` must have the same vtable kind (`Sized`, slice DST, `dyn`, -/// etc) and have the same size. In particular: -/// - If `T: Sized` and `Self: Sized`, then their sizes must be equal -/// - If `T: ?Sized` and `Self: ?Sized`, then `Self::CastFrom` must be a -/// size-preserving cast. *Note that it is **not** guaranteed that an `as` -/// cast preserves referent size: it may be the case that `Self::CastFrom` -/// modifies the pointer's metadata in order to preserve referent size, which -/// an `as` cast does not do.* -pub unsafe trait SizeEq { - type CastFrom: cast::Cast; +/// The associated [`CastFrom`] must preserve referent size. +/// +/// [`CastFrom`]: SizeEq::CastFrom +pub unsafe trait SizeEq { + type CastFrom: cast::Cast; } // SAFETY: `T` trivially has the same size and vtable kind as `T`, and since diff --git a/src/util/macros.rs b/src/util/macros.rs index 4f07852237..bc392e98c7 100644 --- a/src/util/macros.rs +++ b/src/util/macros.rs @@ -850,13 +850,9 @@ macro_rules! unsafe_with_size_eq { define_cast!(unsafe { SrcCast = $src => T }); define_cast!(unsafe { DstCast = U => $dst }); - // SAFETY: See inline for the soundness of this impl when - // `CastFrom::project` is actually instantiated (otherwise, PMEs may not - // be triggered). - // - // We manually instantiate `CastFrom::project` below to ensure that this - // PME can be triggered, and the caller promises not to use `$src` and - // `$dst` with any wrapped types other than `$t` and `$u` respectively. + // SAFETY: `crate::layout::CastFrom` preserves the set of referent + // bytes, as does `SrcCast`, and so this `TransitiveProject` preserves + // the set of referent bytes. unsafe impl SizeEq<$src> for $dst where T: KnownLayout, @@ -869,18 +865,6 @@ macro_rules! unsafe_with_size_eq { >, DstCast>; } - // See safety comment on the preceding `unsafe impl` block for an - // explanation of why we need this block. - if 1 == 0 { - use crate::pointer::cast::Project as _; - - let ptr = <$t as KnownLayout>::raw_dangling(); - // SAFETY: This call is never executed. - #[allow(unused_unsafe, clippy::missing_transmute_annotations)] - let ptr = unsafe { core::mem::transmute(ptr) }; - let _ = <$dst<$u> as SizeEq<$src<$t>>>::CastFrom::project(ptr); - } - impl_for_transmute_from!(T: ?Sized + TryFromBytes => TryFromBytes for $src[]); impl_for_transmute_from!(T: ?Sized + FromBytes => FromBytes for $src[]); impl_for_transmute_from!(T: ?Sized + FromZeros => FromZeros for $src[]);