Skip to content

Commit feb5ccf

Browse files
committed
[pointer] Document validity safety invariant
Document the semantics of `Ptr` validity invariants on the `Validity` trait. Makes progress on #2226, #1866 gherrit-pr-id: Ie66db9044be1dc310a6b7280a73652a357878376
1 parent cff6196 commit feb5ccf

File tree

6 files changed

+232
-143
lines changed

6 files changed

+232
-143
lines changed

src/impls.rs

Lines changed: 14 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -101,19 +101,11 @@ safety_comment! {
101101
/// - Given `t: *mut bool` and `let r = *mut u8`, `r` refers to an object
102102
/// of the same size as that referred to by `t`. This is true because
103103
/// `bool` and `u8` have the same size (1 byte) [1]. Neither `r` nor `t`
104-
/// contain `UnsafeCell`s because neither `bool` nor `u8` do [4].
105-
/// - Since the closure takes a `&u8` argument, given a `Maybe<'a,
106-
/// bool>` which satisfies the preconditions of
107-
/// `TryFromBytes::<bool>::is_bit_valid`, it must be guaranteed that the
108-
/// memory referenced by that `MaybeValid` always contains a valid `u8`.
109-
/// Since `bool`'s single byte is always initialized, `is_bit_valid`'s
110-
/// precondition requires that the same is true of its argument. Since
111-
/// `u8`'s only bit validity invariant is that its single byte must be
112-
/// initialized, this memory is guaranteed to contain a valid `u8`.
104+
/// contain `UnsafeCell`s because neither `bool` nor `u8` do [3].
113105
/// - The impl must only return `true` for its argument if the original
114-
/// `Maybe<bool>` refers to a valid `bool`. We only return true if
115-
/// the `u8` value is 0 or 1, and both of these are valid values for
116-
/// `bool`. [3]
106+
/// `Maybe<bool>` refers to a valid `bool`. We only return true if the
107+
/// `u8` value is 0 or 1, and both of these are valid values for `bool`.
108+
/// [2]
117109
///
118110
/// [1] Per https://doc.rust-lang.org/1.81.0/reference/type-layout.html#primitive-data-layout:
119111
///
@@ -124,16 +116,12 @@ safety_comment! {
124116
/// | `bool` | 1 |
125117
/// | `u8`/`i8` | 1 |
126118
///
127-
/// [2] Per https://doc.rust-lang.org/1.81.0/reference/type-layout.html#size-and-alignment:
128-
///
129-
/// The size of a value is always a multiple of its alignment.
130-
///
131-
/// [3] Per https://doc.rust-lang.org/1.81.0/reference/types/boolean.html:
119+
/// [2] Per https://doc.rust-lang.org/1.81.0/reference/types/boolean.html:
132120
///
133121
/// The value false has the bit pattern 0x00 and the value true has the
134122
/// bit pattern 0x01.
135123
///
136-
/// [4] TODO(#429): Justify this claim.
124+
/// [3] TODO(#429): Justify this claim.
137125
unsafe_impl!(bool: TryFromBytes; |byte: MaybeAligned<u8>| *byte.unaligned_as_ref() < 2);
138126
}
139127
safety_comment! {
@@ -155,20 +143,10 @@ safety_comment! {
155143
/// - Given `t: *mut char` and `let r = *mut u32`, `r` refers to an object
156144
/// of the same size as that referred to by `t`. This is true because
157145
/// `char` and `u32` have the same size [1]. Neither `r` nor `t` contain
158-
/// `UnsafeCell`s because neither `char` nor `u32` do [4].
159-
/// - Since the closure takes a `&u32` argument, given a `Maybe<'a,
160-
/// char>` which satisfies the preconditions of
161-
/// `TryFromBytes::<char>::is_bit_valid`, it must be guaranteed that the
162-
/// memory referenced by that `MaybeValid` always contains a valid
163-
/// `u32`. Since `char`'s bytes are always initialized [2],
164-
/// `is_bit_valid`'s precondition requires that the same is true of its
165-
/// argument. Since `u32`'s only bit validity invariant is that its
166-
/// bytes must be initialized, this memory is guaranteed to contain a
167-
/// valid `u32`.
146+
/// `UnsafeCell`s because neither `char` nor `u32` do [3].
168147
/// - The impl must only return `true` for its argument if the original
169-
/// `Maybe<char>` refers to a valid `char`. `char::from_u32`
170-
/// guarantees that it returns `None` if its input is not a valid
171-
/// `char`. [3]
148+
/// `Maybe<char>` refers to a valid `char`. `char::from_u32` guarantees
149+
/// that it returns `None` if its input is not a valid `char`. [2]
172150
///
173151
/// [1] Per https://doc.rust-lang.org/nightly/reference/types/textual.html#layout-and-bit-validity:
174152
///
@@ -177,14 +155,10 @@ safety_comment! {
177155
///
178156
/// [2] Per https://doc.rust-lang.org/core/primitive.char.html#method.from_u32:
179157
///
180-
/// Every byte of a `char` is guaranteed to be initialized.
181-
///
182-
/// [3] Per https://doc.rust-lang.org/core/primitive.char.html#method.from_u32:
183-
///
184158
/// `from_u32()` will return `None` if the input is not a valid value for
185159
/// a `char`.
186160
///
187-
/// [4] TODO(#429): Justify this claim.
161+
/// [3] TODO(#429): Justify this claim.
188162
unsafe_impl!(char: TryFromBytes; |candidate: MaybeAligned<u32>| {
189163
let candidate = candidate.read_unaligned::<BecauseImmutable>();
190164
char::from_u32(candidate).is_some()
@@ -215,19 +189,9 @@ safety_comment! {
215189
/// `str` and `[u8]` have the same representation. [1] Neither `t` nor
216190
/// `r` contain `UnsafeCell`s because `[u8]` doesn't, and both `t` and
217191
/// `r` have that representation.
218-
/// - Since the closure takes a `&[u8]` argument, given a `Maybe<'a,
219-
/// str>` which satisfies the preconditions of
220-
/// `TryFromBytes::<str>::is_bit_valid`, it must be guaranteed that the
221-
/// memory referenced by that `MaybeValid` always contains a valid
222-
/// `[u8]`. Since `str`'s bytes are always initialized [1],
223-
/// `is_bit_valid`'s precondition requires that the same is true of its
224-
/// argument. Since `[u8]`'s only bit validity invariant is that its
225-
/// bytes must be initialized, this memory is guaranteed to contain a
226-
/// valid `[u8]`.
227192
/// - The impl must only return `true` for its argument if the original
228-
/// `Maybe<str>` refers to a valid `str`. `str::from_utf8`
229-
/// guarantees that it returns `Err` if its input is not a valid `str`.
230-
/// [2]
193+
/// `Maybe<str>` refers to a valid `str`. `str::from_utf8` guarantees
194+
/// that it returns `Err` if its input is not a valid `str`. [2]
231195
///
232196
/// [1] Per https://doc.rust-lang.org/1.81.0/reference/types/textual.html:
233197
///
@@ -296,18 +260,9 @@ safety_comment! {
296260
/// because `NonZeroXxx` and `xxx` have the same size. [1] Neither `r`
297261
/// nor `t` refer to any `UnsafeCell`s because neither `NonZeroXxx` [2]
298262
/// nor `xxx` do.
299-
/// - Since the closure takes a `&xxx` argument, given a `Maybe<'a,
300-
/// NonZeroXxx>` which satisfies the preconditions of
301-
/// `TryFromBytes::<NonZeroXxx>::is_bit_valid`, it must be guaranteed
302-
/// that the memory referenced by that `MabyeValid` always contains a
303-
/// valid `xxx`. Since `NonZeroXxx`'s bytes are always initialized [1],
304-
/// `is_bit_valid`'s precondition requires that the same is true of its
305-
/// argument. Since `xxx`'s only bit validity invariant is that its
306-
/// bytes must be initialized, this memory is guaranteed to contain a
307-
/// valid `xxx`.
308263
/// - The impl must only return `true` for its argument if the original
309-
/// `Maybe<NonZeroXxx>` refers to a valid `NonZeroXxx`. The only
310-
/// `xxx` which is not also a valid `NonZeroXxx` is 0. [1]
264+
/// `Maybe<NonZeroXxx>` refers to a valid `NonZeroXxx`. The only `xxx`
265+
/// which is not also a valid `NonZeroXxx` is 0. [1]
311266
///
312267
/// [1] Per https://doc.rust-lang.org/1.81.0/core/num/type.NonZeroU16.html:
313268
///

src/lib.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2845,7 +2845,12 @@ unsafe fn try_read_from<S, T: TryFromBytes>(
28452845
let c_ptr = Ptr::from_mut(&mut candidate);
28462846
let c_ptr = c_ptr.transparent_wrapper_into_inner();
28472847
// SAFETY: `c_ptr` has no uninitialized sub-ranges because it derived from
2848-
// `candidate`, which the caller promises is entirely initialized.
2848+
// `candidate`, which the caller promises is entirely initialized. Since
2849+
// `candidate` is a `MaybeUninit`, it has no validity requirements, and so
2850+
// no values written to `c_ptr` can violate its validity. Since `c_ptr` has
2851+
// `Exclusive` aliasing, no mutations may happen except via `c_ptr` so long
2852+
// as it is live, so we don't need to worry about the fact that `c_ptr` may
2853+
// have more restricted validity than `candidate`.
28492854
let c_ptr = unsafe { c_ptr.assume_validity::<invariant::Initialized>() };
28502855

28512856
// This call may panic. If that happens, it doesn't cause any soundness
@@ -4603,7 +4608,12 @@ pub unsafe trait FromBytes: FromZeros {
46034608

46044609
let ptr = Ptr::from_mut(&mut buf);
46054610
// SAFETY: After `buf.zero()`, `buf` consists entirely of initialized,
4606-
// zeroed bytes.
4611+
// zeroed bytes. Since `MaybeUninit` has no validity requirements, `ptr`
4612+
// cannot be used to write values which will violate `buf`'s bit
4613+
// validity. Since `ptr` has `Exclusive` aliasing, nothing other than
4614+
// `ptr` may be used to mutate `ptr`'s referent, and so its bit validity
4615+
// cannot be violated even though `buf` may have more permissive bit
4616+
// validity than `ptr`.
46074617
let ptr = unsafe { ptr.assume_validity::<invariant::Initialized>() };
46084618
let ptr = ptr.as_bytes::<BecauseExclusive>();
46094619
src.read_exact(ptr.as_mut())?;

src/pointer/invariant.rs

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,48 @@ pub trait Aliasing: Sealed {
4242
pub trait Alignment: Sealed {}
4343

4444
/// The validity invariant of a [`Ptr`][super::Ptr].
45-
pub trait Validity: Sealed {}
45+
///
46+
/// # Safety
47+
///
48+
/// In this section, we will use `Ptr<T, V>` as a shorthand for `Ptr<T, I:
49+
/// Invariants<Validity = V>>` for brevity.
50+
///
51+
/// Each `V: Validity` defines a set of bit values which may appear in the
52+
/// referent of a `Ptr<T, V>`, denoted `S(T, V)`. Each `V: Validity`, in its
53+
/// documentation, provides a definition of `S(T, V)` which must be valid for
54+
/// all `T: ?Sized`. Any `V: Validity` must guarantee that this set is only a
55+
/// function of the *bit validity* of the referent type, `T`, and not of any
56+
/// other property of `T`. As a consequence, given `V: Validity`, `T`, and `U`
57+
/// where `T` and `U` have the same bit validity, `S(V, T) = S(V, U)`.
58+
///
59+
/// It is guaranteed that the referent of any `ptr: Ptr<T, V>` is a member of
60+
/// `S(T, V)`. Unsafe code must ensure that this guarantee will be upheld for
61+
/// any existing `Ptr`s or any `Ptr`s that that code creates.
62+
///
63+
/// An important implication of this guarantee is that it restricts what
64+
/// transmutes are sound, where "transmute" is used in this context to refer to
65+
/// changing the referent type or validity invariant of a `Ptr`, as either
66+
/// change may change the set of bit values permitted to appear in the referent.
67+
/// In particular, the following are necessary (but not sufficient) conditions
68+
/// in order for a transmute from `src: Ptr<T, V>` to `dst: Ptr<U, W>` to be
69+
/// sound:
70+
/// - If `S(T, V) = S(U, W)`, then no restrictions apply; otherwise,
71+
/// - If `dst` permits mutation of its referent (e.g. via `Exclusive` aliasing
72+
/// or interior mutation under `Shared` aliasing), then it must hold that
73+
/// `S(T, V) ⊇ S(U, W)` - in other words, the transmute must not expand the
74+
/// set of allowed referent bit patterns. A violation of this requirement
75+
/// would permit using `dst` to write `x` where `x ∈ S(U, W)` but `x ∉ S(T,
76+
/// V)`, which would violate the guarantee that `src`'s referent may only
77+
/// contain values in `S(T, V)`.
78+
/// - If the referent may be mutated without going through `dst` while `dst` is
79+
/// live (e.g. via interior mutation on a `Shared`-aliased `Ptr` or `&`
80+
/// reference), then it must hold that `S(T, V) ⊆ S(U, W)` - in other words,
81+
/// the transmute must not shrink the set of allowed referent bit patterns. A
82+
/// violation of this requirement would permit using `src` or another
83+
/// mechanism (e.g. a `&` reference used to derive `src`) to write `x` where
84+
/// `x ∈ S(T, V)` but `x ∉ S(U, W)`, which would violate the guarantee that
85+
/// `dst`'s referent may only contain values in `S(U, W)`.
86+
pub unsafe trait Validity: Sealed {}
4687

4788
/// An [`Aliasing`] invariant which is either [`Shared`] or [`Exclusive`].
4889
///
@@ -90,9 +131,14 @@ impl Alignment for Aligned {}
90131
/// Any bit pattern is allowed in the `Ptr`'s referent, including uninitialized
91132
/// bytes.
92133
pub enum Uninit {}
93-
impl Validity for Uninit {}
94-
95-
/// The byte ranges initialized in `T` are also initialized in the referent.
134+
// SAFETY: `Uninit`'s validity is well-defined for all `T: ?Sized`, and is not a
135+
// function of any property of `T` other than its bit validity (in fact, it's
136+
// not even a property of `T`'s bit validity, but this is more than we are
137+
// required to uphold).
138+
unsafe impl Validity for Uninit {}
139+
140+
/// The byte ranges initialized in `T` are also initialized in the referent of a
141+
/// `Ptr<T>`.
96142
///
97143
/// Formally: uninitialized bytes may only be present in `Ptr<T>`'s referent
98144
/// where they are guaranteed to be present in `T`. This is a dynamic property:
@@ -119,16 +165,24 @@ impl Validity for Uninit {}
119165
/// enum type, in which case the same rules apply depending on the state of
120166
/// its discriminant, and so on recursively).
121167
pub enum AsInitialized {}
122-
impl Validity for AsInitialized {}
168+
// SAFETY: `AsInitialized`'s validity is well-defined for all `T: ?Sized`, and
169+
// is not a function of any property of `T` other than its bit validity.
170+
unsafe impl Validity for AsInitialized {}
123171

124172
/// The byte ranges in the referent are fully initialized. In other words, if
125173
/// the referent is `N` bytes long, then it contains a bit-valid `[u8; N]`.
126174
pub enum Initialized {}
127-
impl Validity for Initialized {}
175+
// SAFETY: `Initialized`'s validity is well-defined for all `T: ?Sized`, and is
176+
// not a function of any property of `T` other than its bit validity (in fact,
177+
// it's not even a property of `T`'s bit validity, but this is more than we are
178+
// required to uphold).
179+
unsafe impl Validity for Initialized {}
128180

129-
/// The referent is bit-valid for `T`.
181+
/// The referent of a `Ptr<T>` is bit-valid for `T`.
130182
pub enum Valid {}
131-
impl Validity for Valid {}
183+
// SAFETY: `Valid`'s validity is well-defined for all `T: ?Sized`, and is not a
184+
// function of any property of `T` other than its bit validity.
185+
unsafe impl Validity for Valid {}
132186

133187
/// # Safety
134188
///

src/pointer/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ where
5050
T: Copy,
5151
T: invariant::Read<Aliasing, R>,
5252
{
53-
// SAFETY: By invariant on `MaybeAligned`, `raw` contains
53+
// SAFETY: By invariant on `MaybeAligned`, `self` contains
5454
// validly-initialized data for `T`. By `T: Read<Aliasing>`, we are
5555
// permitted to perform a read of `self`'s referent.
5656
unsafe { self.as_inner().read_unaligned() }

0 commit comments

Comments
 (0)