From 88213f3ce434fe3644fc764583fe87319d478ef5 Mon Sep 17 00:00:00 2001 From: Joshua Liebow-Feeser Date: Wed, 16 Oct 2024 14:33:59 -0700 Subject: [PATCH] Generalize SizeEq to SizeFrom, support shrinking Generalize `SizeEq`, renaming it to `SizeFrom`, and supporting casts which shrink rather than preserve referent size. This requires fairly deep surgery in our transmute taxonomy. We can no longer rely on `SizeFrom::cast_from_raw` preserving referent size, which means that all pointer transmutes must now reason about two types of transmutes: - Size-shrinking transmutes (in which the source value is larger than the destination value) - Overwriting transmutes (in which the source value is larger than the destination value *and* the destination permits mutation). In an overwriting transmute, the resulting value is a concatenation of a valid `T` and the *suffix* of a valid `U`. (Note that size-preserving transmutes can be viewed as a special case of size-shrinking transmutes.) In order to support these semantics, we split `TransmuteFrom` into two traits: `TransmuteFrom` (which supports shriking transmutes) and `TransmuteOverwrite` (which supports overwriting transmutes). While we're here, we simplify the relationship between `TryTransmuteFromPtr`, `MutationCompatible`, and `Read`. In particular, `TryTransmuteFromPtr` now has a single blanket impl which is bounded by `MutationCompatible`, and `MutationCompatible` now directly supports three cases (and no longer delegates to `Read`): - Exclusive reference transmutes in which the destination type may be transmuted into the source type - Shared reference transmutes in which both the source and the destination type are `Immutable` - Shared reference transmutes which permit interior mutation, in which the source and destination types may both be transmuted into one another, and in which the source and destination types support compatible interior mutation Makes progress on #1817 Co-authored-by: Jack Wrenn gherrit-pr-id: I6c793a9620ad75bdc0d26ab7c7cd1a0c7bef1b8b --- src/impls.rs | 64 +++-- src/layout.rs | 6 +- src/lib.rs | 35 +-- src/pointer/invariant.rs | 19 -- src/pointer/mod.rs | 9 +- src/pointer/ptr.rs | 88 +++--- src/pointer/transmute.rs | 397 +++++++++++++++++++--------- src/ref.rs | 8 +- src/util/macro_util.rs | 30 +-- src/util/macros.rs | 79 +++--- src/wrappers.rs | 7 +- zerocopy-derive/src/enum.rs | 2 +- zerocopy-derive/src/output_tests.rs | 6 +- 13 files changed, 450 insertions(+), 300 deletions(-) diff --git a/src/impls.rs b/src/impls.rs index a040a79df7..faae852f1c 100644 --- a/src/impls.rs +++ b/src/impls.rs @@ -105,11 +105,13 @@ assert_unaligned!(bool); // pattern 0x01. const _: () = unsafe { unsafe_impl!(=> TryFromBytes for bool; |byte| { - let byte = byte.transmute::(); + let mut byte = byte; + let byte = byte.reborrow().into_shared().transmute::(); *byte.unaligned_as_ref() < 2 }) }; -impl_size_eq!(bool, u8); + +impl_size_from!(bool, u8); // SAFETY: // - `Immutable`: `char` self-evidently does not contain any `UnsafeCell`s. @@ -134,13 +136,14 @@ const _: () = unsafe { unsafe_impl!(char: Immutable, FromZeros, IntoBytes) }; // `char`. const _: () = unsafe { unsafe_impl!(=> TryFromBytes for char; |c| { - let c = c.transmute::, invariant::Valid, _>(); - let c = c.read_unaligned().into_inner(); + let mut c = c; + let c = c.reborrow().into_shared().transmute::, invariant::Valid, _>(); + let c = c.read_unaligned::().into_inner(); char::from_u32(c).is_some() }); }; -impl_size_eq!(char, Unalign); +impl_size_from!(char, Unalign); // SAFETY: Per the Reference [1], `str` has the same layout as `[u8]`. // - `Immutable`: `[u8]` does not contain any `UnsafeCell`s. @@ -167,22 +170,23 @@ const _: () = unsafe { unsafe_impl!(str: Immutable, FromZeros, IntoBytes, Unalig // Returns `Err` if the slice is not UTF-8. const _: () = unsafe { unsafe_impl!(=> TryFromBytes for str; |c| { - let c = c.transmute::<[u8], invariant::Valid, _>(); + let mut c = c; + let c = c.reborrow().into_shared().transmute::<[u8], invariant::Valid, _>(); let c = c.unaligned_as_ref(); core::str::from_utf8(c).is_ok() }) }; -impl_size_eq!(str, [u8]); +impl_size_from!(str, [u8]); macro_rules! unsafe_impl_try_from_bytes_for_nonzero { ($($nonzero:ident[$prim:ty]),*) => { $( unsafe_impl!(=> TryFromBytes for $nonzero; |n| { - impl_size_eq!($nonzero, Unalign<$prim>); - - let n = n.transmute::, invariant::Valid, _>(); - $nonzero::new(n.read_unaligned().into_inner()).is_some() + impl_size_from!($nonzero, Unalign<$prim>); + let mut n = n; + let n = n.reborrow().into_shared().transmute::, invariant::Valid, _>(); + $nonzero::new(n.read_unaligned::().into_inner()).is_some() }); )* } @@ -396,45 +400,51 @@ mod atomics { ($($($tyvar:ident)? => $atomic:ty [$prim:ty]),*) => {{ crate::util::macros::__unsafe(); - use core::cell::UnsafeCell; - use crate::pointer::{PtrInner, SizeEq, TransmuteFrom, invariant::Valid}; + use core::{cell::UnsafeCell}; + use crate::pointer::{TransmuteFrom, PtrInner, SizeFrom, invariant::Valid}; $( // SAFETY: The caller promised that `$atomic` and `$prim` have - // the same size and bit validity. + // the same size and bit validity. As a result of size equality, + // both impls of `SizeFrom::cast_from_raw` preserve referent + // size exactly. unsafe impl<$($tyvar)?> TransmuteFrom<$atomic, Valid, Valid> for $prim {} // SAFETY: The caller promised that `$atomic` and `$prim` have - // the same size and bit validity. + // the same size and bit validity. As a result of size equality, + // both impls of `SizeFrom::cast_from_raw` preserve referent + // size exactly. unsafe impl<$($tyvar)?> TransmuteFrom<$prim, Valid, Valid> for $atomic {} - // SAFETY: The caller promised that `$atomic` and `$prim` have - // the same size. - unsafe impl<$($tyvar)?> SizeEq<$atomic> for $prim { + // SAFETY: See inline safety comment. + unsafe impl<$($tyvar)?> SizeFrom<$atomic> for $prim { #[inline] fn cast_from_raw(a: PtrInner<'_, $atomic>) -> PtrInner<'_, $prim> { - // SAFETY: The caller promised that `$atomic` and - // `$prim` have the same size. Thus, this cast preserves + // SAFETY: The caller promised that `$atomic` and `$prim` + // have the same size. Thus, this cast preserves // address, referent size, and provenance. unsafe { cast!(a) } } } // SAFETY: See previous safety comment. - unsafe impl<$($tyvar)?> SizeEq<$prim> for $atomic { + unsafe impl<$($tyvar)?> SizeFrom<$prim> for $atomic { #[inline] fn cast_from_raw(p: PtrInner<'_, $prim>) -> PtrInner<'_, $atomic> { // SAFETY: See previous safety comment. unsafe { cast!(p) } } } - // SAFETY: The caller promised that `$atomic` and `$prim` have - // the same size. `UnsafeCell` has the same size as `T` [1]. + + // SAFETY: The caller promised that `$atomic` and `$prim` + // have the same size. `UnsafeCell` has the same size as + // `T` [1]. Thus, this cast preserves address, referent + // size, and provenance. // // [1] Per https://doc.rust-lang.org/1.85.0/std/cell/struct.UnsafeCell.html#memory-layout: // // `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<$($tyvar)?> SizeEq<$atomic> for UnsafeCell<$prim> { + unsafe impl<$($tyvar)?> SizeFrom<$atomic> for UnsafeCell<$prim> { #[inline] fn cast_from_raw(a: PtrInner<'_, $atomic>) -> PtrInner<'_, UnsafeCell<$prim>> { // SAFETY: See previous safety comment. @@ -442,7 +452,7 @@ mod atomics { } } // SAFETY: See previous safety comment. - unsafe impl<$($tyvar)?> SizeEq> for $atomic { + unsafe impl<$($tyvar)?> SizeFrom> for $atomic { #[inline] fn cast_from_raw(p: PtrInner<'_, UnsafeCell<$prim>>) -> PtrInner<'_, $atomic> { // SAFETY: See previous safety comment. @@ -452,7 +462,9 @@ mod atomics { // SAFETY: The caller promised that `$atomic` and `$prim` have // the same bit validity. `UnsafeCell` has the same bit - // validity as `T` [1]. + // validity as `T` [1]. `UnsafeCell` also has the same size + // as `T` [1], and so both impls of `SizeFrom::cast_from_raw` + // preserve referent size exactly. // // [1] Per https://doc.rust-lang.org/1.85.0/std/cell/struct.UnsafeCell.html#memory-layout: // diff --git a/src/layout.rs b/src/layout.rs index 54cb21ad58..55d5c962b7 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -612,15 +612,15 @@ pub(crate) use cast_from_raw::cast_from_raw; mod cast_from_raw { use crate::{pointer::PtrInner, *}; - /// Implements [`>::cast_from_raw`][cast_from_raw]. + /// Implements [`>::cast_from_raw`][cast_from_raw]. /// /// # PME /// /// Generates a post-monomorphization error if it is not possible to satisfy - /// the soundness conditions of [`SizeEq::cast_from_raw`][cast_from_raw] + /// the soundness conditions of [`SizeFrom::cast_from_raw`][cast_from_raw] /// for `Src` and `Dst`. /// - /// [cast_from_raw]: crate::pointer::SizeEq::cast_from_raw + /// [cast_from_raw]: crate::pointer::SizeFrom::cast_from_raw // // FIXME(#1817): Support Sized->Unsized and Unsized->Sized casts pub(crate) fn cast_from_raw(src: PtrInner<'_, Src>) -> PtrInner<'_, Dst> diff --git a/src/lib.rs b/src/lib.rs index 2ae80906a8..7ae5de4d17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -381,7 +381,7 @@ use core::{ #[cfg(feature = "std")] use std::io; -use crate::pointer::invariant::{self, BecauseExclusive}; +use crate::pointer::{invariant, BecauseBidirectional}; #[cfg(any(feature = "alloc", test, kani))] extern crate alloc; @@ -1840,7 +1840,7 @@ pub unsafe trait TryFromBytes { Self: KnownLayout + IntoBytes, { static_assert_dst_is_not_zst!(Self); - match Ptr::from_mut(bytes).try_cast_into_no_leftover::(None) { + match Ptr::from_mut(bytes).try_cast_into_no_leftover(None) { Ok(source) => { // This call may panic. If that happens, it doesn't cause any soundness // issues, as we have not generated any invalid state which we need to @@ -1852,9 +1852,7 @@ pub unsafe trait TryFromBytes { // condition will not happen. match source.try_into_valid() { Ok(source) => Ok(source.as_mut()), - Err(e) => { - Err(e.map_src(|src| src.as_bytes::().as_mut()).into()) - } + Err(e) => Err(e.map_src(|src| src.as_bytes().as_mut()).into()), } } Err(e) => Err(e.map_src(Ptr::as_mut).into()), @@ -2421,8 +2419,7 @@ pub unsafe trait TryFromBytes { where Self: KnownLayout + IntoBytes, { - match Ptr::from_mut(source).try_cast_into_no_leftover::(Some(count)) - { + match Ptr::from_mut(source).try_cast_into_no_leftover(Some(count)) { Ok(source) => { // This call may panic. If that happens, it doesn't cause any soundness // issues, as we have not generated any invalid state which we need to @@ -2434,9 +2431,7 @@ pub unsafe trait TryFromBytes { // condition will not happen. match source.try_into_valid() { Ok(source) => Ok(source.as_mut()), - Err(e) => { - Err(e.map_src(|src| src.as_bytes::().as_mut()).into()) - } + Err(e) => Err(e.map_src(|src| src.as_bytes().as_mut()).into()), } } Err(e) => Err(e.map_src(Ptr::as_mut).into()), @@ -2844,7 +2839,7 @@ fn try_mut_from_prefix_suffix, ) -> Result<(&mut T, &mut [u8]), TryCastError<&mut [u8], T>> { - match Ptr::from_mut(candidate).try_cast_into::(cast_type, meta) { + match Ptr::from_mut(candidate).try_cast_into(cast_type, meta) { Ok((candidate, prefix_suffix)) => { // This call may panic. If that happens, it doesn't cause any soundness // issues, as we have not generated any invalid state which we need to @@ -2856,7 +2851,7 @@ fn try_mut_from_prefix_suffix Ok((valid.as_mut(), prefix_suffix.as_mut())), - Err(e) => Err(e.map_src(|src| src.as_bytes::().as_mut()).into()), + Err(e) => Err(e.map_src(|src| src.as_bytes().as_mut()).into()), } } Err(e) => Err(e.map_src(Ptr::as_mut).into()), @@ -3832,8 +3827,8 @@ pub unsafe trait FromBytes: FromZeros { Self: IntoBytes + KnownLayout, { static_assert_dst_is_not_zst!(Self); - match Ptr::from_mut(source).try_cast_into_no_leftover::<_, BecauseExclusive>(None) { - Ok(ptr) => Ok(ptr.recall_validity::<_, (_, (_, _))>().as_mut()), + match Ptr::from_mut(source).try_cast_into_no_leftover(None) { + Ok(ptr) => Ok(ptr.recall_validity::<_, BecauseBidirectional>().as_mut()), Err(err) => Err(err.map_src(|src| src.as_mut())), } } @@ -4301,11 +4296,9 @@ pub unsafe trait FromBytes: FromZeros { Self: IntoBytes + KnownLayout + Immutable, { let source = Ptr::from_mut(source); - let maybe_slf = source.try_cast_into_no_leftover::<_, BecauseImmutable>(Some(count)); + let maybe_slf = source.try_cast_into_no_leftover(Some(count)); match maybe_slf { - Ok(slf) => Ok(slf - .recall_validity::<_, (_, (_, (BecauseExclusive, BecauseExclusive)))>() - .as_mut()), + Ok(slf) => Ok(slf.recall_validity::<_, BecauseBidirectional>().as_mut()), Err(err) => Err(err.map_src(|s| s.as_mut())), } } @@ -4662,7 +4655,7 @@ pub unsafe trait FromBytes: FromZeros { // cannot be violated even though `buf` may have more permissive bit // validity than `ptr`. let ptr = unsafe { ptr.assume_validity::() }; - let ptr = ptr.as_bytes::(); + let ptr = ptr.as_bytes(); src.read_exact(ptr.as_mut())?; // SAFETY: `buf` entirely consists of initialized bytes, and `Self` is // `FromBytes`. @@ -4781,9 +4774,9 @@ fn mut_from_prefix_suffix( cast_type: CastType, ) -> Result<(&mut T, &mut [u8]), CastError<&mut [u8], T>> { let (slf, prefix_suffix) = Ptr::from_mut(source) - .try_cast_into::<_, BecauseExclusive>(cast_type, meta) + .try_cast_into(cast_type, meta) .map_err(|err| err.map_src(|s| s.as_mut()))?; - Ok((slf.recall_validity::<_, (_, (_, _))>().as_mut(), prefix_suffix.as_mut())) + Ok((slf.recall_validity::<_, BecauseBidirectional>().as_mut(), prefix_suffix.as_mut())) } /// Analyzes whether a type is [`IntoBytes`]. diff --git a/src/pointer/invariant.rs b/src/pointer/invariant.rs index 90ab1a8734..a913c94659 100644 --- a/src/pointer/invariant.rs +++ b/src/pointer/invariant.rs @@ -198,25 +198,6 @@ unsafe impl CastableFrom for DT {} // SAFETY: `SV = DV = Initialized`. unsafe impl CastableFrom for DT {} -/// [`Ptr`](crate::Ptr) referents that permit unsynchronized read operations. -/// -/// `T: Read` implies that a pointer to `T` with aliasing `A` permits -/// unsynchronized read operations. This can be because `A` is [`Exclusive`] or -/// because `T` does not permit interior mutation. -/// -/// # Safety -/// -/// `T: Read` if either of the following conditions holds: -/// - `A` is [`Exclusive`] -/// - `T` implements [`Immutable`](crate::Immutable) -/// -/// As a consequence, if `T: Read`, then any `Ptr` is -/// permitted to perform unsynchronized reads from its referent. -pub trait Read {} - -impl Read for T {} -impl Read for T {} - /// Unsynchronized reads are permitted because only one live [`Ptr`](crate::Ptr) /// or reference may exist to the referent bytes at a time. #[derive(Copy, Clone, Debug)] diff --git a/src/pointer/mod.rs b/src/pointer/mod.rs index 6005bc78f7..76c6539906 100644 --- a/src/pointer/mod.rs +++ b/src/pointer/mod.rs @@ -17,10 +17,7 @@ mod transmute; #[doc(hidden)] pub use {inner::PtrInner, transmute::*}; #[doc(hidden)] -pub use { - invariant::{BecauseExclusive, BecauseImmutable, Read}, - ptr::Ptr, -}; +pub use {invariant::BecauseExclusive, ptr::Ptr}; /// A shorthand for a maybe-valid, maybe-aligned reference. Used as the argument /// to [`TryFromBytes::is_bit_valid`]. @@ -30,11 +27,11 @@ pub type Maybe<'a, T, Aliasing = invariant::Shared, Alignment = invariant::Unali Ptr<'a, T, (Aliasing, Alignment, invariant::Initialized)>; /// Checks if the referent is zeroed. -pub(crate) fn is_zeroed(ptr: Ptr<'_, T, I>) -> bool +pub(crate) fn is_zeroed(mut ptr: Ptr<'_, T, I>) -> bool where T: crate::Immutable + crate::KnownLayout, I: invariant::Invariants, I::Aliasing: invariant::Reference, { - ptr.as_bytes::().as_ref().iter().all(|&byte| byte == 0) + ptr.reborrow().into_shared().as_bytes().as_ref().iter().all(|&byte| byte == 0) } diff --git a/src/pointer/ptr.rs b/src/pointer/ptr.rs index 254d4f1c6f..1774eecc69 100644 --- a/src/pointer/ptr.rs +++ b/src/pointer/ptr.rs @@ -15,7 +15,7 @@ use crate::{ pointer::{ inner::PtrInner, invariant::*, - transmute::{MutationCompatible, SizeEq, TransmuteFromPtr}, + transmute::{MutationCompatible, SizeFrom, TransmuteFromPtr}, }, AlignmentError, CastError, CastType, KnownLayout, SizeError, TryFromBytes, ValidityError, }; @@ -270,9 +270,8 @@ mod _conversions { { /// Reborrows `self`, producing another `Ptr`. /// - /// Since `self` is borrowed immutably, this prevents any mutable - /// methods from being called on `self` as long as the returned `Ptr` - /// exists. + /// Since `self` is borrowed mutably, this prevents `self` from being + /// used as long as the returned `Ptr` exists. #[doc(hidden)] #[inline] #[allow(clippy::needless_lifetimes)] // Allows us to name the lifetime in the safety comment below. @@ -313,6 +312,24 @@ mod _conversions { // memory may be live. unsafe { Ptr::from_inner(self.as_inner()) } } + + /// Converts `self` to a shared `Ptr`. + #[doc(hidden)] + #[inline] + #[must_use] + #[allow(clippy::needless_lifetimes)] // Allows us to name the lifetime in the safety comment below. + pub fn into_shared(self) -> Ptr<'a, T, (Shared, I::Alignment, I::Validity)> { + // SAFETY: Since `I::Aliasing: Reference`, there are two cases for + // `I::Aliasing`: + // - For `invariant::Shared`, the returned `Ptr` is identical, so no + // new proof obligations are introduced. + // - For `invariant::Exclusive`: Since `self` has `Exclusive` + // aliasing, it is guaranteed that no other `Ptr`s or references + // permit concurrent access to the referent. Thus, the returned + // `Ptr` is the only reference to the referent which may read or + // write the referent during `'a`. + unsafe { self.assume_aliasing() } + } } /// `Ptr<'a, T>` → `&'a mut T` @@ -362,11 +379,11 @@ mod _conversions { pub(crate) fn transmute(self) -> Ptr<'a, U, (I::Aliasing, Unaligned, V)> where V: Validity, - U: TransmuteFromPtr + SizeEq + ?Sized, + U: TransmuteFromPtr + SizeFrom + ?Sized, { // SAFETY: - // - `SizeEq::cast_from_raw` promises to preserve address, - // provenance, and the number of bytes in the referent + // - `SizeFrom::cast_from_raw` promises to preserve address and to + // address a prefix of the bytes addressed by its argument // - If aliasing is `Shared`, then by `U: TransmuteFromPtr`, at // least one of the following holds: // - `T: Immutable` and `U: Immutable`, in which case it is @@ -376,7 +393,7 @@ mod _conversions { // operate on these references simultaneously // - By `U: TransmuteFromPtr`, it is // sound to perform this transmute. - unsafe { self.transmute_unchecked(SizeEq::cast_from_raw) } + unsafe { self.transmute_unchecked(SizeFrom::cast_from_raw) } } #[doc(hidden)] @@ -394,7 +411,7 @@ mod _conversions { // referent simultaneously // - By `T: TransmuteFromPtr`, it is // sound to perform this transmute. - let ptr = unsafe { self.transmute_unchecked(SizeEq::cast_from_raw) }; + let ptr = unsafe { self.transmute_unchecked(SizeFrom::cast_from_raw) }; // SAFETY: `self` and `ptr` have the same address and referent type. // Therefore, if `self` satisfies `I::Alignment`, then so does // `ptr`. @@ -412,8 +429,7 @@ mod _conversions { /// /// The caller promises that `u = cast(p)` is a pointer cast with the /// following properties: - /// - `u` addresses a subset of the bytes addressed by `p` - /// - `u` has the same provenance as `p` + /// - `u` addresses a prefix of the bytes addressed by `p` /// - If `I::Aliasing` is [`Shared`], it must not be possible for safe /// code, operating on a `&T` and `&U` with the same referent /// simultaneously, to cause undefined behavior @@ -511,7 +527,6 @@ mod _conversions { pub fn read_unaligned(self) -> T where T: Copy, - T: Read, { (*self.into_unalign().as_ref()).into_inner() } @@ -799,13 +814,11 @@ mod _transitions { /// On error, unsafe code may rely on this method's returned /// `ValidityError` containing `self`. #[inline] - pub(crate) fn try_into_valid( + pub(crate) fn try_into_valid( mut self, ) -> Result, ValidityError> where - T: TryFromBytes - + Read - + TryTransmuteFromPtr, + T: TryFromBytes + TryTransmuteFromPtr, I::Aliasing: Reference, I: Invariants, { @@ -816,9 +829,13 @@ mod _transitions { // SAFETY: If `T::is_bit_valid`, code may assume that `self` // contains a bit-valid instance of `T`. By `T: // TryTransmuteFromPtr`, so - // long as `self`'s referent conforms to the `Valid` validity - // for `T` (which we just confired), then this transmute is - // sound. + // long as `SizeFrom::cast_from_raw(self.into_inner)`'s referent + // conforms to the `Valid` validity for `T`, then this transmute + // is sound. Since `>::cast_from_raw` is + // guaranteed to be the identity function, this is equivalent to + // the statement that `self`'s referent conforms to the `Valid` + // validity for `T`, which we just confirmed using + // `T::is_bit_valid`. Ok(unsafe { self.assume_valid() }) } else { Err(ValidityError::new(self)) @@ -915,16 +932,16 @@ mod _casts { cast: F, ) -> Ptr<'a, U, (I::Aliasing, Unaligned, I::Validity)> where - T: MutationCompatible, - U: 'a + ?Sized + CastableFrom, + U: 'a + + MutationCompatible + + CastableFrom + + ?Sized, F: FnOnce(PtrInner<'a, T>) -> PtrInner<'a, U>, { // SAFETY: Because `T: MutationCompatible`, one // of the following holds: - // - `T: Read` and `U: Read`, in which - // case one of the following holds: - // - `I::Aliasing` is `Exclusive` - // - `T` and `U` are both `Immutable` + // - `I::Aliasing` is `Exclusive` + // - `T` and `U` are both `Immutable` // - It is sound for safe code to operate on `&T` and `&U` with the // same referent simultaneously // @@ -944,14 +961,14 @@ mod _casts { #[inline] pub fn as_bytes(self) -> Ptr<'a, [u8], (I::Aliasing, Aligned, Valid)> where - T: Read, + [u8]: MutationCompatible, I::Aliasing: Reference, { // SAFETY: `PtrInner::as_bytes` returns a pointer which addresses // the same byte range as its argument, and which has the same // provenance. let ptr = unsafe { self.cast_unsized(PtrInner::as_bytes) }; - ptr.bikeshed_recall_aligned().recall_validity::() + ptr.bikeshed_recall_aligned().recall_validity() } } @@ -1036,7 +1053,8 @@ mod _casts { > where I::Aliasing: Reference, - U: 'a + ?Sized + KnownLayout + Read, + U: 'a + ?Sized + KnownLayout, + [u8]: MutationCompatible, { let (inner, remainder) = self.as_inner().try_cast_into(cast_type, meta).map_err(|err| { @@ -1049,13 +1067,16 @@ mod _casts { })?; // SAFETY: - // 0. Since `U: Read`, either: - // - `I::Aliasing` is `Exclusive`, in which case both `src` and - // `ptr` conform to `Exclusive` + // 0. Since `[u8]: MutationCompatible`, either: + // - `I::Aliasing` is `Exclusive`, in which case both `self` and + // `inner` conform to `Exclusive` // - `I::Aliasing` is `Shared` and `U` is `Immutable` (we already // know that `[u8]: Immutable`). In this case, neither `U` nor // `[u8]` permit mutation, and so `Shared` aliasing is // satisfied. + // - `I::Aliasing` is `Shared` and `[u8]: InvariantsEq`, in + // which case it is sound for `self` and `inner` to exist at + // the same time. // 1. `ptr` conforms to the alignment invariant of `Aligned` because // it is derived from `try_cast_into`, which promises that the // object described by `target` is validly aligned for `U`. @@ -1099,7 +1120,8 @@ mod _casts { ) -> Result, CastError> where I::Aliasing: Reference, - U: 'a + ?Sized + KnownLayout + Read, + U: 'a + ?Sized + KnownLayout, + [u8]: MutationCompatible, { // FIXME(#67): Remove this allow. See NonNulSlicelExt for more // details. @@ -1231,7 +1253,7 @@ mod tests { use super::*; #[allow(unused)] // Needed on our MSRV, but considered unused on later toolchains. use crate::util::AsAddress; - use crate::{pointer::BecauseImmutable, util::testutil::AU64, FromBytes, Immutable}; + use crate::{pointer::invariant::BecauseImmutable, util::testutil::AU64, FromBytes, Immutable}; mod test_ptr_try_cast_into_soundness { use super::*; diff --git a/src/pointer/transmute.rs b/src/pointer/transmute.rs index 1fa0540526..0ac8a6becb 100644 --- a/src/pointer/transmute.rs +++ b/src/pointer/transmute.rs @@ -13,7 +13,7 @@ use core::{ }; use crate::{ - pointer::{invariant::*, PtrInner}, + pointer::{inner::PtrInner, invariant::*}, FromBytes, Immutable, IntoBytes, Unalign, }; @@ -33,42 +33,43 @@ use crate::{ /// Given `Dst: TryTransmuteFromPtr`, callers may assume the /// following: /// -/// 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. +/// Given `src: Ptr<'a, Src, (A, _, SV)>` and `dst_inner = +/// SizeFrom::cast_from_raw(src.into_inner())`, if the referent of `dst_inner` +/// is `DV`-valid for `Dst`, then it is sound to construct `dst: Ptr<'a, Dst, +/// (A, Unaligned, DV)>` from `dst_inner`. Equivalently, it is sound to +/// transmute `src` into `dst` using [`SizeFrom::cast_from_raw`]. /// /// ## Pre-conditions /// -/// Given `src: Ptr` and `dst: Ptr`, -/// `Dst: TryTransmuteFromPtr` is sound if all of the -/// following hold: +/// Given `src: Ptr` and `dst: Ptr` +/// constructed from `SizeFrom::cast_from_raw(src.into_inner())`, `Dst: +/// TryTransmuteFromPtr` is sound if all of the following +/// hold: /// - 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 +/// - `Dst: TransmuteFrom` /// - 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 +/// - `Src: TransmuteFromOverwrite` /// - No safe code, given access to `src` and `dst`, can cause undefined /// behavior: Any of the following hold: /// - `A` is `Exclusive` /// - `Src: Immutable` and `Dst: Immutable` -/// - It is sound for shared code to operate on a `&Src` and `&Dst` which -/// reference the same byte range at the same time +/// - It is sound for safe code to operate on `src.as_ref(): &Src` and +/// `dst.as_ref(): &Dst` at the same time /// /// ## Proof /// /// Given: /// - `src: Ptr<'a, Src, (A, _, SV)>` -/// - `src`'s referent is `DV`-valid for `Dst` -/// - `Dst: SizeEq` +/// - The leading `N` bytes of `src`'s referent are a `DV`-valid `Dst`, where +/// `N` is the referent size of `SizeFrom::cast_from_raw(src)` /// -/// 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, -/// 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. +/// We are trying to prove that it is sound to use `SizeFrom::cast_from_raw` to +/// transmute 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: @@ -78,16 +79,16 @@ use crate::{ /// - If its referent is not zero sized, `A` is guaranteed to live for at least /// `'a` /// -/// 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`. +/// By post-condition on `SizeFrom::cast_from_raw`, `dst` addresses a prefix of +/// the bytes addressed by `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`: +/// Third, aliasing (`A`) 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 @@ -95,79 +96,63 @@ use crate::{ /// - If it is `Shared`, then either: /// - `Src: Immutable` and `Dst: Immutable`, and so `UnsafeCell`s trivially /// cover the same byte ranges in both types. -/// - 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. +/// - It is sound for safe code to operate on a `src.as_ref()` and +/// `dst.as_ref()` at the same time. /// /// Fourth, `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`. +/// - `Src: TransmuteFromOverwrite`. Since `Dst: SizeFrom`, +/// and since `dst` is produced using `SizeFrom::cast_from_raw`, given `src'` +/// composed by concatenating any `DV`-valid `Dst` of size `size_of_val(dst)` +/// with the trailing `size_of_val(src) - size_of_val(dst)` bytes of `src`, +/// `src'` is an `SV`-valid `Src`. Thus, any value written to `dst` is +/// guaranteed not to violate the `SV`-validity of `Src`. /// /// Fifth, `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: +/// leading bytes of the referent are `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`. +/// - `Dst: TransmuteFrom`. Since `Dst: SizeFrom`, and since +/// `dst` is produced using `SizeFrom::cast_from_raw`, the leading +/// `size_of_val(dst)` bytes of any `SV`-valid `Src` constitute a `DV`-valid +/// `Dst`. Thus, any value written via `src` is guaranteed not to violate the +/// `DV`-validity of `Dst`. pub unsafe trait TryTransmuteFromPtr: - SizeEq + SizeFrom { } -#[allow(missing_copy_implementations, missing_debug_implementations)] -pub enum BecauseMutationCompatible {} - // SAFETY: -// - Forwards transmutation: By `Dst: MutationCompatible`, we -// know that at least one of the following holds: -// - So long as `dst: Ptr` is active, no mutation of its referent is -// allowed except via `dst` itself if either of the following hold: -// - Aliasing is `Exclusive`, in which case, so long as the `Dst` `Ptr` -// 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. -// - No safe code, given access to `src` and `dst`, can cause undefined -// behavior: By `Dst: MutationCompatible`, at least one of +// - Forwards transmutation: By `Dst: MutationCompatible`, one +// of the following holds: +// - So long as `dst` is active, no mutation of `dst`'s referent is allowed +// except via `dst` itself because: +// - `A = Exclusive`, or +// - `A = Shared`, `Src: Immutable`, and `Dst: Immutable`, in which case no +// mutation is permitted whatsoever. +// - `Dst: TransmuteFrom` +// - Reverse transmutation: By `Dst: MutationCompatible`, one of // the following holds: +// - `dst` does not permit mutation of its referent because `A = Shared`, +// `Src: Immutable`, and `Dst: Immutable` +// - `Src: TransmuteFromOverwrite` +// - No safe code, given access to `src` and `dst`, can cause undefined +// behavior: By `Dst: MutationCompatible`, one of the +// following holds: // - `A` is `Exclusive` // - `Src: Immutable` and `Dst: Immutable` -// - `Dst: InvariantsEq`, which guarantees that `Src` and `Dst` have the -// same invariants, and have `UnsafeCell`s covering the same byte ranges -unsafe impl - TryTransmuteFromPtr for Dst +// - `Dst: InvariantsEq`, in which case it is sound for safe code to +// operate on `src.as_ref(): &Src` and `dst.as_ref(): &Dst` at the same time +unsafe impl TryTransmuteFromPtr for Dst where A: Aliasing, SV: Validity, DV: Validity, - Src: TransmuteFrom + ?Sized, - Dst: MutationCompatible + SizeEq + ?Sized, -{ -} - -// SAFETY: -// - Forwards transmutation: Since aliasing is `Shared` and `Src: Immutable`, -// `src` does not permit mutation of its referent. -// - Reverse transmutation: Since aliasing is `Shared` and `Dst: Immutable`, -// `dst` does not permit mutation of its referent. -// - No safe code, given access to `src` and `dst`, can cause undefined -// behavior: `Src: Immutable` and `Dst: Immutable` -unsafe impl TryTransmuteFromPtr for Dst -where - SV: Validity, - DV: Validity, - Src: Immutable + ?Sized, - Dst: Immutable + SizeEq + ?Sized, + Src: ?Sized, + Dst: MutationCompatible + SizeFrom + ?Sized, { } @@ -178,21 +163,42 @@ where /// # Safety /// /// At least one of the following must hold: -/// - `Src: Read` and `Self: Read` -/// - `Self: InvariantsEq`, and, for some `V`: -/// - `Dst: TransmuteFrom` -/// - `Src: TransmuteFrom` +/// - `A = Exclusive` and `Src: TransmuteOverwrite` +/// - `A = Shared`, `Src: Immutable`, and `Dst: Immutable` +/// - `Dst: TransmuteFrom`, `Src: TransmuteOverwrite`, +/// and `Dst: InvariantsEq` pub unsafe trait MutationCompatible {} #[allow(missing_copy_implementations, missing_debug_implementations)] -pub enum BecauseRead {} +pub enum BecauseReversible {} -// SAFETY: `Src: Read` and `Dst: Read`. -unsafe impl - MutationCompatible for Dst +#[allow(missing_copy_implementations, missing_debug_implementations)] +pub enum BecauseBidirectional {} + +// SAFETY: Aliasing is `Exclusive` and `Src: TransmuteOverwrite`. +unsafe impl + MutationCompatible for Dst where - Src: Read, - Dst: Read, + Src: TransmuteOverwrite, +{ +} + +// SAFETY: Aliasing is `Shared`, `Src: Immutable`, and `Dst: Immutable`. +unsafe impl + MutationCompatible for Dst +where + Src: Immutable, + Dst: Immutable, +{ +} + +// SAFETY: `Dst: TransmuteFrom`, `Src: TransmuteFromOverwrite`, and `Dst: InvariantsEq`. +unsafe impl + MutationCompatible for Dst +where + Dst: TransmuteFrom + InvariantsEq, + Src: TransmuteOverwrite, { } @@ -208,18 +214,6 @@ pub unsafe trait InvariantsEq {} // SAFETY: Trivially sound to have multiple `&T` pointing to the same referent. unsafe impl InvariantsEq for T {} -// SAFETY: `Dst: InvariantsEq + TransmuteFrom`, and `Src: -// TransmuteFrom`. -unsafe impl - MutationCompatible for Dst -where - Src: TransmuteFrom, - Dst: TransmuteFrom + InvariantsEq, -{ -} - -pub(crate) enum BecauseInvariantsEq {} - macro_rules! unsafe_impl_invariants_eq { ($tyvar:ident => $t:ty, $u:ty) => {{ crate::util::macros::__unsafe(); @@ -250,13 +244,45 @@ unsafe impl InvariantsEq> for T {} /// Transmutations which are always sound. /// -/// `TransmuteFromPtr` is a shorthand for [`TryTransmuteFromPtr`] and -/// [`TransmuteFrom`]. +/// `TransmuteFromPtr` is a shorthand for the conjuction of +/// [`TryTransmuteFromPtr`] and [`TransmuteFrom`]. /// /// # Safety /// /// `Dst: TransmuteFromPtr` is equivalent to `Dst: /// TryTransmuteFromPtr + TransmuteFrom`. +/// +/// Further, if `Dst: TransmuteFromPtr`, then given `src: +/// Ptr<'_, (A, _, SV)>`, it is sound to transmute `src` to `dst: Ptr<'_, (A, _, +/// DV)>` using [`SizeFrom::cast_from_raw`] to perform the raw pointer +/// transmute. +/// +/// ## Proof +/// +/// By `Dst: TryTransmuteFromPtr`: +/// +/// > Given `src: Ptr<'a, Src, (A, _, SV)>` and `dst_inner = +/// > SizeFrom::cast_from_raw(src.into_inner())`, if the referent of `dst_inner` +/// > is `DV`-valid for `Dst`, then it is sound to construct `dst: Ptr<'a, Dst, +/// > (A, Unaligned, DV)>` from `dst_inner`. Equivalently, it is sound to +/// > transmute `src` into `dst` using [`SizeFrom::cast_from_raw`]. +/// +/// Thus, we just need to prove that `dst_inner`'s referent is `DV`-valid for +/// `Dst`. +/// +/// By `Dst: TransmuteFrom`: +/// +/// > If `Dst: SizeFrom`, then the following must hold: For all [valid +/// > sizes] of `Src`, `ssize`, let `s: PtrInner<'_, Src>` with referent size +/// > `ssize`. Let `dsize` be the referent size of `SizeFrom::cast_from_raw(s)`. +/// > Note that, by invariant on `cast_from_raw`, `ssize >= dsize`. For all +/// > `SV`-valid values of `Src` with size `ssize`, `src`, it must be the case +/// > that the leading `dsize` bytes of `src` constitute a `DV`-valid `Dst`. +/// +/// `dst_inner = SizeFrom::cast_from_raw(src.into_inner())`, and so its referent +/// size is equal to `dsize` in the `TransmuteFrom` safety invariant. Thus, for +/// all possible referents of `src`, `dst_inner`'s referent constitutes a +/// `DV`-valid `Dst`. pub unsafe trait TransmuteFromPtr: TryTransmuteFromPtr + TransmuteFrom { @@ -276,43 +302,76 @@ where /// /// # Safety /// -/// Given `src: Ptr` and `dst: Ptr`, if the -/// referents of `src` and `dst` are the same size, then the set of bit patterns -/// allowed to appear in `src`'s referent must be a subset of the set allowed to -/// appear in `dst`'s referent. +/// *In this section, we refer to `Self` as `Dst`.* /// -/// If the referents are not the same size, then `Dst: TransmuteFrom` conveys no safety guarantee. +/// If `Dst: SizeFrom` (or `Dst: Sized` and `Src: Sized` where +/// `size_of::() <= size_of::()`), then the following must hold: For +/// all [valid sizes] of `Src` (or for `size_of::()`), `ssize`, let `s: +/// PtrInner<'_, Src>` with referent size `ssize`. Let `dsize` be the referent +/// size of `SizeFrom::cast_from_raw(s)` (or `size_of::()`). Note that, by +/// invariant on `cast_from_raw` (or by `size_of::() <= size_of::()`), +/// `ssize >= dsize`. For all `SV`-valid values of `Src` with size `ssize`, +/// `src`, it must be the case that the leading `dsize` bytes of `src` +/// constitute a `DV`-valid `Dst`. +/// +/// [valid sizes]: crate::KnownLayout#what-is-a-valid-size pub unsafe trait TransmuteFrom {} +/// Denotes that any `BV`-valid `Self` may have its prefix overwritten with any +/// `OV`-valid `Overlay` and remain `BV`-valid. +/// /// # 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 it must be the case that, given -/// any `t: PtrInner<'_, T>`, `>::cast_from_raw(t)` produces -/// a pointer which addresses the same number of bytes as `t`. *Note that it -/// is **not** guaranteed that an `as` cast preserves referent size: it may be -/// the case that `cast_from_raw` modifies the pointer's metadata in order to -/// preserve referent size, which an `as` cast does not do.* -pub unsafe trait SizeEq { - fn cast_from_raw(t: PtrInner<'_, T>) -> PtrInner<'_, Self>; +/// *In this section, we refer to `Self` as `Base`.* +/// +/// If `Overlay: SizeFrom` (or `Overlay: Sized` and `Base: Sized` where +/// `size_of::() <= size_of::()`), then the following must hold: +/// For all [valid sizes] of `Base` (or for `size_of::()`), `bsize`, let +/// `b: PtrInner<'_, Base>` with referent size `bsize`. Let `osize` be the +/// referent size of `SizeFrom::cast_from_raw(b)` (or `size_of::()`). +/// Note that, by invariant on `cast_from_raw` (or by `size_of::() <= +/// size_of::()`), `bsize >= osize`. For all `BV`-valid values of `Base` +/// with size `bsize`, `base`, and for all `OV`-valid values of `Overlay` with +/// size `osize`, `overlay`, let `base'` be constructed by concatenating +/// `overlay` with the trailing `bsize - osize` bytes of `base`. It must be the +/// case that `base'` is a `BV`-valid `Base`. +/// +/// [valid sizes]: crate::KnownLayout#what-is-a-valid-size +pub unsafe trait TransmuteOverwrite {} + +/// # Safety +/// +/// Implementations of `cast_from_raw` must satisfy that method's safety +/// post-condition. +pub unsafe trait SizeFrom { + /// # Safety + /// + /// Given `src: PtrInner<'_, Src>`, `let dst = Self::cast_from_raw(src)` + /// produces a pointer with the same address as `src`, and referring to at + /// most as many bytes. + /// + /// The size of `dst` must only be a function of the size of `src`. It must + /// not be a function of `src`'s address. + /// + /// `>::cast_from_raw` is guaranteed to be the + /// identity function. + fn cast_from_raw(src: PtrInner<'_, Src>) -> PtrInner<'_, 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 SizeEq for T { +unsafe impl SizeFrom for T { #[inline(always)] - fn cast_from_raw(t: PtrInner<'_, T>) -> PtrInner<'_, T> { + fn cast_from_raw<'a>(t: PtrInner<'a, T>) -> PtrInner<'a, T> { t } } +/// `Valid → Initialized` // 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`. +// any `Initialized` `Ptr`. This holds even for shrinking transmutes. unsafe impl TransmuteFrom for Dst where Src: IntoBytes + ?Sized, @@ -320,9 +379,24 @@ 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`. +/// `Valid → Initialized` +// SAFETY: Let `overlay` be a `Valid` `Overlay` and `base` be an `Initialized` +// `Base`. The trailing bytes of `base` have bit validity `[u8; N]`. By +// `Overlay: IntoBytes`, `overlay`'s bit validity is at least as restrictive as +// `[u8; M]` (some `[u8; M]` values may not be valid `Overlay` values). Thus, +// `base' = overlay + trailing_bytes_of(base)` is a valid `[u8; N + M]`, which +// is a valid `Initialized` value. +unsafe impl TransmuteOverwrite for Base +where + Overlay: IntoBytes + ?Sized, + Base: ?Sized, +{ +} + +/// `Initialized → Valid` +// SAFETY: Since `Dst: FromBytes`, any initialized bit pattern is a valid `Dst`. +// An `Initialized` is guaranteed to have all its bytes initialized, so any +// (prefix of an) `Initialized` is a valid `Dst`. unsafe impl TransmuteFrom for Dst where Src: ?Sized, @@ -330,12 +404,44 @@ where { } +/// `Initialized → Valid` +// SAFETY: The bit validity of `Initialized` is equivalent to that of +// `[u8]`. By `Base: FromBytes + IntoBytes`, the validity of `Valid` is +// also equivalent to that of `[u8]`. Any two `[u8]`s concatenated together are +// a valid `[u8]`. +// +// It might be tempting to remove the `Base: IntoBytes` bound. Unfortunately, +// that would be unsound. Consider the following type: +// +// #[repr(u8)] +// enum Base { +// V0(u8), +// V1(MaybeUninit), +// ... +// V255(MaybeUninit), +// } +// +// Consider combining this with `Overlay` type `u8`. Now consider the following +// sequence: +// - Start with `base = Base::V1(MaybeUninit::uninit())` +// - Overwrite with overlay `0` +// - This results in the bit pattern `0x00` followed by an uninit byte, which is +// invalid for `Base` +unsafe impl TransmuteOverwrite for Base +where + Overlay: ?Sized, + Base: FromBytes + IntoBytes + ?Sized, +{ +} + // FIXME(#2354): This seems like a smell - the soundness of this bound has // nothing to do with `Src` or `Dst` - we're basically just saying `[u8; N]` is // transmutable into `[u8; N]`. -// SAFETY: The set of allowed bit patterns in the referent of any `Initialized` -// `Ptr` is the same regardless of referent type. +/// `Initialized → Initialized` +// SAFETY: The validity of `Initialized` is equal to that of `[u8]`. `[u8]`'s +// validity does not depend on a value's length, so any prefix of an +// `Initialized` is a valid `Initialized`. unsafe impl TransmuteFrom for Dst where Src: ?Sized, @@ -343,12 +449,25 @@ where { } +/// `Initialized → Initialized` +// SAFETY: The validity of `Initialized` is equal to that of `[u8]`. `[u8]`'s +// validity does not depend on a value's length, so two `[u8]`s concatenated +// together are also a valid `[u8]`. Thus, an `Initialized` +// concatenated with the `[u8]` suffix of an `Initialized` is a valid +// `Initialized`. +unsafe impl TransmuteOverwrite for Base +where + Overlay: ?Sized, + Base: ?Sized, +{ +} + // FIXME(#2354): This seems like a smell - the soundness of this bound has // nothing to do with `Dst` - we're basically just saying that any type is // transmutable into `MaybeUninit<[u8; N]>`. -// SAFETY: A `Dst` with validity `Uninit` permits any byte sequence, and -// therefore can be transmuted from any value. +/// `V → Uninit` +// SAFETY: A `Dst` with validity `Uninit` permits any byte sequence. unsafe impl TransmuteFrom for Dst where Src: ?Sized, @@ -357,6 +476,16 @@ where { } +/// `V → Uninit` +// SAFETY: A `Base` with validity `Uninit` permits any byte sequence. +unsafe impl TransmuteOverwrite for Base +where + Overlay: ?Sized, + Base: ?Sized, + V: Validity, +{ +} + // SAFETY: // - `ManuallyDrop` has the same size as `T` [1] // - `ManuallyDrop` has the same validity as `T` [1] @@ -447,19 +576,25 @@ const _: () = unsafe { unsafe_impl_for_transparent_wrapper!(T: ?Sized => Cell impl_transitive_transmute_from!(T: ?Sized => Cell => T => UnsafeCell); impl_transitive_transmute_from!(T: ?Sized => UnsafeCell => T => Cell); +/// `Uninit → Valid>` // SAFETY: `MaybeUninit` has no validity requirements. Currently this is not // explicitly guaranteed, but it's obvious from `MaybeUninit`'s documentation // that this is the intention: // https://doc.rust-lang.org/1.85.0/core/mem/union.MaybeUninit.html -unsafe impl TransmuteFrom for MaybeUninit {} +unsafe impl TransmuteFrom for MaybeUninit {} + +/// `Uninit → Valid>` +// SAFETY: See previous safety comment. +unsafe impl TransmuteOverwrite for MaybeUninit {} -// SAFETY: `MaybeUninit` has the same size as `T` [1]. +// SAFETY: `MaybeUninit` has the same size as `T` [1]. Thus, a pointer cast +// preserves address and referent size. // // [1] Per https://doc.rust-lang.org/1.81.0/std/mem/union.MaybeUninit.html#layout-1: // // `MaybeUninit` is guaranteed to have the same size, alignment, and ABI as // `T` -unsafe impl SizeEq for MaybeUninit { +unsafe impl SizeFrom for MaybeUninit { #[inline(always)] fn cast_from_raw(t: PtrInner<'_, T>) -> PtrInner<'_, MaybeUninit> { // SAFETY: Per preceding safety comment, `MaybeUninit` and `T` have @@ -469,7 +604,7 @@ unsafe impl SizeEq for MaybeUninit { } // SAFETY: See previous safety comment. -unsafe impl SizeEq> for T { +unsafe impl SizeFrom> for T { #[inline(always)] fn cast_from_raw(t: PtrInner<'_, MaybeUninit>) -> PtrInner<'_, T> { // SAFETY: Per preceding safety comment, `MaybeUninit` and `T` have diff --git a/src/ref.rs b/src/ref.rs index 423bf84988..a428168ff6 100644 --- a/src/ref.rs +++ b/src/ref.rs @@ -656,9 +656,9 @@ where // `b.into_byte_slice_mut()` produces a byte slice with identical // address and length to that produced by `b.deref_mut()`. let ptr = Ptr::from_mut(b.into_byte_slice_mut()) - .try_cast_into_no_leftover::(None) + .try_cast_into_no_leftover(None) .expect("zerocopy internal error: into_ref should be infallible"); - let ptr = ptr.recall_validity::<_, (_, (_, _))>(); + let ptr = ptr.recall_validity::<_, BecauseBidirectional>(); ptr.as_mut() } } @@ -797,9 +797,9 @@ where // are preserved through `.deref_mut()`, so this `unwrap` will not // panic. let ptr = Ptr::from_mut(b.deref_mut()) - .try_cast_into_no_leftover::(None) + .try_cast_into_no_leftover(None) .expect("zerocopy internal error: DerefMut::deref_mut should be infallible"); - let ptr = ptr.recall_validity::<_, (_, (_, (BecauseExclusive, BecauseExclusive)))>(); + let ptr = ptr.recall_validity::<_, BecauseBidirectional>(); ptr.as_mut() } } diff --git a/src/util/macro_util.rs b/src/util/macro_util.rs index affa65c044..81ac0a0f26 100644 --- a/src/util/macro_util.rs +++ b/src/util/macro_util.rs @@ -30,8 +30,8 @@ use core::ptr::{self, NonNull}; use crate::{ pointer::{ - invariant::{self, BecauseExclusive, BecauseImmutable, Invariants}, - BecauseInvariantsEq, InvariantsEq, SizeEq, TryTransmuteFromPtr, + invariant::{self, BecauseImmutable, Invariants}, + BecauseBidirectional, InvariantsEq, MutationCompatible, SizeFrom, TryTransmuteFromPtr, }, FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout, Ptr, TryFromBytes, ValidityError, }; @@ -493,7 +493,7 @@ macro_rules! assert_size_eq { /// [`is_bit_valid`]: TryFromBytes::is_bit_valid #[doc(hidden)] #[inline] -fn try_cast_or_pme( +fn try_cast_or_pme( src: Ptr<'_, Src, I>, ) -> Result< Ptr<'_, Dst, (I::Aliasing, invariant::Unaligned, invariant::Valid)>, @@ -502,10 +502,10 @@ fn try_cast_or_pme( where // FIXME(#2226): There should be a `Src: FromBytes` bound here, but doing so // requires deeper surgery. - Src: invariant::Read, + Src: MutationCompatible, Dst: TryFromBytes - + invariant::Read - + TryTransmuteFromPtr, + + MutationCompatible + + TryTransmuteFromPtr, I: Invariants, I::Aliasing: invariant::Reference, { @@ -620,7 +620,7 @@ where { let ptr = Ptr::from_ref(src); let ptr = ptr.bikeshed_recall_initialized_immutable(); - match try_cast_or_pme::(ptr) { + match try_cast_or_pme::(ptr) { Ok(ptr) => { static_assert!(Src, Dst => mem::align_of::() <= mem::align_of::()); // SAFETY: We have checked that `Dst` does not have a stricter @@ -664,7 +664,7 @@ where { let ptr = Ptr::from_mut(src); let ptr = ptr.bikeshed_recall_initialized_from_bytes(); - match try_cast_or_pme::(ptr) { + match try_cast_or_pme::(ptr) { Ok(ptr) => { static_assert!(Src, Dst => mem::align_of::() <= mem::align_of::()); // SAFETY: We have checked that `Dst` does not have a stricter @@ -673,7 +673,7 @@ where Ok(ptr.as_mut()) } Err(err) => { - Err(err.map_src(|ptr| ptr.recall_validity::<_, (_, BecauseInvariantsEq)>().as_mut())) + Err(err.map_src(|ptr| ptr.recall_validity::<_, BecauseBidirectional>().as_mut())) } } } @@ -793,11 +793,11 @@ where // SAFETY: We only use `S` as `S` and `D` as `D`. unsafe { - unsafe_with_size_eq!(, D> { + unsafe_with_size_from!(, D> { let ptr = Ptr::from_ref(self.0) - .transmute::, invariant::Valid, BecauseImmutable>() + .transmute::, invariant::Valid, _>() .recall_validity::() - .transmute::, invariant::Initialized, (crate::pointer::BecauseMutationCompatible, _)>() + .transmute::, invariant::Initialized, _>() .recall_validity::(); #[allow(unused_unsafe)] @@ -833,12 +833,12 @@ where // SAFETY: We only use `S` as `S` and `D` as `D`. unsafe { - unsafe_with_size_eq!(, D> { + unsafe_with_size_from!(, D> { let ptr = Ptr::from_mut(self.0) .transmute::, invariant::Valid, _>() - .recall_validity::() + .recall_validity::() .transmute::, invariant::Initialized, _>() - .recall_validity::(); + .recall_validity::(); #[allow(unused_unsafe)] // SAFETY: The preceding `static_assert!` ensures that diff --git a/src/util/macros.rs b/src/util/macros.rs index 35211bdb74..a790262c22 100644 --- a/src/util/macros.rs +++ b/src/util/macros.rs @@ -220,7 +220,7 @@ macro_rules! impl_for_transmute_from { #[inline] fn is_bit_valid(candidate: Maybe<'_, Self, A>) -> bool { let c: Maybe<'_, Self, crate::pointer::invariant::Exclusive> = candidate.into_exclusive_or_pme(); - let c: Maybe<'_, $repr, _> = c.transmute::<_, _, (_, (_, (BecauseExclusive, BecauseExclusive)))>(); + let c: Maybe<'_, $repr, _> = c.transmute(); // 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`. @@ -702,34 +702,42 @@ macro_rules! cast { }}; } -/// Implements `TransmuteFrom` and `SizeEq` for `T` and `$wrapper`. +/// Implements `TransmuteFrom` and `SizeFrom` for `T` and `$wrapper`. /// /// # Safety /// -/// `T` and `$wrapper` must have the same bit validity, and must have the -/// same size in the sense of `SizeEq`. +/// `T` and `$wrapper` must have the same bit validity, and must satisfy the +/// following property: given `t: *mut T`, `let w = t as *mut $wrapper` +/// produces a pointer which address the same bytes as `t`. The inverse must +/// also hold. macro_rules! unsafe_impl_for_transparent_wrapper { (T $(: ?$optbound:ident)? => $wrapper:ident) => {{ crate::util::macros::__unsafe(); - use crate::pointer::{TransmuteFrom, PtrInner, SizeEq, invariant::Valid}; + use crate::pointer::{TransmuteFrom, TransmuteOverwrite, PtrInner, SizeFrom, invariant::Valid}; // SAFETY: The caller promises that `T` and `$wrapper` have the same - // bit validity. + // bit validity, and that both implementations of + // `SizeFrom::cast_from_raw` preserve referent size exactly. unsafe impl TransmuteFrom for $wrapper {} // SAFETY: See previous safety comment. unsafe impl TransmuteFrom<$wrapper, Valid, Valid> for T {} + // SAFETY: See previous safety comment. + unsafe impl TransmuteOverwrite for $wrapper {} + // SAFETY: See previous safety comment. + unsafe impl TransmuteOverwrite<$wrapper, Valid, Valid> for T {} // SAFETY: The caller promises that `T` and `$wrapper` satisfy - // `SizeEq`. - unsafe impl SizeEq for $wrapper { + // `cast_from_raw`'s safety post-condition. + unsafe impl SizeFrom for $wrapper { #[inline(always)] fn cast_from_raw(t: PtrInner<'_, T>) -> PtrInner<'_, $wrapper> { - // SAFETY: See previous safety comment. + // SAFETY: The caller promises that this cast produces a pointer + // which addresses the same bytes as `t`. unsafe { cast!(t) } } } // SAFETY: See previous safety comment. - unsafe impl SizeEq<$wrapper> for T { + unsafe impl SizeFrom<$wrapper> for T { #[inline(always)] fn cast_from_raw(t: PtrInner<'_, $wrapper>) -> PtrInner<'_, T> { // SAFETY: See previous safety comment. @@ -742,19 +750,20 @@ 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 crate::pointer::{TransmuteFrom, PtrInner, SizeEq, invariant::Valid}; + use crate::pointer::{TransmuteFrom, SizeFrom, invariant::Valid}; - // SAFETY: Since `$u: SizeEq<$t>` and `$v: SizeEq`, this impl is - // transitively sound. - unsafe impl<$($tyvar $(: ?$optbound)?)?> SizeEq<$t> for $v + // SAFETY: See safety comment on `cast_from_raw`. + unsafe impl<$($tyvar $(: ?$optbound)?)?> SizeFrom<$t> for $v where - $u: SizeEq<$t>, - $v: SizeEq<$u>, + $u: SizeFrom<$t>, + $v: SizeFrom<$u>, { + // SAFETY: Each inner call to `cast_from_raw` preserves + // provenance and addressed byte range. #[inline(always)] fn cast_from_raw(t: PtrInner<'_, $t>) -> PtrInner<'_, $v> { - let u = <$u as SizeEq<_>>::cast_from_raw(t); - <$v as SizeEq<_>>::cast_from_raw(u) + let u = SizeFrom::cast_from_raw(t); + SizeFrom::cast_from_raw(u) } } @@ -772,10 +781,10 @@ macro_rules! impl_transitive_transmute_from { } #[rustfmt::skip] -macro_rules! impl_size_eq { +macro_rules! impl_size_from { ($t:ty, $u:ty) => { const _: () = { - use crate::{KnownLayout, pointer::{PtrInner, SizeEq}}; + use crate::{KnownLayout, pointer::{PtrInner, SizeFrom}}; static_assert!(=> { let t = <$t as KnownLayout>::LAYOUT; @@ -791,7 +800,7 @@ macro_rules! impl_size_eq { }); // SAFETY: See inline. - unsafe impl SizeEq<$t> for $u { + unsafe impl SizeFrom<$t> for $u { #[inline(always)] fn cast_from_raw(t: PtrInner<'_, $t>) -> PtrInner<'_, $u> { // SAFETY: We've asserted that their @@ -802,7 +811,7 @@ macro_rules! impl_size_eq { } } // SAFETY: See previous safety comment. - unsafe impl SizeEq<$u> for $t { + unsafe impl SizeFrom<$u> for $t { #[inline(always)] fn cast_from_raw(u: PtrInner<'_, $u>) -> PtrInner<'_, $t> { // SAFETY: See previous safety comment. @@ -814,17 +823,17 @@ macro_rules! impl_size_eq { } /// Invokes `$blk` in a context in which `$src<$t>` and `$dst<$u>` implement -/// `SizeEq`. +/// `SizeFrom`. /// -/// This macro emits code which implements `SizeEq`, and ensures that the impl -/// is sound via PME. +/// This macro emits code which implements `SizeComapt`, and ensures that the +/// impl is sound via PME. /// /// # Safety /// /// Inside of `$blk`, the caller must only use `$src` and `$dst` as `$src<$t>` /// and `$dst<$u>`. The caller must not use `$src` or `$dst` to wrap any other /// types. -macro_rules! unsafe_with_size_eq { +macro_rules! unsafe_with_size_from { (<$src:ident<$t:ident>, $dst:ident<$u:ident>> $blk:expr) => {{ crate::util::macros::__unsafe(); @@ -859,14 +868,14 @@ macro_rules! unsafe_with_size_eq { // We manually instantiate `cast_from_raw` 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. - unsafe impl SizeEq<$src> for $dst + unsafe impl SizeFrom<$src> for $dst where T: KnownLayout, U: KnownLayout, { fn cast_from_raw(src: PtrInner<'_, $src>) -> PtrInner<'_, Self> { // SAFETY: `crate::layout::cast_from_raw` promises to satisfy - // the safety invariants of `SizeEq::cast_from_raw`, or to + // the safety invariants of `SizeFrom::cast_from_raw`, or to // generate a PME. Since `$src` and `$dst` are // `#[repr(transparent)]` wrappers around `T` and `U` // respectively, a `cast_from_raw` impl which satisfies the @@ -887,14 +896,12 @@ macro_rules! unsafe_with_size_eq { // See safety comment on the preceding `unsafe impl` block for an // explanation of why we need this block. if 1 == 0 { - let ptr = <$t as KnownLayout>::raw_dangling(); - #[allow(unused_unsafe)] - // SAFETY: This call is never executed. - let ptr = unsafe { crate::pointer::PtrInner::new(ptr) }; - #[allow(unused_unsafe)] - // SAFETY: This call is never executed. - let ptr = unsafe { cast!(ptr) }; - let _ = <$dst<$u> as SizeEq<$src<$t>>>::cast_from_raw(ptr); + use crate::pointer::PtrInner; + + #[allow(invalid_value, unused_unsafe)] + // SAFETY: This code is never executed. + let ptr: PtrInner<'_, $src<$t>> = unsafe { core::mem::MaybeUninit::uninit().assume_init() }; + let _ = <$dst<$u> as SizeFrom<$src<$t>>>::cast_from_raw(ptr); } impl_for_transmute_from!(T: ?Sized + TryFromBytes => TryFromBytes for $src[]); diff --git a/src/wrappers.rs b/src/wrappers.rs index 6d7891354a..9d3a7457be 100644 --- a/src/wrappers.rs +++ b/src/wrappers.rs @@ -200,7 +200,7 @@ impl Unalign { /// callers may prefer [`DerefMut::deref_mut`], which is infallible. #[inline(always)] pub fn try_deref_mut(&mut self) -> Result<&mut T, AlignmentError<&mut Self, T>> { - let inner = Ptr::from_mut(self).transmute::<_, _, (_, (_, _))>(); + let inner = Ptr::from_mut(self).transmute::<_, _, BecauseBidirectional>(); match inner.try_into_aligned() { Ok(aligned) => Ok(aligned.as_mut()), Err(err) => Err(err.map_src(|src| src.into_unalign().as_mut())), @@ -396,7 +396,10 @@ impl Deref for Unalign { impl DerefMut for Unalign { #[inline(always)] fn deref_mut(&mut self) -> &mut T { - Ptr::from_mut(self).transmute::<_, _, (_, (_, _))>().bikeshed_recall_aligned().as_mut() + Ptr::from_mut(self) + .transmute::<_, _, BecauseBidirectional>() + .bikeshed_recall_aligned() + .as_mut() } } diff --git a/zerocopy-derive/src/enum.rs b/zerocopy-derive/src/enum.rs index 50e632d383..8de833602d 100644 --- a/zerocopy-derive/src/enum.rs +++ b/zerocopy-derive/src/enum.rs @@ -332,7 +332,7 @@ pub(crate) fn derive_is_bit_valid( // is `Initialized`. Since we have not written uninitialized // bytes into the referent, `tag_ptr` is also `Initialized`. let tag_ptr = unsafe { tag_ptr.assume_initialized() }; - tag_ptr.recall_validity::<_, (_, (_, _))>().read_unaligned::<#zerocopy_crate::BecauseImmutable>() + tag_ptr.recall_validity().read_unaligned::<#zerocopy_crate::BecauseImmutable>() }; // SAFETY: diff --git a/zerocopy-derive/src/output_tests.rs b/zerocopy-derive/src/output_tests.rs index eb991c5b23..a6ffe89606 100644 --- a/zerocopy-derive/src/output_tests.rs +++ b/zerocopy-derive/src/output_tests.rs @@ -799,7 +799,7 @@ fn test_try_from_bytes_enum() { candidate.reborrow().cast_unsized_unchecked(|p: ::zerocopy::pointer::PtrInner<'_, Self>| { p.cast_sized::<___ZerocopyTagPrimitive>() }) }; let tag_ptr = unsafe { tag_ptr.assume_initialized() }; - tag_ptr.recall_validity::<_, (_, (_, _))>().read_unaligned::<::zerocopy::BecauseImmutable>() + tag_ptr.recall_validity().read_unaligned::<::zerocopy::BecauseImmutable>() }; let raw_enum = unsafe { candidate.cast_unsized_unchecked(|p: ::zerocopy::pointer::PtrInner<'_, Self>| { p.cast_sized::<___ZerocopyRawEnum<'a, N, X, Y>>() }) @@ -1140,7 +1140,7 @@ fn test_try_from_bytes_enum() { candidate.reborrow().cast_unsized_unchecked(|p: ::zerocopy::pointer::PtrInner<'_, Self>| { p.cast_sized::<___ZerocopyTagPrimitive> ()}) }; let tag_ptr = unsafe { tag_ptr.assume_initialized() }; - tag_ptr.recall_validity::<_, (_, (_, _))>().read_unaligned::<::zerocopy::BecauseImmutable>() + tag_ptr.recall_validity().read_unaligned::<::zerocopy::BecauseImmutable>() }; let raw_enum = unsafe { candidate.cast_unsized_unchecked(|p: ::zerocopy::pointer::PtrInner<'_, Self>| { p.cast_sized::<___ZerocopyRawEnum<'a, N, X, Y>> ()}) @@ -1481,7 +1481,7 @@ fn test_try_from_bytes_enum() { candidate.reborrow().cast_unsized_unchecked(|p: ::zerocopy::pointer::PtrInner<'_, Self>| { p.cast_sized::<___ZerocopyTagPrimitive> ()}) }; let tag_ptr = unsafe { tag_ptr.assume_initialized() }; - tag_ptr.recall_validity::<_, (_, (_, _))>().read_unaligned::<::zerocopy::BecauseImmutable>() + tag_ptr.recall_validity().read_unaligned::<::zerocopy::BecauseImmutable>() }; let raw_enum = unsafe { candidate.cast_unsized_unchecked(|p: ::zerocopy::pointer::PtrInner<'_, Self>| { p.cast_sized::<___ZerocopyRawEnum<'a, N, X, Y>> ()})