Skip to content

Commit 69792bc

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 99b49b7 commit 69792bc

File tree

7 files changed

+145
-93
lines changed

7 files changed

+145
-93
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: 124 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>` adheres to the aliasing rules of a `&'a T`.
91104
///
@@ -99,6 +112,7 @@ pub enum Shared {}
99112
impl Aliasing for Shared {
100113
const IS_EXCLUSIVE: bool = false;
101114
type Variance<'a, T: 'a + ?Sized> = &'a T;
115+
type MappedTo<M: AliasingMapping> = M::FromShared;
102116
}
103117
impl Reference for Shared {}
104118

@@ -111,51 +125,60 @@ pub enum Exclusive {}
111125
impl Aliasing for Exclusive {
112126
const IS_EXCLUSIVE: bool = true;
113127
type Variance<'a, T: 'a + ?Sized> = &'a mut T;
128+
type MappedTo<M: AliasingMapping> = M::FromExclusive;
114129
}
115130
impl Reference for Exclusive {}
116131

117-
/// The referent is aligned: for `Ptr<T>`, the referent's address is a multiple
118-
/// of the `T`'s alignment.
132+
/// The referent is aligned: for `Ptr<T>`, the referent's address is a
133+
/// multiple of the `T`'s alignment.
119134
pub enum Aligned {}
120-
impl Alignment for Aligned {}
135+
impl Alignment for Aligned {
136+
type MappedTo<M: AlignmentMapping> = M::FromAligned;
137+
}
121138

122139
/// The byte ranges initialized in `T` are also initialized in the referent.
123140
///
124141
/// Formally: uninitialized bytes may only be present in `Ptr<T>`'s referent
125-
/// where they are guaranteed to be present in `T`. This is a dynamic
126-
/// property: if, at a particular byte offset, a valid enum discriminant is
127-
/// set, the subsequent bytes may only have uninitialized bytes as
128-
/// specificed by the corresponding enum.
142+
/// where they are guaranteed to be present in `T`. This is a dynamic property:
143+
/// if, at a particular byte offset, a valid enum discriminant is set, the
144+
/// subsequent bytes may only have uninitialized bytes as specificed by the
145+
/// corresponding enum.
129146
///
130-
/// Formally, given `len = size_of_val_raw(ptr)`, at every byte offset, `b`,
131-
/// in the range `[0, len)`:
132-
/// - If, in any instance `t: T` of length `len`, the byte at offset `b` in
133-
/// `t` is initialized, then the byte at offset `b` within `*ptr` must be
147+
/// Formally, given `len = size_of_val_raw(ptr)`, at every byte offset, `b`, in
148+
/// the range `[0, len)`:
149+
/// - If, in any instance `t: T` of length `len`, the byte at offset `b` in `t`
150+
/// is initialized, then the byte at offset `b` within `*ptr` must be
134151
/// initialized.
135-
/// - Let `c` be the contents of the byte range `[0, b)` in `*ptr`. Let `S`
136-
/// be the subset of valid instances of `T` of length `len` which contain
137-
/// `c` in the offset range `[0, b)`. If, in any instance of `t: T` in
138-
/// `S`, the byte at offset `b` in `t` is initialized, then the byte at
139-
/// offset `b` in `*ptr` must be initialized.
152+
/// - Let `c` be the contents of the byte range `[0, b)` in `*ptr`. Let `S` be
153+
/// the subset of valid instances of `T` of length `len` which contain `c` in
154+
/// the offset range `[0, b)`. If, in any instance of `t: T` in `S`, the byte
155+
/// at offset `b` in `t` is initialized, then the byte at offset `b` in `*ptr`
156+
/// must be initialized.
140157
///
141-
/// Pragmatically, this means that if `*ptr` is guaranteed to contain an
142-
/// enum type at a particular offset, and the enum discriminant stored in
143-
/// `*ptr` corresponds to a valid variant of that enum type, then it is
144-
/// guaranteed that the appropriate bytes of `*ptr` are initialized as
145-
/// defined by that variant's bit validity (although note that the variant
146-
/// may contain another enum type, in which case the same rules apply
147-
/// depending on the state of its discriminant, and so on recursively).
158+
/// Pragmatically, this means that if `*ptr` is guaranteed to contain an enum
159+
/// type at a particular offset, and the enum discriminant stored in `*ptr`
160+
/// corresponds to a valid variant of that enum type, then it is guaranteed
161+
/// that the appropriate bytes of `*ptr` are initialized as defined by that
162+
/// variant's bit validity (although note that the variant may contain another
163+
/// enum type, in which case the same rules apply depending on the state of
164+
/// its discriminant, and so on recursively).
148165
pub enum AsInitialized {}
149-
impl Validity for AsInitialized {}
166+
impl Validity for AsInitialized {
167+
type MappedTo<M: ValidityMapping> = M::FromAsInitialized;
168+
}
150169

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

156177
/// The referent is bit-valid for `T`.
157178
pub enum Valid {}
158-
impl Validity for Valid {}
179+
impl Validity for Valid {
180+
type MappedTo<M: ValidityMapping> = M::FromValid;
181+
}
159182

