Skip to content

Commit

Permalink
Introduce invariant::Initialized and migrate Maybe to it
Browse files Browse the repository at this point in the history
This opens the door to implementing `TryFromBytes<Option<&T>>`,
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.
  • Loading branch information
jswrenn committed Feb 8, 2024
1 parent 9890e55 commit 837a67e
Show file tree
Hide file tree
Showing 5 changed files with 23 additions and 22 deletions.
10 changes: 4 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1253,10 +1253,9 @@ pub unsafe trait TryFromBytes {
let candidate = Ptr::from_ref(bytes).try_cast_into_no_leftover::<Self>()?;

// 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::<crate::pointer::invariant::AsInitialized>() };
unsafe { candidate.assume_validity::<crate::pointer::invariant::Initialized>() };

// 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
Expand Down Expand Up @@ -1284,9 +1283,8 @@ pub unsafe trait TryFromBytes {
let candidate = MaybeUninit::<Self>::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::<crate::pointer::invariant::Initialized>() };

if !Self::is_bit_valid(c_ptr.forget_aligned()) {
return None;
Expand Down
7 changes: 4 additions & 3 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<crate::pointer::invariant::AsInitialized>() };
// 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::<crate::pointer::invariant::Initialized>() };

$is_bit_valid
}
Expand Down
2 changes: 1 addition & 1 deletion src/pointer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 6 additions & 4 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down Expand Up @@ -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) }
}
Expand Down Expand Up @@ -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<Validity = invariant::Initialized>,
{
/// Projects a field from `self`.
///
Expand All @@ -888,7 +890,7 @@ mod _project {
pub unsafe fn project<U: 'a + ?Sized>(
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());
Expand Down
16 changes: 8 additions & 8 deletions zerocopy-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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::<Self>()]`
// 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::<Self>()]) };
// 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();

Expand Down

0 comments on commit 837a67e

Please sign in to comment.