Skip to content

Commit 6bc91f5

Browse files
committed
[pointer] Support generic invariant mapping
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
1 parent e4ae056 commit 6bc91f5

File tree

7 files changed

+148
-94
lines changed

7 files changed

+148
-94
lines changed

src/impls.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -290,15 +290,6 @@ safety_comment! {
290290
/// because `NonZeroXxx` and `xxx` have the same size. [1] Neither `r`
291291
/// nor `t` refer to any `UnsafeCell`s because neither `NonZeroXxx` [2]
292292
/// nor `xxx` do.
293-
/// - Since the closure takes a `&xxx` argument, given a `Maybe<'a,
294-
/// NonZeroXxx>` which satisfies the preconditions of
295-
/// `TryFromBytes::<NonZeroXxx>::is_bit_valid`, it must be guaranteed
296-
/// that the memory referenced by that `MabyeValid` always contains a
297-
/// valid `xxx`. Since `NonZeroXxx`'s bytes are always initialized [1],
298-
/// `is_bit_valid`'s precondition requires that the same is true of its
299-
/// argument. Since `xxx`'s only bit validity invariant is that its
300-
/// bytes must be initialized, this memory is guaranteed to contain a
301-
/// valid `xxx`.
302293
/// - The impl must only return `true` for its argument if the original
303294
/// `Maybe<NonZeroXxx>` refers to a valid `NonZeroXxx`. The only
304295
/// `xxx` which is not also a valid `NonZeroXxx` is 0. [1]

src/pointer/invariant.rs

Lines changed: 127 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,22 @@ pub trait Aliasing: Sealed {
6565
/// Aliasing>::Variance<'a, T>` to inherit this variance.
6666
#[doc(hidden)]
6767
type Variance<'a, T: 'a + ?Sized>;
68+
69+
#[doc(hidden)]
70+
type MappedTo<M: AliasingMapping>: Aliasing;
6871
}
6972

7073
/// The alignment invariant of a [`Ptr`][super::Ptr].
71-
pub trait Alignment: Sealed {}
74+
pub trait Alignment: Sealed {
75+
#[doc(hidden)]
76+
type MappedTo<M: AlignmentMapping>: Alignment;
77+
}
7278

7379
/// The validity invariant of a [`Ptr`][super::Ptr].
74-
pub trait Validity: Sealed {}
80+
pub trait Validity: Sealed {
81+
#[doc(hidden)]
82+
type MappedTo<M: ValidityMapping>: Validity;
83+
}
7584

7685
/// An [`Aliasing`] invariant which is either [`Shared`] or [`Exclusive`].
7786
///
@@ -84,8 +93,12 @@ pub trait Reference: Aliasing + Sealed {}
8493
/// It is unknown whether any invariant holds.
8594
pub enum Unknown {}
8695

87-
impl Alignment for Unknown {}
88-
impl Validity for Unknown {}
96+
impl Alignment for Unknown {
97+
type MappedTo<M: AlignmentMapping> = M::FromUnknown;
98+
}
99+
impl Validity for Unknown {
100+
type MappedTo<M: ValidityMapping> = M::FromUnknown;
101+
}
89102

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

105119
/// The `Ptr<'a, T>` adheres to the aliasing rules of a `&'a T`.
@@ -114,6 +128,7 @@ pub enum Shared {}
114128
impl Aliasing for Shared {
115129
const IS_EXCLUSIVE: bool = false;
116130
type Variance<'a, T: 'a + ?Sized> = &'a T;
131+
type MappedTo<M: AliasingMapping> = M::FromShared;
117132
}
118133
impl Reference for Shared {}
119134

@@ -126,51 +141,60 @@ pub enum Exclusive {}
126141
impl Aliasing for Exclusive {
127142
const IS_EXCLUSIVE: bool = true;
128143
type Variance<'a, T: 'a + ?Sized> = &'a mut T;
144+
type MappedTo<M: AliasingMapping> = M::FromExclusive;
129145
}
130146
impl Reference for Exclusive {}
131147

132-
/// The referent is aligned: for `Ptr<T>`, the referent's address is a multiple
133-
/// of the `T`'s alignment.
148+
/// The referent is aligned: for `Ptr<T>`, the referent's address is a
149+
/// multiple of the `T`'s alignment.
134150
pub enum Aligned {}
135-
impl Alignment for Aligned {}
151+
impl Alignment for Aligned {
152+
type MappedTo<M: AlignmentMapping> = M::FromAligned;
153+
}
136154

137155
/// The byte ranges initialized in `T` are also initialized in the referent.
138156
///
139157
/// Formally: uninitialized bytes may only be present in `Ptr<T>`'s referent
140-
/// where they are guaranteed to be present in `T`. This is a dynamic
141-
/// property: if, at a particular byte offset, a valid enum discriminant is
142-
/// set, the subsequent bytes may only have uninitialized bytes as
143-
/// specificed by the corresponding enum.
158+
/// where they are guaranteed to be present in `T`. This is a dynamic property:
159+
/// if, at a particular byte offset, a valid enum discriminant is set, the
160+
/// subsequent bytes may only have uninitialized bytes as specificed by the
161+
/// corresponding enum.
144162
///
145-
/// Formally, given `len = size_of_val_raw(ptr)`, at every byte offset, `b`,
146-
/// in the range `[0, len)`:
147-
/// - If, in any instance `t: T` of length `len`, the byte at offset `b` in
148-
/// `t` is initialized, then the byte at offset `b` within `*ptr` must be
163+
/// Formally, given `len = size_of_val_raw(ptr)`, at every byte offset, `b`, in
164+
/// the range `[0, len)`:
165+
/// - If, in any instance `t: T` of length `len`, the byte at offset `b` in `t`
166+
/// is initialized, then the byte at offset `b` within `*ptr` must be
149167
/// initialized.
150-
/// - Let `c` be the contents of the byte range `[0, b)` in `*ptr`. Let `S`
151-
/// be the subset of valid instances of `T` of length `len` which contain
152-
/// `c` in the offset range `[0, b)`. If, in any instance of `t: T` in
153-
/// `S`, the byte at offset `b` in `t` is initialized, then the byte at
154-
/// offset `b` in `*ptr` must be initialized.
168+
/// - Let `c` be the contents of the byte range `[0, b)` in `*ptr`. Let `S` be
169+
/// the subset of valid instances of `T` of length `len` which contain `c` in
170+
/// the offset range `[0, b)`. If, in any instance of `t: T` in `S`, the byte
171+
/// at offset `b` in `t` is initialized, then the byte at offset `b` in `*ptr`
172+
/// must be initialized.
155173
///
156-
/// Pragmatically, this means that if `*ptr` is guaranteed to contain an
157-
/// enum type at a particular offset, and the enum discriminant stored in
158-
/// `*ptr` corresponds to a valid variant of that enum type, then it is
159-
/// guaranteed that the appropriate bytes of `*ptr` are initialized as
160-
/// defined by that variant's bit validity (although note that the variant
161-
/// may contain another enum type, in which case the same rules apply
162-
/// depending on the state of its discriminant, and so on recursively).
174+
/// Pragmatically, this means that if `*ptr` is guaranteed to contain an enum
175+
/// type at a particular offset, and the enum discriminant stored in `*ptr`
176+
/// corresponds to a valid variant of that enum type, then it is guaranteed
177+
/// that the appropriate bytes of `*ptr` are initialized as defined by that
178+
/// variant's bit validity (although note that the variant may contain another
179+
/// enum type, in which case the same rules apply depending on the state of
180+
/// its discriminant, and so on recursively).
163181
pub enum AsInitialized {}
164-
impl Validity for AsInitialized {}
182+
impl Validity for AsInitialized {
183+
type MappedTo<M: ValidityMapping> = M::FromAsInitialized;
184+
}
165185

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

171193
/// The referent is bit-valid for `T`.
172194
pub enum Valid {}
173-
impl Validity for Valid {}
195+
impl Validity for Valid {
196+
type MappedTo<M: ValidityMapping> = M::FromValid;
197+
}
174198

175199
/// [`Ptr`](crate::Ptr) referents that permit unsynchronized read operations.
176200
///
@@ -231,3 +255,76 @@ mod sealed {
231255
impl Sealed for BecauseImmutable {}
232256
impl Sealed for BecauseExclusive {}
233257
}
258+
259+
pub use mapping::*;
260+
mod mapping {
261+
use super::*;
262+
263+
pub trait AliasingMapping {
264+
type FromInaccessible: Aliasing;
265+
type FromShared: Aliasing;
266+
type FromExclusive: Aliasing;
267+
}
268+
269+
pub trait AlignmentMapping {
270+
type FromUnknown: Alignment;
271+
type FromAligned: Alignment;
272+
}
273+
274+
pub trait ValidityMapping {
275+
type FromUnknown: Validity;
276+
type FromAsInitialized: Validity;
277+
type FromInitialized: Validity;
278+
type FromValid: Validity;
279+
}
280+
281+
#[allow(type_alias_bounds)]
282+
pub type MappedAliasing<I: Aliasing, M: AliasingMapping> = I::MappedTo<M>;
283+
284+
#[allow(type_alias_bounds)]
285+
pub type MappedAlignment<I: Alignment, M: AlignmentMapping> = I::MappedTo<M>;
286+
287+
#[allow(type_alias_bounds)]
288+
pub type MappedValidity<I: Validity, M: ValidityMapping> = I::MappedTo<M>;
289+
290+
impl<FromInaccessible: Aliasing, FromShared: Aliasing, FromExclusive: Aliasing> AliasingMapping
291+
for ((Inaccessible, FromInaccessible), (Shared, FromShared), (Exclusive, FromExclusive))
292+
{
293+
type FromInaccessible = FromInaccessible;
294+
type FromShared = FromShared;
295+
type FromExclusive = FromExclusive;
296+
}
297+
298+
impl<FromUnknown: Alignment, FromAligned: Alignment> AlignmentMapping
299+
for ((Unknown, FromUnknown), (Shared, FromAligned))
300+
{
301+
type FromUnknown = FromUnknown;
302+
type FromAligned = FromAligned;
303+
}
304+
305+
impl<
306+
FromUnknown: Validity,
307+
FromAsInitialized: Validity,
308+
FromInitialized: Validity,
309+
FromValid: Validity,
310+
> ValidityMapping
311+
for (
312+
(Unknown, FromUnknown),
313+
(AsInitialized, FromAsInitialized),
314+
(Initialized, FromInitialized),
315+
(Valid, FromValid),
316+
)
317+
{
318+
type FromUnknown = FromUnknown;
319+
type FromAsInitialized = FromAsInitialized;
320+
type FromInitialized = FromInitialized;
321+
type FromValid = FromValid;
322+
}
323+
324+
impl<FromInitialized: Validity> ValidityMapping for (Initialized, FromInitialized) {
325+
type FromUnknown = Unknown;
326+
type FromAsInitialized = Unknown;
327+
type FromInitialized = FromInitialized;
328+
type FromValid = Unknown;
329+
}
330+
}

src/pointer/ptr.rs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,11 @@ mod _casts {
713713
pub unsafe fn cast_unsized_unchecked<U: 'a + ?Sized, F: FnOnce(*mut T) -> *mut U>(
714714
self,
715715
cast: F,
716-
) -> Ptr<'a, U, (I::Aliasing, Unknown, Unknown)> {
716+
) -> Ptr<
717+
'a,
718+
U,
719+
(I::Aliasing, Unknown, MappedValidity<I::Validity, (Initialized, Initialized)>),
720+
> {
717721
let ptr = cast(self.as_inner().as_non_null().as_ptr());
718722

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

@@ -788,7 +797,11 @@ mod _casts {
788797
pub unsafe fn cast_unsized<U, F, R, S>(
789798
self,
790799
cast: F,
791-
) -> Ptr<'a, U, (I::Aliasing, Unknown, Unknown)>
800+
) -> Ptr<
801+
'a,
802+
U,
803+
(I::Aliasing, Unknown, MappedValidity<I::Validity, (Initialized, Initialized)>),
804+
>
792805
where
793806
T: Read<I::Aliasing, R>,
794807
U: 'a + ?Sized + Read<I::Aliasing, S>,
@@ -842,14 +855,7 @@ mod _casts {
842855
})
843856
};
844857

