Skip to content

Commit

Permalink
[pointer] Support generic invariant mapping
Browse files Browse the repository at this point in the history
This commit adds a framework which supports encoding in the type system
any `I -> I` mapping where `I` is any `Invariant` type. This permits us
to make `cast_unsized`'s return value smarter, and as a result, allows
us to remove a lot of `unsafe` code.

Makes progress on #1122
  • Loading branch information
joshlf committed Oct 13, 2024
1 parent 7c245c5 commit edeaf45
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 69 deletions.
9 changes: 0 additions & 9 deletions src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,15 +290,6 @@ safety_comment! {
/// because `NonZeroXxx` and `xxx` have the same size. [1] Neither `r`
/// nor `t` refer to any `UnsafeCell`s because neither `NonZeroXxx` [2]
/// nor `xxx` do.
/// - Since the closure takes a `&xxx` argument, given a `Maybe<'a,
/// NonZeroXxx>` which satisfies the preconditions of
/// `TryFromBytes::<NonZeroXxx>::is_bit_valid`, it must be guaranteed
/// that the memory referenced by that `MabyeValid` always contains a
/// valid `xxx`. Since `NonZeroXxx`'s bytes are always initialized [1],
/// `is_bit_valid`'s precondition requires that the same is true of its
/// argument. Since `xxx`'s only bit validity invariant is that its
/// bytes must be initialized, this memory is guaranteed to contain a
/// valid `xxx`.
/// - The impl must only return `true` for its argument if the original
/// `Maybe<NonZeroXxx>` refers to a valid `NonZeroXxx`. The only
/// `xxx` which is not also a valid `NonZeroXxx` is 0. [1]
Expand Down
136 changes: 113 additions & 23 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,22 @@ pub mod invariant {
/// Aliasing>::Variance<'a, T>` to inherit this variance.
#[doc(hidden)]
type Variance<'a, T: 'a + ?Sized>;

#[doc(hidden)]
type MappedTo<M: AliasingMapping>: Aliasing;
}

/// The alignment invariant of a [`Ptr`][super::Ptr].
pub trait Alignment: Sealed {}
pub trait Alignment: Sealed {
#[doc(hidden)]
type MappedTo<M: AlignmentMapping>: Alignment;
}

/// The validity invariant of a [`Ptr`][super::Ptr].
pub trait Validity: Sealed {}
pub trait Validity: Sealed {
#[doc(hidden)]
type MappedTo<M: ValidityMapping>: Validity;
}

/// An [`Aliasing`] invariant which is either [`Shared`] or [`Exclusive`].
///
Expand All @@ -217,9 +226,15 @@ pub mod invariant {
//
// [1] https://doc.rust-lang.org/1.81.0/reference/subtyping.html#variance
type Variance<'a, T: 'a + ?Sized> = fn(&'a T) -> &'a T;

type MappedTo<M: AliasingMapping> = M::FromAny;
}
impl Alignment for Any {
type MappedTo<M: AlignmentMapping> = M::FromAny;
}
impl Validity for Any {
type MappedTo<M: ValidityMapping> = M::FromAny;
}
impl Alignment for Any {}
impl Validity for Any {}

/// The `Ptr<'a, T>` adheres to the aliasing rules of a `&'a T`.
///
Expand All @@ -234,6 +249,7 @@ pub mod invariant {
impl Aliasing for Shared {
const IS_EXCLUSIVE: bool = false;
type Variance<'a, T: 'a + ?Sized> = &'a T;
type MappedTo<M: AliasingMapping> = M::FromShared;
}
impl Reference for Shared {}

Expand All @@ -246,13 +262,16 @@ pub mod invariant {
impl Aliasing for Exclusive {
const IS_EXCLUSIVE: bool = true;
type Variance<'a, T: 'a + ?Sized> = &'a mut T;
type MappedTo<M: AliasingMapping> = M::FromExclusive;
}
impl Reference for Exclusive {}

/// The referent is aligned: for `Ptr<T>`, the referent's address is a
/// multiple of the `T`'s alignment.
pub enum Aligned {}
impl Alignment for Aligned {}
impl Alignment for Aligned {
type MappedTo<M: AlignmentMapping> = M::FromAligned;
}

/// The byte ranges initialized in `T` are also initialized in the referent.
///
Expand Down Expand Up @@ -281,17 +300,23 @@ pub mod invariant {
/// may contain another enum type, in which case the same rules apply
/// depending on the state of its discriminant, and so on recursively).
pub enum AsInitialized {}
impl Validity for AsInitialized {}
impl Validity for AsInitialized {
type MappedTo<M: ValidityMapping> = M::FromAsInitialized;
}

/// The byte ranges in the referent are fully initialized. In other words,
/// if the referent is `N` bytes long, then it contains a bit-valid `[u8;
/// N]`.
pub enum Initialized {}
impl Validity for Initialized {}
impl Validity for Initialized {
type MappedTo<M: ValidityMapping> = M::FromInitialized;
}

/// The referent is bit-valid for `T`.
pub enum Valid {}
impl Validity for Valid {}
impl Validity for Valid {
type MappedTo<M: ValidityMapping> = M::FromValid;
}

use sealed::Sealed;
mod sealed {
Expand All @@ -312,6 +337,72 @@ pub mod invariant {

impl<A: Sealed, AA: Sealed, V: Sealed> Sealed for (A, AA, V) {}
}

pub use mapping::*;
mod mapping {
use super::*;

pub trait AliasingMapping {
type FromAny: Aliasing;
type FromShared: Aliasing;
type FromExclusive: Aliasing;
}

pub trait AlignmentMapping {
type FromAny: Alignment;
type FromAligned: Alignment;
}

pub trait ValidityMapping {
type FromAny: Validity;
type FromAsInitialized: Validity;
type FromInitialized: Validity;
type FromValid: Validity;
}

#[allow(type_alias_bounds)]
pub type MappedAliasing<I: Aliasing, M: AliasingMapping> = I::MappedTo<M>;

#[allow(type_alias_bounds)]
pub type MappedAlignment<I: Alignment, M: AlignmentMapping> = I::MappedTo<M>;

#[allow(type_alias_bounds)]
pub type MappedValidity<I: Validity, M: ValidityMapping> = I::MappedTo<M>;

impl<FromAny: Aliasing, FromShared: Aliasing, FromExclusive: Aliasing> AliasingMapping
for ((Any, FromAny), (Shared, FromShared), (Exclusive, FromExclusive))
{
type FromAny = FromAny;
type FromShared = FromShared;
type FromExclusive = FromExclusive;
}

impl<FromAny: Alignment, FromAligned: Alignment> AlignmentMapping
for ((Any, FromAny), (Shared, FromAligned))
{
type FromAny = FromAny;
type FromAligned = FromAligned;
}

impl<
FromAny: Validity,
FromAsInitialized: Validity,
FromInitialized: Validity,
FromValid: Validity,
> ValidityMapping
for (
(Any, FromAny),
(AsInitialized, FromAsInitialized),
(Initialized, FromInitialized),
(Valid, FromValid),
)
{
type FromAny = FromAny;
type FromAsInitialized = FromAsInitialized;
type FromInitialized = FromInitialized;
type FromValid = FromValid;
}
}
}

pub(crate) use invariant::*;
Expand Down Expand Up @@ -911,7 +1002,18 @@ mod _casts {
pub unsafe fn cast_unsized<U: 'a + ?Sized, F: FnOnce(*mut T) -> *mut U>(
self,
cast: F,
) -> Ptr<'a, U, (I::Aliasing, Any, Any)> {
) -> Ptr<
'a,
U,
(
I::Aliasing,
Any,
MappedValidity<
I::Validity,
((Any, Any), (AsInitialized, Any), (Initialized, Initialized), (Valid, Any)),
>,
),
> {
let ptr = cast(self.as_inner().as_non_null().as_ptr());

// SAFETY: Caller promises that `cast` returns a pointer whose
Expand Down Expand Up @@ -1010,14 +1112,7 @@ mod _casts {
})
};

let ptr = ptr.bikeshed_recall_aligned();

// SAFETY: `ptr`'s referent begins as `Initialized`, denoting that
// all bytes of the referent are initialized bytes. The referent
// type is then casted to `[u8]`, whose only validity invariant is
// that its bytes are initialized. This validity invariant is
// satisfied by the `Initialized` invariant on the starting `ptr`.
unsafe { ptr.assume_validity::<Valid>() }
ptr.bikeshed_recall_aligned().bikeshed_recall_valid()
}
}

Expand Down Expand Up @@ -1250,12 +1345,7 @@ mod _project {

// SAFETY: This method has the same safety preconditions as
// `cast_unsized`.
let ptr = unsafe { self.cast_unsized(projector) };

// SAFETY: If all of the bytes of `self` are initialized (as
// promised by `I: Invariants<Validity = Initialized>`), then any
// subset of those bytes are also all initialized.
unsafe { ptr.assume_validity::<Initialized>() }
unsafe { self.cast_unsized(projector) }
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/util/macro_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ macro_rules! assert_align_gt_eq {
#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
#[macro_export]
macro_rules! assert_size_eq {
($t:ident, $u: ident) => {{
($t:ident, $u:ident) => {{
// The comments here should be read in the context of this macro's
// invocations in `transmute_ref!` and `transmute_mut!`.
if false {
Expand Down
14 changes: 1 addition & 13 deletions src/util/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,6 @@ macro_rules! safety_comment {
/// referred to by `t`.
/// - `r` refers to an object with `UnsafeCell`s at the same byte ranges as
/// the object referred to by `t`.
/// - If the provided closure takes a `&$repr` argument, then given a `Ptr<'a,
/// $ty>` which satisfies the preconditions of
/// `TryFromBytes::<$ty>::is_bit_valid`, it must be guaranteed that the
/// memory referenced by that `Ptr` always contains a valid `$repr`.
/// - The impl of `is_bit_valid` must only return `true` for its argument
/// `Ptr<$repr>` if the original `Ptr<$ty>` refers to a valid `$ty`.
macro_rules! unsafe_impl {
Expand Down Expand Up @@ -153,9 +149,7 @@ 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 the referenced memory region
// will contain a valid `$repr`.
let $candidate = unsafe { candidate.assume_validity::<crate::pointer::invariant::Valid>() };
let $candidate = candidate.bikeshed_recall_valid();
$is_bit_valid
}
};
Expand All @@ -176,12 +170,6 @@ macro_rules! unsafe_impl {
// `UnsafeCell`s at the same byte ranges as the source type.
#[allow(clippy::as_conversions)]
let $candidate = unsafe { candidate.cast_unsized::<$repr, _>(|p| p as *mut _) };

// 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
11 changes: 0 additions & 11 deletions zerocopy-derive/src/enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,10 +265,6 @@ pub(crate) fn derive_is_bit_valid(
}
)
};
// SAFETY: `cast_unsized` removes the initialization
// invariant from `p`, so we re-assert that all of the bytes
// are initialized.
let variant = unsafe { variant.assume_initialized() };
<
#variant_struct_ident #ty_generics as #trait_path
>::is_bit_valid(variant)
Expand Down Expand Up @@ -325,10 +321,6 @@ pub(crate) fn derive_is_bit_valid(
p as *mut ___ZerocopyTagPrimitive
})
};
// SAFETY: `tag_ptr` is casted from `candidate`, whose referent
// 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.bikeshed_recall_valid().read_unaligned::<::zerocopy::BecauseImmutable>()
};

Expand All @@ -347,9 +339,6 @@ pub(crate) fn derive_is_bit_valid(
p as *mut ___ZerocopyRawEnum #ty_generics
})
};
// SAFETY: `cast_unsized` removes the initialization invariant from
// `p`, so we re-assert that all of the bytes are initialized.
let raw_enum = unsafe { raw_enum.assume_initialized() };
// SAFETY:
// - This projection returns a subfield of `this` using
// `addr_of_mut!`.
Expand Down
Loading

0 comments on commit edeaf45

Please sign in to comment.