Skip to content

Commit 644cdb8

Browse files
authored
Invariant-parameterize Ptr and make is_bit_valid safe (#699)
Closes #715.
1 parent a84bdaa commit 644cdb8

File tree

7 files changed

+1551
-922
lines changed

7 files changed

+1551
-922
lines changed

src/lib.rs

+392-262
Large diffs are not rendered by default.

src/macros.rs

+22-31
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,14 @@ macro_rules! safety_comment {
5252
/// $ty>` which satisfies the preconditions of
5353
/// `TryFromBytes::<$ty>::is_bit_valid`, it must be guaranteed that the
5454
/// memory referenced by that `Ptr` always contains a valid `$repr`.
55-
/// - The alignment of `$repr` is less than or equal to the alignment of
56-
/// `$ty`.
5755
/// - The impl of `is_bit_valid` must only return `true` for its argument
5856
/// `Ptr<$repr>` if the original `Ptr<$ty>` refers to a valid `$ty`.
5957
macro_rules! unsafe_impl {
6058
// Implement `$trait` for `$ty` with no bounds.
61-
($(#[$attr:meta])* $ty:ty: $trait:ident $(; |$candidate:ident: &$repr:ty| $is_bit_valid:expr)?) => {
59+
($(#[$attr:meta])* $ty:ty: $trait:ident $(; |$candidate:ident: MaybeAligned<$repr:ty>| $is_bit_valid:expr)?) => {
6260
$(#[$attr])*
6361
unsafe impl $trait for $ty {
64-
unsafe_impl!(@method $trait $(; |$candidate: &$repr| $is_bit_valid)?);
62+
unsafe_impl!(@method $trait $(; |$candidate: MaybeAligned<$repr>| $is_bit_valid)?);
6563
}
6664
};
6765
// Implement all `$traits` for `$ty` with no bounds.
@@ -97,93 +95,86 @@ macro_rules! unsafe_impl {
9795
$(#[$attr:meta])*
9896
const $constname:ident : $constty:ident $(,)?
9997
$($tyvar:ident $(: $(? $optbound:ident $(+)?)* $($bound:ident $(+)?)* )?),*
100-
=> $trait:ident for $ty:ty $(; |$candidate:ident $(: &$ref_repr:ty)? $(: Ptr<$ptr_repr:ty>)?| $is_bit_valid:expr)?
98+
=> $trait:ident for $ty:ty $(; |$candidate:ident $(: MaybeAligned<$ref_repr:ty>)? $(: Maybe<$ptr_repr:ty>)?| $is_bit_valid:expr)?
10199
) => {
102100
unsafe_impl!(
103101
@inner
104102
$(#[$attr])*
105103
@const $constname: $constty,
106104
$($tyvar $(: $(? $optbound +)* + $($bound +)*)?,)*
107-
=> $trait for $ty $(; |$candidate $(: &$ref_repr)? $(: Ptr<$ptr_repr>)?| $is_bit_valid)?
105+
=> $trait for $ty $(; |$candidate $(: MaybeAligned<$ref_repr>)? $(: Maybe<$ptr_repr>)?| $is_bit_valid)?
108106
);
109107
};
110108
(
111109
$(#[$attr:meta])*
112110
$($tyvar:ident $(: $(? $optbound:ident $(+)?)* $($bound:ident $(+)?)* )?),*
113-
=> $trait:ident for $ty:ty $(; |$candidate:ident $(: &$ref_repr:ty)? $(: Ptr<$ptr_repr:ty>)?| $is_bit_valid:expr)?
111+
=> $trait:ident for $ty:ty $(; |$candidate:ident $(: MaybeAligned<$ref_repr:ty>)? $(: Maybe<$ptr_repr:ty>)?| $is_bit_valid:expr)?
114112
) => {
115113
unsafe_impl!(
116114
@inner
117115
$(#[$attr])*
118116
$($tyvar $(: $(? $optbound +)* + $($bound +)*)?,)*
119-
=> $trait for $ty $(; |$candidate $(: &$ref_repr)? $(: Ptr<$ptr_repr>)?| $is_bit_valid)?
117+
=> $trait for $ty $(; |$candidate $(: MaybeAligned<$ref_repr>)? $(: Maybe<$ptr_repr>)?| $is_bit_valid)?
120118
);
121119
};
122120
(
123121
@inner
124122
$(#[$attr:meta])*
125123
$(@const $constname:ident : $constty:ident,)*
126124
$($tyvar:ident $(: $(? $optbound:ident +)* + $($bound:ident +)* )?,)*
127-
=> $trait:ident for $ty:ty $(; |$candidate:ident $(: &$ref_repr:ty)? $(: Ptr<$ptr_repr:ty>)?| $is_bit_valid:expr)?
125+
=> $trait:ident for $ty:ty $(; |$candidate:ident $(: MaybeAligned<$ref_repr:ty>)? $(: Maybe<$ptr_repr:ty>)?| $is_bit_valid:expr)?
128126
) => {
129127
$(#[$attr])*
130128
unsafe impl<$(const $constname: $constty,)* $($tyvar $(: $(? $optbound +)* $($bound +)*)?),*> $trait for $ty {
131-
unsafe_impl!(@method $trait $(; |$candidate: $(&$ref_repr)? $(Ptr<$ptr_repr>)?| $is_bit_valid)?);
129+
unsafe_impl!(@method $trait $(; |$candidate: $(MaybeAligned<$ref_repr>)? $(Maybe<$ptr_repr>)?| $is_bit_valid)?);
132130
}
133131
};
134132

135-
(@method TryFromBytes ; |$candidate:ident: &$repr:ty| $is_bit_valid:expr) => {
133+
(@method TryFromBytes ; |$candidate:ident: MaybeAligned<$repr:ty>| $is_bit_valid:expr) => {
136134
#[allow(clippy::missing_inline_in_public_items)]
137135
fn only_derive_is_allowed_to_implement_this_trait() {}
138136

139137
#[inline]
140-
unsafe fn is_bit_valid(candidate: Ptr<'_, Self>) -> bool {
138+
fn is_bit_valid(candidate: Maybe<'_, Self>) -> bool {
141139
// SAFETY:
142140
// - The argument to `cast_unsized` is `|p| p as *mut _` as required
143141
// by that method's safety precondition.
144142
// - The caller has promised that the cast results in an object of
145143
// equal or lesser size.
146-
// - The caller has promised that `$repr`'s alignment is less than
147-
// or equal to `Self`'s alignment.
148144
#[allow(clippy::as_conversions)]
149145
let candidate = unsafe { candidate.cast_unsized::<$repr, _>(|p| p as *mut _) };
150-
// SAFETY:
151-
// - The caller has promised that the referenced memory region will
152-
// contain a valid `$repr` for `'a`.
153-
// - The memory may not be referenced by any mutable references.
154-
// This is a precondition of `is_bit_valid`.
155-
// - The memory may not be mutated even via `UnsafeCell`s. This is a
156-
// precondition of `is_bit_valid`.
157-
// - There must not exist any references to the same memory region
158-
// which contain `UnsafeCell`s at byte ranges which are not
159-
// identical to the byte ranges at which `T` contains
160-
// `UnsafeCell`s. This is a precondition of `is_bit_valid`.
161-
let $candidate: &$repr = unsafe { candidate.as_ref() };
146+
147+
// SAFETY: The caller has promised that the referenced memory region
148+
// will contain a valid `$repr`.
149+
let $candidate = unsafe { candidate.assume_valid() };
162150
$is_bit_valid
163151
}
164152
};
165-
(@method TryFromBytes ; |$candidate:ident: Ptr<$repr:ty>| $is_bit_valid:expr) => {
153+
(@method TryFromBytes ; |$candidate:ident: Maybe<$repr:ty>| $is_bit_valid:expr) => {
166154
#[allow(clippy::missing_inline_in_public_items)]
167155
fn only_derive_is_allowed_to_implement_this_trait() {}
168156

169157
#[inline]
170-
unsafe fn is_bit_valid(candidate: Ptr<'_, Self>) -> bool {
158+
fn is_bit_valid(candidate: Maybe<'_, Self>) -> bool {
171159
// SAFETY:
172160
// - The argument to `cast_unsized` is `|p| p as *mut _` as required
173161
// by that method's safety precondition.
174162
// - The caller has promised that the cast results in an object of
175163
// equal or lesser size.
176-
// - The caller has promised that `$repr`'s alignment is less than
177-
// or equal to `Self`'s alignment.
178164
#[allow(clippy::as_conversions)]
179165
let $candidate = unsafe { candidate.cast_unsized::<$repr, _>(|p| p as *mut _) };
166+
167+
// SAFETY: The caller has promised that `$repr` is as-initialized as
168+
// `Self`.
169+
let $candidate = unsafe { $candidate.assume_as_initialized() };
170+
180171
$is_bit_valid
181172
}
182173
};
183174
(@method TryFromBytes) => {
184175
#[allow(clippy::missing_inline_in_public_items)]
185176
fn only_derive_is_allowed_to_implement_this_trait() {}
186-
#[inline(always)] unsafe fn is_bit_valid(_: Ptr<'_, Self>) -> bool { true }
177+
#[inline(always)] fn is_bit_valid(_: Maybe<'_, Self>) -> bool { true }
187178
};
188179
(@method $trait:ident) => {
189180
#[allow(clippy::missing_inline_in_public_items)]

src/pointer/mod.rs

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright 2023 The Fuchsia Authors
2+
//
3+
// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4+
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5+
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6+
// This file may not be copied, modified, or distributed except according to
7+
// those terms.
8+
9+
//! Abstractions over raw pointers.
10+
11+
mod ptr;
12+
13+
pub use ptr::{invariant, Ptr};
14+
15+
use crate::{TryFromBytes, Unaligned};
16+
17+
/// A shorthand for a maybe-valid, maybe-aligned reference. Used as the argument
18+
/// to [`TryFromBytes::is_bit_valid`].
19+
pub type Maybe<'a, T, Alignment = invariant::AnyAlignment> =
20+
Ptr<'a, T, (invariant::Shared, Alignment, invariant::AsInitialized)>;
21+
22+
// These methods are defined on the type alias, `Maybe`, so as to bring them to
23+
// the forefront of the rendered rustdoc.
24+
impl<'a, T, Alignment> Maybe<'a, T, Alignment>
25+
where
26+
T: 'a + ?Sized + TryFromBytes,
27+
Alignment: invariant::Alignment,
28+
{
29+
/// Checks that `Ptr`'s referent is validly initialized for `T`.
30+
///
31+
/// # Panics
32+
///
33+
/// This method will panic if
34+
/// [`T::is_bit_valid`][TryFromBytes::is_bit_valid] panics.
35+
#[inline]
36+
pub(crate) fn check_valid(self) -> Option<MaybeAligned<'a, T, Alignment>> {
37+
// This call may panic. If that happens, it doesn't cause any soundness
38+
// issues, as we have not generated any invalid state which we need to
39+
// fix before returning.
40+
if T::is_bit_valid(self.forget_aligned()) {
41+
// SAFETY: If `T::is_bit_valid`, code may assume that `self`
42+
// contains a bit-valid instance of `Self`.
43+
Some(unsafe { self.assume_valid() })
44+
} else {
45+
None
46+
}
47+
}
48+
}
49+
50+
/// A semi-user-facing wrapper type representing a maybe-aligned reference, for
51+
/// use in [`TryFromBytes::is_bit_valid`].
52+
pub type MaybeAligned<'a, T, Alignment = invariant::AnyAlignment> =
53+
Ptr<'a, T, (invariant::Shared, Alignment, invariant::Valid)>;
54+
55+
// These methods are defined on the type alias, `MaybeAligned`, so as to bring
56+
// them to the forefront of the rendered rustdoc for that type alias.
57+
impl<'a, T, Alignment> MaybeAligned<'a, T, Alignment>
58+
where
59+
T: 'a + ?Sized,
60+
Alignment: invariant::Alignment,
61+
{
62+
/// Reads the value from `MaybeAligned`.
63+
#[inline]
64+
pub fn read_unaligned(self) -> T
65+
where
66+
T: Copy,
67+
{
68+
let raw = self.as_non_null().as_ptr();
69+
// SAFETY: By invariant on `MaybeAligned`, `raw` contains
70+
// validly-initialized data for `T`. The value is safe to read and
71+
// return, because `T` is copy.
72+
unsafe { core::ptr::read_unaligned(raw) }
73+
}
74+
75+
/// Views the value as an aligned reference.
76+
///
77+
/// This is only available if `T` is [`Unaligned`].
78+
#[inline]
79+
pub fn unaligned_as_ref(self) -> &'a T
80+
where
81+
T: Unaligned,
82+
{
83+
// SAFETY: The alignment of `T` is 1 and thus is always aligned
84+
// because `T: Unaligned`.
85+
let ptr = unsafe { self.assume_aligned() };
86+
ptr.as_ref()
87+
}
88+
}

0 commit comments

Comments
 (0)