Skip to content

[pointer] Support generic invariant mapping #1896

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
193 changes: 163 additions & 30 deletions src/pointer/invariant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,22 @@ pub trait Aliasing: Sealed {
/// 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 @@ -84,8 +93,12 @@ pub trait Reference: Aliasing + Sealed {}
/// It is unknown whether any invariant holds.
pub enum Unknown {}

impl Alignment for Unknown {}
impl Validity for Unknown {}
impl Alignment for Unknown {
type MappedTo<M: AlignmentMapping> = M::FromUnknown;
}
impl Validity for Unknown {
type MappedTo<M: ValidityMapping> = M::FromUnknown;
}

/// The `Ptr<'a, T>` does not permit any reads or writes from or to its referent.
pub enum Inaccessible {}
Expand All @@ -100,6 +113,7 @@ impl Aliasing for Inaccessible {
//
// [1] https://doc.rust-lang.org/1.81.0/reference/subtyping.html#variance
type Variance<'a, T: 'a + ?Sized> = &'a T;
type MappedTo<M: AliasingMapping> = M::FromInaccessible;
}

/// The `Ptr<'a, T>` adheres to the aliasing rules of a `&'a T`.
Expand All @@ -114,6 +128,7 @@ pub enum Shared {}
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 @@ -126,51 +141,60 @@ pub enum Exclusive {}
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.
/// 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.
///
/// Formally: uninitialized bytes may only be present in `Ptr<T>`'s referent
/// where they are guaranteed to be present in `T`. This is a dynamic
/// property: if, at a particular byte offset, a valid enum discriminant is
/// set, the subsequent bytes may only have uninitialized bytes as
/// specificed by the corresponding enum.
/// where they are guaranteed to be present in `T`. This is a dynamic property:
/// if, at a particular byte offset, a valid enum discriminant is set, the
/// subsequent bytes may only have uninitialized bytes as specificed by the
/// corresponding enum.
///
/// Formally, given `len = size_of_val_raw(ptr)`, at every byte offset, `b`,
/// in the range `[0, len)`:
/// - If, in any instance `t: T` of length `len`, the byte at offset `b` in
/// `t` is initialized, then the byte at offset `b` within `*ptr` must be
/// Formally, given `len = size_of_val_raw(ptr)`, at every byte offset, `b`, in
/// the range `[0, len)`:
/// - If, in any instance `t: T` of length `len`, the byte at offset `b` in `t`
/// is initialized, then the byte at offset `b` within `*ptr` must be
/// initialized.
/// - Let `c` be the contents of the byte range `[0, b)` in `*ptr`. Let `S`
/// be the subset of valid instances of `T` of length `len` which contain
/// `c` in the offset range `[0, b)`. If, in any instance of `t: T` in
/// `S`, the byte at offset `b` in `t` is initialized, then the byte at
/// offset `b` in `*ptr` must be initialized.
/// - Let `c` be the contents of the byte range `[0, b)` in `*ptr`. Let `S` be
/// the subset of valid instances of `T` of length `len` which contain `c` in
/// the offset range `[0, b)`. If, in any instance of `t: T` in `S`, the byte
/// at offset `b` in `t` is initialized, then the byte at offset `b` in `*ptr`
/// must be initialized.
///
/// Pragmatically, this means that if `*ptr` is guaranteed to contain an
/// enum type at a particular offset, and the enum discriminant stored in
/// `*ptr` corresponds to a valid variant of that enum type, then it is
/// guaranteed that the appropriate bytes of `*ptr` are initialized as
/// defined by that variant's bit validity (although note that the variant
/// may contain another enum type, in which case the same rules apply
/// depending on the state of its discriminant, and so on recursively).
/// Pragmatically, this means that if `*ptr` is guaranteed to contain an enum
/// type at a particular offset, and the enum discriminant stored in `*ptr`
/// corresponds to a valid variant of that enum type, then it is guaranteed
/// that the appropriate bytes of `*ptr` are initialized as defined by that
/// variant's bit validity (although note that the variant 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;
}

/// [`Ptr`](crate::Ptr) referents that permit unsynchronized read operations.
///
Expand Down Expand Up @@ -231,3 +255,112 @@ mod sealed {
impl Sealed for BecauseImmutable {}
impl Sealed for BecauseExclusive {}
}

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

/// A mapping from one [`Aliasing`] type to another.
///
/// An `AliasingMapping` is a type-level map which maps one `Aliasing` type
/// to another. It is always "total" in the sense of having a mapping for
/// any `A: Aliasing`.
///
/// Given `A: Aliasing` and `M: AliasingMapping`, `M` can be applied to `A`
/// as [`MappedAliasing<A, M>`](MappedAliasing).
///
/// Mappings are used by [`Ptr`](crate::Ptr) conversion methods to preserve
/// or modify invariants as required by each method's semantics.
pub trait AliasingMapping {
type FromInaccessible: Aliasing;
type FromShared: Aliasing;
type FromExclusive: Aliasing;
}