845-
let ptr = ptr.bikeshed_recall_aligned();
846-
847-
// SAFETY: `ptr`'s referent begins as `Initialized`, denoting that
848-
// all bytes of the referent are initialized bytes. The referent
849-
// type is then casted to `[u8]`, whose only validity invariant is
850-
// that its bytes are initialized. This validity invariant is
851-
// satisfied by the `Initialized` invariant on the starting `ptr`.
852-
unsafe { ptr.assume_validity::<Valid>() }
858+
ptr.bikeshed_recall_aligned().bikeshed_recall_valid()
853859
}
854860
}
855861

@@ -1082,12 +1088,7 @@ mod _project {
10821088

10831089
// SAFETY: This method has the same safety preconditions as
10841090
// `cast_unsized_unchecked`.
1085-
let ptr = unsafe { self.cast_unsized_unchecked(projector) };
1086-
1087-
// SAFETY: If all of the bytes of `self` are initialized (as
1088-
// promised by `I: Invariants<Validity = Initialized>`), then any
1089-
// subset of those bytes are also all initialized.
1090-
unsafe { ptr.assume_validity::<Initialized>() }
1091+
unsafe { self.cast_unsized_unchecked(projector) }
10911092
}
10921093
}
10931094

src/util/macro_util.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ macro_rules! assert_align_gt_eq {
421421
#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
422422
#[macro_export]
423423
macro_rules! assert_size_eq {
424-
($t:ident, $u: ident) => {{
424+
($t:ident, $u:ident) => {{
425425
// The comments here should be read in the context of this macro's
426426
// invocations in `transmute_ref!` and `transmute_mut!`.
427427
if false {

src/util/macros.rs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,6 @@ macro_rules! safety_comment {
5252
/// referred to by `t`.
5353
/// - `r` refers to an object with `UnsafeCell`s at the same byte ranges as
5454
/// the object referred to by `t`.
55-
/// - If the provided closure takes a `&$repr` argument, then given a `Ptr<'a,
56-
/// $ty>` which satisfies the preconditions of
57-
/// `TryFromBytes::<$ty>::is_bit_valid`, it must be guaranteed that the
58-
/// memory referenced by that `Ptr` always contains a valid `$repr`.
5955
/// - The impl of `is_bit_valid` must only return `true` for its argument
6056
/// `Ptr<$repr>` if the original `Ptr<$ty>` refers to a valid `$ty`.
6157
macro_rules! unsafe_impl {
@@ -153,9 +149,7 @@ macro_rules! unsafe_impl {
153149
#[allow(clippy::as_conversions)]
154150
let candidate = unsafe { candidate.cast_unsized_unchecked::<$repr, _>(|p| p as *mut _) };
155151

156-
// SAFETY: The caller has promised that the referenced memory region
157-
// will contain a valid `$repr`.
158-
let $candidate = unsafe { candidate.assume_validity::<crate::pointer::invariant::Valid>() };
152+
let $candidate = candidate.bikeshed_recall_valid();
159153
$is_bit_valid
160154
}
161155
};
@@ -177,11 +171,6 @@ macro_rules! unsafe_impl {
177171
#[allow(clippy::as_conversions)]
178172
let $candidate = unsafe { candidate.cast_unsized_unchecked::<$repr, _>(|p| p as *mut _) };
179173

180-
// Restore the invariant that the referent bytes are initialized.
181-
// SAFETY: The above cast does not uninitialize any referent bytes;
182-
// they remain initialized.
183-
let $candidate = unsafe { $candidate.assume_validity::<crate::pointer::invariant::Initialized>() };
184-
185174
$is_bit_valid
186175
}
187176
};

zerocopy-derive/src/enum.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -265,10 +265,6 @@ pub(crate) fn derive_is_bit_valid(
265265
}
266266
)
267267
};
268-
// SAFETY: `cast_unsized_unchecked` removes the
269-
// initialization invariant from `p`, so we re-assert that
270-
// all of the bytes are initialized.
271-
let variant = unsafe { variant.assume_initialized() };
272268
<
273269
#variant_struct_ident #ty_generics as #trait_path
274270
>::is_bit_valid(variant)
@@ -325,10 +321,6 @@ pub(crate) fn derive_is_bit_valid(
325321
p as *mut ___ZerocopyTagPrimitive
326322
})
327323
};
328-
// SAFETY: `tag_ptr` is casted from `candidate`, whose referent
329-
// is `Initialized`. Since we have not written uninitialized
330-
// bytes into the referent, `tag_ptr` is also `Initialized`.
331-
let tag_ptr = unsafe { tag_ptr.assume_initialized() };
332324
tag_ptr.bikeshed_recall_valid().read_unaligned::<::zerocopy::BecauseImmutable>()
333325
};
334326

@@ -347,10 +339,6 @@ pub(crate) fn derive_is_bit_valid(
347339
p as *mut ___ZerocopyRawEnum #ty_generics
348340
})
349341
};
350-
// SAFETY: `cast_unsized_unchecked` removes the initialization
351-
// invariant from `p`, so we re-assert that all of the bytes are
352-
// initialized.
353-
let raw_enum = unsafe { raw_enum.assume_initialized() };
354342
// SAFETY:
355343
// - This projection returns a subfield of `this` using
356344
// `addr_of_mut!`.

0 commit comments

Comments
 (0)