From 837a67e7377555b59c7f393e960be9664b5e1b58 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Thu, 8 Feb 2024 18:01:55 +0000 Subject: [PATCH] Introduce `invariant::Initialized` and migrate `Maybe` to it This opens the door to implementing `TryFromBytes>`, which is currently blocked on us needing to inspect whether the referent bytes are zeroed. We couldn't do that previously, since `AsInitialized` leaves the possibility for uninitialized bytes, which cannot be soundly checked for equality. --- src/lib.rs | 10 ++++------ src/macros.rs | 7 ++++--- src/pointer/mod.rs | 2 +- src/pointer/ptr.rs | 10 ++++++---- zerocopy-derive/src/lib.rs | 16 ++++++++-------- 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 43246b6548..7d12522f4c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1253,10 +1253,9 @@ pub unsafe trait TryFromBytes { let candidate = Ptr::from_ref(bytes).try_cast_into_no_leftover::()?; // SAFETY: `candidate` has no uninitialized sub-ranges because it - // derived from `bytes: &[u8]`, and is therefore at least as-initialized - // as `Self`. + // derived from `bytes: &[u8]`. let candidate = - unsafe { candidate.assume_validity::() }; + unsafe { candidate.assume_validity::() }; // 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 @@ -1284,9 +1283,8 @@ pub unsafe trait TryFromBytes { let candidate = MaybeUninit::::read_from(bytes)?; let c_ptr = Ptr::from_maybe_uninit_ref(&candidate); // SAFETY: `c_ptr` has no uninitialized sub-ranges because it derived - // from `candidate`, which in turn derives from `bytes: &[u8]`, and is - // therefore at least as-initialized as `Self`. - let c_ptr = unsafe { c_ptr.assume_as_initialized() }; + // from `candidate`, which in turn derives from `bytes: &[u8]`. + let c_ptr = unsafe { c_ptr.assume_validity::() }; if !Self::is_bit_valid(c_ptr.forget_aligned()) { return None; diff --git a/src/macros.rs b/src/macros.rs index e14920534a..f1f115cc87 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -164,9 +164,10 @@ macro_rules! unsafe_impl { #[allow(clippy::as_conversions)] let $candidate = unsafe { candidate.cast_unsized::<$repr, _>(|p| p as *mut _) }; - // SAFETY: The caller has promised that `$repr` is as-initialized as - // `Self`. - let $candidate = unsafe { $candidate.assume_validity::() }; + // Restore the invariant that the referent bytes are initialized. + // SAFETY: The above cast does not uninitialize any referent bytes; + // they remain initialized. + let $candidate = unsafe { $candidate.assume_validity::() }; $is_bit_valid } diff --git a/src/pointer/mod.rs b/src/pointer/mod.rs index 34f1d204bf..88d7f1e0dd 100644 --- a/src/pointer/mod.rs +++ b/src/pointer/mod.rs @@ -17,7 +17,7 @@ use crate::{TryFromBytes, Unaligned}; /// A shorthand for a maybe-valid, maybe-aligned reference. Used as the argument /// to [`TryFromBytes::is_bit_valid`]. pub type Maybe<'a, T, Alignment = invariant::AnyAlignment> = - Ptr<'a, T, (invariant::Shared, Alignment, invariant::AsInitialized)>; + Ptr<'a, T, (invariant::Shared, Alignment, invariant::Initialized)>; // These methods are defined on the type alias, `Maybe`, so as to bring them to // the forefront of the rendered rustdoc. diff --git a/src/pointer/ptr.rs b/src/pointer/ptr.rs index 0aeebf5a83..eeefd4a2af 100644 --- a/src/pointer/ptr.rs +++ b/src/pointer/ptr.rs @@ -319,6 +319,9 @@ pub mod invariant { /// discriminant, and so on recursively). AsInitialized, + /// The byte ranges in the referent are fully initialized. + Initialized, + /// The referent is bit-valid for `T`. Valid, } @@ -600,7 +603,7 @@ mod _transitions { #[inline] pub fn forget_valid( self, - ) -> Ptr<'a, T, (I::Aliasing, I::Alignment, invariant::AsInitialized)> { + ) -> Ptr<'a, T, (I::Aliasing, I::Alignment, invariant::Initialized)> { // SAFETY: `AnyValidity` is less restrictive than `Valid`. unsafe { Ptr::from_ptr(self) } } @@ -864,8 +867,7 @@ mod _project { impl<'a, T, I> Ptr<'a, T, I> where T: 'a + ?Sized, - I: Invariants, - I::Validity: invariant::at_least::AsInitialized, + I: Invariants, { /// Projects a field from `self`. /// @@ -888,7 +890,7 @@ mod _project { pub unsafe fn project( self, projector: impl FnOnce(*mut T) -> *mut U, - ) -> Ptr<'a, U, (I::Aliasing, invariant::AnyAlignment, invariant::AsInitialized)> { + ) -> Ptr<'a, U, (I::Aliasing, invariant::AnyAlignment, invariant::Initialized)> { // SAFETY: `projector` is provided with `self` casted to a raw // pointer. let field = projector(self.as_non_null().as_ptr()); diff --git a/zerocopy-derive/src/lib.rs b/zerocopy-derive/src/lib.rs index c3ef3d8e7b..0fc895e7b1 100644 --- a/zerocopy-derive/src/lib.rs +++ b/zerocopy-derive/src/lib.rs @@ -420,7 +420,7 @@ fn derive_try_from_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2: ( ::zerocopy::pointer::invariant::Shared, ::zerocopy::pointer::invariant::AnyAlignment, - ::zerocopy::pointer::invariant::AsInitialized, + ::zerocopy::pointer::invariant::Initialized, ), >, ) -> ::zerocopy::macro_util::core_reexport::primitive::bool { @@ -430,14 +430,14 @@ fn derive_try_from_bytes_enum(ast: &DeriveInput, enm: &DataEnum) -> proc_macro2: // - By definition, `*mut Self` and `*mut [u8; size_of::()]` // are types of the same size. let discriminant = unsafe { candidate.cast_unsized(|p: *mut Self| p as *mut [core_reexport::primitive::u8; core_reexport::mem::size_of::()]) }; - // SAFETY: Since `candidate` has the invariant `AsInitialized`, we + // SAFETY: Since `candidate` has the invariant `Initialized`, we // know that `candidate`'s referent (and thus `discriminant`'s - // referent) is as-initialized as `Self`. Since all of the allowed - // `repr`s are types for which all bytes are always initialized, we - // know that `discriminant`'s referent has all of its bytes - // initialized. Since `[u8; N]`'s validity invariant is just that - // all of its bytes are initialized, we know that `discriminant`'s - // referent is bit-valid. + // referent) are fully initialized. Since all of the allowed `repr`s + // are types for which all bytes are always initialized, we know + // that `discriminant`'s referent has all of its bytes initialized. + // Since `[u8; N]`'s validity invariant is just that all of its + // bytes are initialized, we know that `discriminant`'s referent is + // bit-valid. let discriminant = unsafe { discriminant.assume_valid() }; let discriminant = discriminant.read_unaligned();