Skip to content

Commit 4ef287c

Browse files
committed
Make SizeEq safe, introduce CastExact
Introduce `CastExact: Cast`, which denotes that a `Cast` exactly preserves the set of referent bytes. Add this bound to `SizeEq::CastFrom`, allowing `SizeEq` to be safe to implement. gherrit-pr-id: G57ec07c3841271440bbaf40cab04b942cbdbddb9
1 parent 59f812e commit 4ef287c

7 files changed

Lines changed: 203 additions & 133 deletions

File tree

src/impls.rs

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -436,29 +436,17 @@ mod atomics {
436436
// the same size and bit validity.
437437
unsafe impl<$($tyvar)?> TransmuteFrom<$prim, Valid, Valid> for $atomic {}
438438

439-
// SAFETY: The caller promised that `$atomic` and `$prim` have
440-
// the same size.
441-
unsafe impl<$($tyvar)?> SizeEq<$atomic> for $prim {
442-
type CastFrom = $crate::pointer::cast::CastSized;
439+
impl<$($tyvar)?> SizeEq<$atomic> for $prim {
440+
type CastFrom = $crate::pointer::cast::CastSizedExact;
443441
}
444-
// SAFETY: See previous safety comment.
445-
unsafe impl<$($tyvar)?> SizeEq<$prim> for $atomic {
446-
type CastFrom = $crate::pointer::cast::CastSized;
442+
impl<$($tyvar)?> SizeEq<$prim> for $atomic {
443+
type CastFrom = $crate::pointer::cast::CastSizedExact;
447444
}
448-
// SAFETY: The caller promised that `$atomic` and `$prim` have
449-
// the same size. `UnsafeCell<T>` has the same size as `T` [1].
450-
//
451-
// [1] Per https://doc.rust-lang.org/1.85.0/std/cell/struct.UnsafeCell.html#memory-layout:
452-
//
453-
// `UnsafeCell<T>` has the same in-memory representation as
454-
// its inner type `T`. A consequence of this guarantee is that
455-
// it is possible to convert between `T` and `UnsafeCell<T>`.
456-
unsafe impl<$($tyvar)?> SizeEq<$atomic> for UnsafeCell<$prim> {
457-
type CastFrom = $crate::pointer::cast::CastSized;
445+
impl<$($tyvar)?> SizeEq<$atomic> for UnsafeCell<$prim> {
446+
type CastFrom = $crate::pointer::cast::CastSizedExact;
458447
}
459-
// SAFETY: See previous safety comment.
460-
unsafe impl<$($tyvar)?> SizeEq<UnsafeCell<$prim>> for $atomic {
461-
type CastFrom = $crate::pointer::cast::CastSized;
448+
impl<$($tyvar)?> SizeEq<UnsafeCell<$prim>> for $atomic {
449+
type CastFrom = $crate::pointer::cast::CastSizedExact;
462450
}
463451

464452
// SAFETY: The caller promised that `$atomic` and `$prim` have

src/layout.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,20 @@ mod cast_from {
756756
{
757757
}
758758

759+
// SAFETY: FIXME(#1818, #2701): THIS IS UNSOUND. However, given how it's
760+
// used today, it can't cause UB. In particular, the static assertions below
761+
// only check that source alignment is not less than destination alignment,
762+
// but not that they are equal. This means that a pointer cast could result
763+
// in a referent with less trailing padding. This technically violates the
764+
// safety invariant of `CastExact`, but can only result in UB if the padding
765+
// bytes are read, which they never are. Obviously we should still fix this.
766+
unsafe impl<Src, Dst> crate::pointer::cast::CastExact<Src, Dst> for CastFrom<Dst>
767+
where
768+
Src: KnownLayout<PointerMetadata = usize> + ?Sized,
769+
Dst: KnownLayout<PointerMetadata = usize> + ?Sized,
770+
{
771+
}
772+
759773
// SAFETY: `project` produces a pointer which refers to the same referent
760774
// bytes as its input, or to a subset of them (see inline comments for a
761775
// more detailed proof of this). It does this using provenance-preserving
@@ -771,7 +785,7 @@ mod cast_from {
771785
/// implement soundly.
772786
//
773787
// FIXME(#1817): Support Sized->Unsized and Unsized->Sized casts
774-
fn project(src: PtrInner<'_, Src>) -> *mut Dst {
788+
fn project_inner(src: PtrInner<'_, Src>) -> *mut Dst {
775789
// At compile time (specifically, post-monomorphization time), we
776790
// need to compute two things:
777791
// - Whether, given *any* `*Src`, it is possible to construct a

src/pointer/inner.rs

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -176,39 +176,11 @@ impl<'a, T: ?Sized> PtrInner<'a, T> {
176176
unsafe { Self::new(ptr) }
177177
}
178178

179-
/// # Safety
180-
///
181-
/// The caller may assume that the resulting `PtrInner` addresses a subset
182-
/// of the bytes of `self`'s referent.
179+
/// A shorthand for `C::project(self)`.
183180
#[must_use]
184181
#[inline(always)]
185182
pub fn project<U: ?Sized, C: cast::Project<T, U>>(self) -> PtrInner<'a, U> {
186-
let projected_raw = C::project(self);
187-
188-
// SAFETY: `self`'s referent lives at a `NonNull` address, and is either
189-
// zero-sized or lives in an allocation. In either case, it does not
190-
// wrap around the address space [1], and so none of the addresses
191-
// contained in it or one-past-the-end of it are null.
192-
//
193-
// By invariant on `C: Project`, `C::project` is a provenance-preserving
194-
// projection which preserves or shrinks the set of referent bytes, so
195-
// `projected_raw` references a subset of `self`'s referent, and so it
196-
// cannot be null.
197-
//
198-
// [1] https://doc.rust-lang.org/1.92.0/std/ptr/index.html#allocation
199-
let projected_non_null = unsafe { NonNull::new_unchecked(projected_raw) };
200-
201-
// SAFETY: As described in the preceding safety comment, `projected_raw`,
202-
// and thus `projected_non_null`, addresses a subset of `self`'s
203-
// referent. Thus, `projected_non_null` either:
204-
// - Addresses zero bytes or,
205-
// - Addresses a subset of the referent of `self`. In this case, `self`
206-
// has provenance for its referent, which lives in an allocation.
207-
// Since `projected_non_null` was constructed using a sequence of
208-
// provenance-preserving operations, it also has provenance for its
209-
// referent and that referent lives in an allocation. By invariant on
210-
// `self`, that allocation lives for `'a`.
211-
unsafe { PtrInner::new(projected_non_null) }
183+
C::project(self)
212184
}
213185
}
214186

src/pointer/mod.rs

Lines changed: 126 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,45 @@ pub mod cast {
6060
///
6161
/// The returned pointer refers to a non-strict subset of the bytes of
6262
/// `src`'s referent, and has the same provenance as `src`.
63-
fn project(src: PtrInner<'_, Src>) -> *mut Dst;
63+
#[must_use]
64+
fn project_inner(src: PtrInner<'_, Src>) -> *mut Dst;
65+
66+
/// Projects a [`PtrInner`] from `Src` to `Dst`.
67+
///
68+
/// # Safety
69+
///
70+
/// The caller may assume that the resulting `PtrInner` addresses a
71+
/// subset of the bytes of `src`'s referent.
72+
#[must_use]
73+
#[inline(always)]
74+
fn project(src: PtrInner<'_, Src>) -> PtrInner<'_, Dst> {
75+
let projected_raw = Self::project_inner(src);
76+
77+
// SAFETY: `src`'s referent lives at a `NonNull` address, and is
78+
// either zero-sized or lives in an allocation. In either case, it
79+
// does not wrap around the address space [1], and so none of the
80+
// addresses contained in it or one-past-the-end of it are null.
81+
//
82+
// By invariant on `Self: Project`, `Self::project` is a
83+
// provenance-preserving projection which preserves or shrinks the
84+
// set of referent bytes, so `projected_raw` references a subset of
85+
// `src`'s referent, and so it cannot be null.
86+
//
87+
// [1] https://doc.rust-lang.org/1.92.0/std/ptr/index.html#allocation
88+
let projected_non_null = unsafe { core::ptr::NonNull::new_unchecked(projected_raw) };
89+
90+
// SAFETY: As described in the preceding safety comment, `projected_raw`,
91+
// and thus `projected_non_null`, addresses a subset of `src`'s
92+
// referent. Thus, `projected_non_null` either:
93+
// - Addresses zero bytes or,
94+
// - Addresses a subset of the referent of `src`. In this case, `src`
95+
// has provenance for its referent, which lives in an allocation.
96+
// Since `projected_non_null` was constructed using a sequence of
97+
// provenance-preserving operations, it also has provenance for its
98+
// referent and that referent lives in an allocation. By invariant on
99+
// `src`, that allocation lives for `'a`.
100+
unsafe { PtrInner::new(projected_non_null) }
101+
}
64102
}
65103

66104
/// A [`Project`] which preserves the address of the referent – a pointer
@@ -72,6 +110,13 @@ pub mod cast {
72110
/// shrink the set of referent bytes, and it may change the referent's type.
73111
pub unsafe trait Cast<Src: ?Sized, Dst: ?Sized>: Project<Src, Dst> {}
74112

113+
/// A [`Cast`] which does not shrink the set of referent bytes.
114+
///
115+
/// # Safety
116+
///
117+
/// A `CastExact` projection must preserve the set of referent bytes.
118+
pub unsafe trait CastExact<Src: ?Sized, Dst: ?Sized>: Cast<Src, Dst> {}
119+
75120
/// A no-op pointer cast.
76121
#[derive(Default, Copy, Clone)]
77122
#[allow(missing_debug_implementations)]
@@ -82,14 +127,17 @@ pub mod cast {
82127
// bytes.
83128
unsafe impl<T: ?Sized> Project<T, T> for IdCast {
84129
#[inline(always)]
85-
fn project(src: PtrInner<'_, T>) -> *mut T {
130+
fn project_inner(src: PtrInner<'_, T>) -> *mut T {
86131
src.as_ptr()
87132
}
88133
}
89134

90135
// SAFETY: The `Project::project` impl preserves referent address.
91136
unsafe impl<T: ?Sized> Cast<T, T> for IdCast {}
92137

138+
// SAFETY: The `Project::project` impl preserves referent size.
139+
unsafe impl<T: ?Sized> CastExact<T, T> for IdCast {}
140+
93141
/// A pointer cast which preserves or shrinks the set of referent bytes of
94142
/// a statically-sized referent.
95143
///
@@ -107,7 +155,7 @@ pub mod cast {
107155
// operations preserve provenance.
108156
unsafe impl<Src, Dst> Project<Src, Dst> for CastSized {
109157
#[inline(always)]
110-
fn project(src: PtrInner<'_, Src>) -> *mut Dst {
158+
fn project_inner(src: PtrInner<'_, Src>) -> *mut Dst {
111159
static_assert!(Src, Dst => mem::size_of::<Src>() >= mem::size_of::<Dst>());
112160
src.as_ptr().cast::<Dst>()
113161
}
@@ -116,6 +164,37 @@ pub mod cast {
116164
// SAFETY: The `Project::project` impl preserves referent address.
117165
unsafe impl<Src, Dst> Cast<Src, Dst> for CastSized {}
118166

167+
/// A pointer cast which preserves the set of referent bytes of a
168+
/// statically-sized referent.
169+
///
170+
/// # Safety
171+
///
172+
/// The implementation of [`Project`] uses a compile-time assertion to
173+
/// guarantee that `Dst` has the same size as `Src`. Thus, `CastSizedExact`
174+
/// has a sound implementation of [`Project`] for all `Src` and `Dst` – the
175+
/// caller may pass any `Src` and `Dst` without being responsible for
176+
/// soundness.
177+
#[allow(missing_debug_implementations, missing_copy_implementations)]
178+
pub enum CastSizedExact {}
179+
180+
// SAFETY: By the `static_assert!`, `Dst` has the same size as `Src`,
181+
// and so all casts preserve the set of referent bytes. All operations
182+
// preserve provenance.
183+
unsafe impl<Src, Dst> Project<Src, Dst> for CastSizedExact {
184+
#[inline(always)]
185+
fn project_inner(src: PtrInner<'_, Src>) -> *mut Dst {
186+
static_assert!(Src, Dst => mem::size_of::<Src>() == mem::size_of::<Dst>());
187+
src.as_ptr().cast::<Dst>()
188+
}
189+
}
190+
191+
// SAFETY: The `Project::project` impl preserves referent address.
192+
unsafe impl<Src, Dst> Cast<Src, Dst> for CastSizedExact {}
193+
194+
// SAFETY: By the `static_assert!`, `Project::project` impl preserves
195+
// referent size.
196+
unsafe impl<Src, Dst> CastExact<Src, Dst> for CastSizedExact {}
197+
119198
/// A pointer cast which preserves or shrinks the set of referent bytes of
120199
/// a dynamically-sized referent.
121200
///
@@ -129,30 +208,34 @@ pub mod cast {
129208
#[allow(missing_debug_implementations, missing_copy_implementations)]
130209
pub enum CastUnsized {}
131210

132-
// SAFETY: The `static_assert!` ensures that `Src` and `Dst` have the same
133-
// `SizeInfo`. Thus, casting preserves the set of referent bytes. All
134-
// operations are provenance-preserving.
211+
// SAFETY: By the `static_assert!`, `Src` and `Dst` are either:
212+
// - Both sized and equal in size
213+
// - Both slice DSTs with the same alignment, trailing slice offset, and
214+
// element size. These ensure that any given pointer metadata encodes the
215+
// same size for both `Src` and `Dst` (note that the alignment is required
216+
// as it affects the amount of trailing padding).
135217
unsafe impl<Src, Dst> Project<Src, Dst> for CastUnsized
136218
where
137219
Src: ?Sized + KnownLayout,
138220
Dst: ?Sized + KnownLayout<PointerMetadata = Src::PointerMetadata>,
139221
{
140222
#[inline(always)]
141-
fn project(src: PtrInner<'_, Src>) -> *mut Dst {
223+
fn project_inner(src: PtrInner<'_, Src>) -> *mut Dst {
142224
// FIXME:
143225
// - Is the alignment check necessary for soundness? It's not
144226
// necessary for the soundness of the `Project` impl, but what
145227
// about the soundness of particular use sites?
146-
// - Do we want this to support shrinking casts as well?
228+
// - Do we want this to support shrinking casts as well? If so,
229+
// we'll need to remove the `CastExact` impl.
147230
static_assert!(Src: ?Sized + KnownLayout, Dst: ?Sized + KnownLayout => {
148231
let t = <Src as KnownLayout>::LAYOUT;
149232
let u = <Dst as KnownLayout>::LAYOUT;
150-
t.align.get() >= u.align.get() && match (t.size_info, u.size_info) {
233+
match (t.size_info, u.size_info) {
151234
(SizeInfo::Sized { size: t }, SizeInfo::Sized { size: u }) => t == u,
152235
(
153236
SizeInfo::SliceDst(TrailingSliceLayout { offset: t_offset, elem_size: t_elem_size }),
154237
SizeInfo::SliceDst(TrailingSliceLayout { offset: u_offset, elem_size: u_elem_size })
155-
) => t_offset == u_offset && t_elem_size == u_elem_size,
238+
) => t.align.get() >= u.align.get() && t_offset == u_offset && t_elem_size == u_elem_size,
156239
_ => false,
157240
}
158241
});
@@ -170,6 +253,20 @@ pub mod cast {
170253
{
171254
}
172255

256+
// SAFETY: By the `static_assert!` in `Project::project`, `Src` and `Dst`
257+
// are either:
258+
// - Both sized and equal in size
259+
// - Both slice DSTs with the same alignment, trailing slice offset, and
260+
// element size. These ensure that any given pointer metadata encodes the
261+
// same size for both `Src` and `Dst` (note that the alignment is required
262+
// as it affects the amount of trailing padding).
263+
unsafe impl<Src, Dst> CastExact<Src, Dst> for CastUnsized
264+
where
265+
Src: ?Sized + KnownLayout,
266+
Dst: ?Sized + KnownLayout<PointerMetadata = Src::PointerMetadata>,
267+
{
268+
}
269+
173270
/// A field projection
174271
///
175272
/// A `Projection` is a [`Project`] which implements projection by
@@ -188,7 +285,7 @@ pub mod cast {
188285
T: HasField<F, VARIANT_ID, FIELD_ID>,
189286
{
190287
#[inline(always)]
191-
fn project(src: PtrInner<'_, T>) -> *mut T::Type {
288+
fn project_inner(src: PtrInner<'_, T>) -> *mut T::Type {
192289
T::project(src)
193290
}
194291
}
@@ -221,7 +318,7 @@ pub mod cast {
221318
UV: Project<U, V>,
222319
{
223320
#[inline(always)]
224-
fn project(t: PtrInner<'_, T>) -> *mut V {
321+
fn project_inner(t: PtrInner<'_, T>) -> *mut V {
225322
t.project::<_, TU>().project::<_, UV>().as_ptr()
226323
}
227324
}
@@ -239,6 +336,19 @@ pub mod cast {
239336
{
240337
}
241338

339+
// SAFETY: Since the `Project::project` impl delegates to `TU::project` and
340+
// `UV::project`, and since `TU` and `UV` are `CastExact`, the `Project::project`
341+
// impl preserves the set of referent bytes.
342+
unsafe impl<T, U, V, TU, UV> CastExact<T, V> for TransitiveProject<U, TU, UV>
343+
where
344+
T: ?Sized,
345+
U: ?Sized,
346+
V: ?Sized,
347+
TU: CastExact<T, U>,
348+
UV: CastExact<U, V>,
349+
{
350+
}
351+
242352
/// A cast from `T` to `[u8]`.
243353
pub(crate) struct AsBytesCast;
244354

@@ -251,7 +361,7 @@ pub mod cast {
251361
// true of other proofs in this codebase). Is this guaranteed anywhere?
252362
unsafe impl<T: ?Sized + KnownLayout> Project<T, [u8]> for AsBytesCast {
253363
#[inline(always)]
254-
fn project(src: PtrInner<'_, T>) -> *mut [u8] {
364+
fn project_inner(src: PtrInner<'_, T>) -> *mut [u8] {
255365
let bytes = match T::size_of_val_raw(src.as_non_null()) {
256366
Some(bytes) => bytes,
257367
// SAFETY: `KnownLayout::size_of_val_raw` promises to always
@@ -268,4 +378,7 @@ pub mod cast {
268378

269379
// SAFETY: The `Project::project` impl preserves referent address.
270380
unsafe impl<T: ?Sized + KnownLayout> Cast<T, [u8]> for AsBytesCast {}
381+
382+
// SAFETY: The `Project::project` impl preserves the set of referent bytes.
383+
unsafe impl<T: ?Sized + KnownLayout> CastExact<T, [u8]> for AsBytesCast {}
271384
}

0 commit comments

Comments
 (0)