/// A mapping from one [`Alignment`] type to another.
///
/// An `AlignmentMapping` is a type-level map which maps one `Alignment`
/// type to another. It is always "total" in the sense of having a mapping
/// for any `A: Alignment`.
///
/// Given `A: Alignment` and `M: AlignmentMapping`, `M` can be applied to
/// `A` as [`MappedAlignment<A, M>`](MappedAlignment).
///
/// Mappings are used by [`Ptr`](crate::Ptr) conversion methods to preserve
/// or modify invariants as required by each method's semantics.
pub trait AlignmentMapping {
type FromUnknown: Alignment;
type FromAligned: Alignment;
}

/// A mapping from one [`Validity`] type to another.
///
/// An `ValidityMapping` is a type-level map which maps one `Validity` type
/// to another. It is always "total" in the sense of having a mapping for
/// any `A: Validity`.
///
/// Given `V: Validity` and `M: ValidityMapping`, `M` can be applied to `V`
/// as [`MappedValidity<A, M>`](MappedValidity).
///
/// Mappings are used by [`Ptr`](crate::Ptr) conversion methods to preserve
/// or modify invariants as required by each method's semantics.
pub trait ValidityMapping {
type FromUnknown: Validity;
type FromAsInitialized: Validity;
type FromInitialized: Validity;
type FromValid: Validity;
}

/// The application of the [`AliasingMapping`] `M` to the [`Aliasing`] `A`.
#[allow(type_alias_bounds)]
pub type MappedAliasing<A: Aliasing, M: AliasingMapping> = A::MappedTo<M>;

/// The application of the [`AlignmentMapping`] `M` to the [`Alignment`] `A`.
#[allow(type_alias_bounds)]
pub type MappedAlignment<A: Alignment, M: AlignmentMapping> = A::MappedTo<M>;

/// The application of the [`ValidityMapping`] `M` to the [`Validity`] `A`.
#[allow(type_alias_bounds)]
pub type MappedValidity<V: Validity, M: ValidityMapping> = V::MappedTo<M>;

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

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

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

impl<FromInitialized: Validity> ValidityMapping for (Initialized, FromInitialized) {
type FromUnknown = Unknown;
type FromAsInitialized = Unknown;
type FromInitialized = FromInitialized;
type FromValid = Unknown;
}
}
37 changes: 19 additions & 18 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,11 @@ mod _casts {
pub unsafe fn cast_unsized_unchecked<U: 'a + ?Sized, F: FnOnce(*mut T) -> *mut U>(
self,
cast: F,
) -> Ptr<'a, U, (I::Aliasing, Unknown, Unknown)> {
) -> Ptr<
'a,
U,
(I::Aliasing, Unknown, MappedValidity<I::Validity, (Initialized, Initialized)>),
> {
let ptr = cast(self.as_inner().as_non_null().as_ptr());

// SAFETY: Caller promises that `cast` returns a pointer whose
Expand Down Expand Up @@ -770,8 +774,13 @@ mod _casts {
// - `Inaccessible`: There are no restrictions we need to uphold.
// 7. `ptr`, trivially, conforms to the alignment invariant of
// `Unknown`.
// 8. `ptr`, trivially, conforms to the validity invariant of
// `Unknown`.
// 8. If `I::Validity = Unknown`, `AsInitialized`, or `Valid`, the
// output validity invariant is `Unknown`. `ptr` trivially
// conforms to this invariant. If `I::Validity = Initialized`,
// the output validity invariant is `Initialized`. Regardless of
// what subset of `self`'s referent is referred to by `ptr`, if
// all of `self`'s referent is initialized, then the same holds
// of `ptr`'s referent.
unsafe { Ptr::new(ptr) }
}

Expand All @@ -788,7 +797,11 @@ mod _casts {
pub unsafe fn cast_unsized<U, F, R, S>(
self,
cast: F,
) -> Ptr<'a, U, (I::Aliasing, Unknown, Unknown)>
) -> Ptr<
'a,
U,
(I::Aliasing, Unknown, MappedValidity<I::Validity, (Initialized, Initialized)>),
>
where
T: Read<I::Aliasing, R>,
U: 'a + ?Sized + Read<I::Aliasing, S>,
Expand Down Expand Up @@ -842,14 +855,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 @@ -1082,12 +1088,7 @@ mod _project {

// SAFETY: This method has the same safety preconditions as
// `cast_unsized_unchecked`.
let ptr = unsafe { self.cast_unsized_unchecked(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_unchecked(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 @@ -421,7 +421,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
13 changes: 1 addition & 12 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_unchecked::<$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 @@ -177,11 +171,6 @@ macro_rules! unsafe_impl {
#[allow(clippy::as_conversions)]
let $candidate = unsafe { candidate.cast_unsized_unchecked::<$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
Loading