160183
/// [`Ptr`](crate::Ptr) referents that permit unsynchronized read operations.
161184
///
@@ -215,3 +238,74 @@ mod sealed {
215238
impl Sealed for BecauseImmutable {}
216239
impl Sealed for BecauseExclusive {}
217240
}
241+
242+
pub use mapping::*;
243+
mod mapping {
244+
use super::*;
245+
246+
pub trait AliasingMapping {
247+
type FromShared: Aliasing;
248+
type FromExclusive: Aliasing;
249+
}
250+
251+
pub trait AlignmentMapping {
252+
type FromUnknown: Alignment;
253+
type FromAligned: Alignment;
254+
}
255+
256+
pub trait ValidityMapping {
257+
type FromUnknown: Validity;
258+
type FromAsInitialized: Validity;
259+
type FromInitialized: Validity;
260+
type FromValid: Validity;
261+
}
262+
263+
#[allow(type_alias_bounds)]
264+
pub type MappedAliasing<I: Aliasing, M: AliasingMapping> = I::MappedTo<M>;
265+
266+
#[allow(type_alias_bounds)]
267+
pub type MappedAlignment<I: Alignment, M: AlignmentMapping> = I::MappedTo<M>;
268+
269+
#[allow(type_alias_bounds)]
270+
pub type MappedValidity<I: Validity, M: ValidityMapping> = I::MappedTo<M>;
271+
272+
impl<FromUnknown: Aliasing, FromShared: Aliasing, FromExclusive: Aliasing> AliasingMapping
273+
for ((Unknown, FromUnknown), (Shared, FromShared), (Exclusive, FromExclusive))
274+
{
275+
type FromShared = FromShared;
276+
type FromExclusive = FromExclusive;
277+
}
278+
279+
impl<FromUnknown: Alignment, FromAligned: Alignment> AlignmentMapping
280+
for ((Unknown, FromUnknown), (Shared, FromAligned))
281+
{
282+
type FromUnknown = FromUnknown;
283+
type FromAligned = FromAligned;
284+
}
285+
286+
impl<
287+
FromUnknown: Validity,
288+
FromAsInitialized: Validity,
289+
FromInitialized: Validity,
290+
FromValid: Validity,
291+
> ValidityMapping
292+
for (
293+
(Unknown, FromUnknown),
294+
(AsInitialized, FromAsInitialized),
295+
(Initialized, FromInitialized),
296+
(Valid, FromValid),
297+
)
298+
{
299+
type FromUnknown = FromUnknown;
300+
type FromAsInitialized = FromAsInitialized;
301+
type FromInitialized = FromInitialized;
302+
type FromValid = FromValid;
303+
}
304+
305+
impl<FromInitialized: Validity> ValidityMapping for (Initialized, FromInitialized) {
306+
type FromUnknown = Unknown;
307+
type FromAsInitialized = Unknown;
308+
type FromInitialized = FromInitialized;
309+
type FromValid = Unknown;
310+
}
311+
}

src/pointer/ptr.rs

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

720724
// SAFETY: Caller promises that `cast` returns a pointer whose
@@ -770,7 +774,13 @@ mod _casts {
770774
// not happen.
771775
// 7. `ptr`, trivially, conforms to the alignment invariant of
772776
// `Any`.
773-
// 8. `ptr`, trivially, conforms to the validity invariant of `Any`.
777+
// 8. If `I::Validity = Any`, `AsInitialized`, or `Valid`, the
778+
// output validity invariant is `Any`. `ptr` trivially conforms
779+
// to this invariant. If `I::Validity = Initialized`, the output
780+
// validity invariant is `Initialized`. Regardless of what subset
781+
// of `self`'s referent is referred to by `ptr`, if all of
782+
// `self`'s referent is initialized, then the same holds of
783+
// `ptr`'s referent.
774784
unsafe { Ptr::new(ptr) }
775785
}
776786

@@ -787,7 +797,11 @@ mod _casts {
787797
pub unsafe fn cast_unsized<U, F, R, S>(
788798
self,
789799
cast: F,
790-
) -> Ptr<'a, U, (I::Aliasing, Unknown, Unknown)>
800+
) -> Ptr<
801+
'a,
802+
U,
803+
(I::Aliasing, Unknown, MappedValidity<I::Validity, (Initialized, Initialized)>),
804+
>
791805
where
792806
T: Read<I::Aliasing, R>,
793807
U: 'a + ?Sized + Read<I::Aliasing, S>,
@@ -841,14 +855,7 @@ mod _casts {
841855
})
842856
};
843857

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

@@ -1080,12 +1087,7 @@ mod _project {
10801087

10811088
// SAFETY: This method has the same safety preconditions as
10821089
// `cast_unsized_unchecked`.
1083-
let ptr = unsafe { self.cast_unsized_unchecked(projector) };
1084-
1085-
// SAFETY: If all of the bytes of `self` are initialized (as
1086-
// promised by `I: Invariants<Validity = Initialized>`), then any
1087-
// subset of those bytes are also all initialized.
1088-
unsafe { ptr.assume_validity::<Initialized>() }
1090+
unsafe { self.cast_unsized_unchecked(projector) }
10891091
}
10901092
}
10911093

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)