Skip to content

Commit f0a5b8c

Browse files
committed
Make SizeEq safe
Make `SizeEq` safe by moving its safety invariant to a new `CastExact` trait which now bounds the associated `SizeEq::CastFrom` type. This permits us to write safety proofs in fewer places, since there are certain `CastFrom` impls which we can re-use for many `SizeEq` impls. Makes progress on #2701, #1940, #1852 gherrit-pr-id: Gda7c06c9a8e54f5afd6aa5a093406a7b71259fd3
1 parent eb8f348 commit f0a5b8c

File tree

5 files changed

+135
-64
lines changed

5 files changed

+135
-64
lines changed

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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,15 @@ mod cast_from {
756756
{
757757
}
758758

759+
// SAFETY: The implementation of `Project::project` preserves the size of
760+
// the referent (see inline comments for a more detailed proof of this).
761+
unsafe impl<Src, Dst> crate::pointer::cast::CastExact<Src, Dst> for CastFrom<Dst>
762+
where
763+
Src: KnownLayout<PointerMetadata = usize> + ?Sized,
764+
Dst: KnownLayout<PointerMetadata = usize> + ?Sized,
765+
{
766+
}
767+
759768
// SAFETY: `project` produces a pointer which refers to the same referent
760769
// bytes as its input, or to a subset of them (see inline comments for a
761770
// more detailed proof of this). It does this using provenance-preserving

src/pointer/mod.rs

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ pub mod cast {
7878
/// shrink the set of referent bytes, and it may change the referent's type.
7979
pub unsafe trait Cast<Src: ?Sized, Dst: ?Sized>: Project<Src, Dst> {}
8080

81+
/// A [`Cast`] which does not shrink the set of referent bytes.
82+
///
83+
/// # Safety
84+
///
85+
/// A `CastExact` projection must preserve the set of referent bytes.
86+
pub unsafe trait CastExact<Src: ?Sized, Dst: ?Sized>: Cast<Src, Dst> {}
87+
8188
/// A no-op pointer cast.
8289
#[derive(Default, Copy, Clone)]
8390
#[allow(missing_debug_implementations)]
@@ -96,6 +103,9 @@ pub mod cast {
96103
// SAFETY: The `Project::project` impl preserves referent address.
97104
unsafe impl<T: ?Sized> Cast<T, T> for IdCast {}
98105

106+
// SAFETY: The `Project::project` impl preserves referent size.
107+
unsafe impl<T: ?Sized> CastExact<T, T> for IdCast {}
108+
99109
/// A pointer cast which preserves or shrinks the set of referent bytes of
100110
/// a statically-sized referent.
101111
///
@@ -122,6 +132,37 @@ pub mod cast {
122132
// SAFETY: The `Project::project` impl preserves referent address.
123133
unsafe impl<Src, Dst> Cast<Src, Dst> for CastSized {}
124134

135+
/// A pointer cast which preserves the set of referent bytes of a
136+
/// statically-sized referent.
137+
///
138+
/// # Safety
139+
///
140+
/// The implementation of [`Project`] uses a compile-time assertion to
141+
/// guarantee that `Dst` has the same size as `Src`. Thus, `CastSizedExact`
142+
/// has a sound implementation of [`Project`] for all `Src` and `Dst` – the
143+
/// caller may pass any `Src` and `Dst` without being responsible for
144+
/// soundness.
145+
#[allow(missing_debug_implementations, missing_copy_implementations)]
146+
pub enum CastSizedExact {}
147+
148+
// SAFETY: By the `static_assert!`, `Dst` has the same size as `Src`,
149+
// and so all casts preserve the set of referent bytes. All operations
150+
// preserve provenance.
151+
unsafe impl<Src, Dst> Project<Src, Dst> for CastSizedExact {
152+
#[inline(always)]
153+
fn project(src: PtrInner<'_, Src>) -> *mut Dst {
154+
static_assert!(Src, Dst => mem::size_of::<Src>() == mem::size_of::<Dst>());
155+
src.as_ptr().cast::<Dst>()
156+
}
157+
}
158+
159+
// SAFETY: The `Project::project_raw` impl preserves referent address.
160+
unsafe impl<Src, Dst> Cast<Src, Dst> for CastSizedExact {}
161+
162+
// SAFETY: By the `static_assert!`, `Project::project_raw` impl preserves
163+
// referent size.
164+
unsafe impl<Src, Dst> CastExact<Src, Dst> for CastSizedExact {}
165+
125166
/// A pointer cast which preserves or shrinks the set of referent bytes of
126167
/// a dynamically-sized referent.
127168
///
@@ -138,19 +179,19 @@ pub mod cast {
138179
// SAFETY: By the `static_assert!`, `Src` and `Dst` are either:
139180
// - Both sized and equal in size
140181
// - Both slice DSTs with the same trailing slice offset and element size
141-
// and with align_of::<Src>() >= align_of::<Dst>(). These ensure that any
142-
// given pointer metadata encodes the same size or more size for `Src`
143-
// than for `Dst` (note that the alignment is required as it affects the
144-
// amount of trailing padding). Thus, `project` preserves or shrinks the
145-
// set of referent bytes.
182+
// and with align_of::<Src>() == align_of::<Dst>(). These ensure that any
183+
// given pointer metadata encodes the same size for both `Src` and `Dst`
184+
// (note that the alignment is required as it affects the amount of
185+
// trailing padding). Thus, `project` preserves the set of referent bytes.
146186
unsafe impl<Src, Dst> Project<Src, Dst> for CastUnsized
147187
where
148188
Src: ?Sized + KnownLayout,
149189
Dst: ?Sized + KnownLayout<PointerMetadata = Src::PointerMetadata>,
150190
{
151191
#[inline(always)]
152192
fn project(src: PtrInner<'_, Src>) -> *mut Dst {
153-
// FIXME: Do we want this to support shrinking casts as well?
193+
// FIXME: Do we want this to support shrinking casts as well? If so,
194+
// we'll need to remove the `CastExact` impl.
154195
static_assert!(Src: ?Sized + KnownLayout, Dst: ?Sized + KnownLayout => {
155196
let src = <Src as KnownLayout>::LAYOUT;
156197
let dst = <Dst as KnownLayout>::LAYOUT;
@@ -159,7 +200,7 @@ pub mod cast {
159200
(
160201
SizeInfo::SliceDst(TrailingSliceLayout { offset: src_offset, elem_size: src_elem_size }),
161202
SizeInfo::SliceDst(TrailingSliceLayout { offset: dst_offset, elem_size: dst_elem_size })
162-
) => src.align.get() >= dst.align.get() && src_offset == dst_offset && src_elem_size == dst_elem_size,
203+
) => src.align.get() == dst.align.get() && src_offset == dst_offset && src_elem_size == dst_elem_size,
163204
_ => false,
164205
}
165206
});
@@ -177,6 +218,20 @@ pub mod cast {
177218
{
178219
}
179220

221+
// SAFETY: By the `static_assert!` in `Project::project`, `Src` and `Dst`
222+
// are either:
223+
// - Both sized and equal in size
224+
// - Both slice DSTs with the same alignment, trailing slice offset, and
225+
// element size. These ensure that any given pointer metadata encodes the
226+
// same size for both `Src` and `Dst` (note that the alignment is required
227+
// as it affects the amount of trailing padding).
228+
unsafe impl<Src, Dst> CastExact<Src, Dst> for CastUnsized
229+
where
230+
Src: ?Sized + KnownLayout,
231+
Dst: ?Sized + KnownLayout<PointerMetadata = Src::PointerMetadata>,
232+
{
233+
}
234+
180235
/// A field projection
181236
///
182237
/// A `Projection` is a [`Project`] which implements projection by
@@ -246,6 +301,19 @@ pub mod cast {
246301
{
247302
}
248303

304+
// SAFETY: Since the `Project::project` impl delegates to `TU::project` and
305+
// `UV::project`, and since `TU` and `UV` are `CastExact`, the `Project::project`
306+
// impl preserves the set of referent bytes.
307+
unsafe impl<T, U, V, TU, UV> CastExact<T, V> for TransitiveProject<U, TU, UV>
308+
where
309+
T: ?Sized,
310+
U: ?Sized,
311+
V: ?Sized,
312+
TU: CastExact<T, U>,
313+
UV: CastExact<U, V>,
314+
{
315+
}
316+
249317
/// A cast from `T` to `[u8]`.
250318
pub(crate) struct AsBytesCast;
251319

@@ -275,4 +343,7 @@ pub mod cast {
275343

276344
// SAFETY: The `Project::project` impl preserves referent address.
277345
unsafe impl<T: ?Sized + KnownLayout> Cast<T, [u8]> for AsBytesCast {}
346+
347+
// SAFETY: The `Project::project` impl preserves the set of referent bytes.
348+
unsafe impl<T: ?Sized + KnownLayout> CastExact<T, [u8]> for AsBytesCast {}
278349
}

src/pointer/transmute.rs

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ use crate::{
7575
/// any of `src`'s invariants, and that it satisfies all invariants of the
7676
/// destination `Ptr` type.
7777
///
78-
/// First, by `SizeEq::CastFrom: Cast`, `src`'s address is unchanged, so it
78+
/// First, by `SizeEq::CastFrom: CastExact`, `src`'s address is unchanged, so it
7979
/// still satisfies its alignment. Since `dst`'s alignment is `Unaligned`, it
8080
/// trivially satisfies its alignment.
8181
///
@@ -290,17 +290,17 @@ pub unsafe trait TransmuteFrom<Src: ?Sized, SV, DV> {}
290290
///
291291
/// # Safety
292292
///
293-
/// The associated [`CastFrom`] must preserve referent size.
293+
/// `SizeEq` on its own conveys no safety guarantee. Any safety guarantees come
294+
/// from the safety invariants on the associated [`CastFrom`] type, specifically
295+
/// the [`CastExact`] bound.
294296
///
295297
/// [`CastFrom`]: SizeEq::CastFrom
296-
pub unsafe trait SizeEq<Src: ?Sized> {
297-
type CastFrom: cast::Cast<Src, Self>;
298+
/// [`CastExact`]: cast::CastExact
299+
pub trait SizeEq<Src: ?Sized> {
300+
type CastFrom: cast::CastExact<Src, Self>;
298301
}
299302

300-
// SAFETY: `T` trivially has the same size and vtable kind as `T`, and since
301-
// pointer `*mut T -> *mut T` pointer casts are no-ops, this cast trivially
302-
// preserves referent size (when `T: ?Sized`).
303-
unsafe impl<T: ?Sized> SizeEq<T> for T {
303+
impl<T: ?Sized> SizeEq<T> for T {
304304
type CastFrom = cast::IdCast;
305305
}
306306

@@ -454,19 +454,12 @@ impl_transitive_transmute_from!(T: ?Sized => UnsafeCell<T> => T => Cell<T>);
454454
// https://doc.rust-lang.org/1.85.0/core/mem/union.MaybeUninit.html
455455
unsafe impl<T> TransmuteFrom<T, Uninit, Valid> for MaybeUninit<T> {}
456456

457-
// SAFETY: `MaybeUninit<T>` has the same size as `T` [1].
458-
//
459-
// [1] Per https://doc.rust-lang.org/1.81.0/std/mem/union.MaybeUninit.html#layout-1:
460-
//
461-
// `MaybeUninit<T>` is guaranteed to have the same size, alignment, and ABI as
462-
// `T`
463-
unsafe impl<T> SizeEq<T> for MaybeUninit<T> {
464-
type CastFrom = cast::CastSized;
457+
impl<T> SizeEq<T> for MaybeUninit<T> {
458+
type CastFrom = cast::CastSizedExact;
465459
}
466460

467-
// SAFETY: See previous safety comment.
468-
unsafe impl<T> SizeEq<MaybeUninit<T>> for T {
469-
type CastFrom = cast::CastSized;
461+
impl<T> SizeEq<MaybeUninit<T>> for T {
462+
type CastFrom = cast::CastSizedExact;
470463
}
471464

472465
#[cfg(test)]

src/util/macros.rs

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -732,27 +732,36 @@ macro_rules! define_cast {
732732
/// # Safety
733733
///
734734
/// `T` and `$wrapper<T>` must have the same bit validity, and must have the
735-
/// same size in the sense of `SizeEq`.
735+
/// same size in the sense of `CastExact` (specifically, both a
736+
/// `T`-to-`$wrapper<T>` cast and a `$wrapper<T>`-to-`T` cast must be
737+
/// size-preserving).
736738
macro_rules! unsafe_impl_for_transparent_wrapper {
737739
($vis:vis T $(: ?$optbound:ident)? => $wrapper:ident<T>) => {{
738740
crate::util::macros::__unsafe();
739741

740-
use crate::pointer::{TransmuteFrom, SizeEq, invariant::Valid};
742+
use crate::pointer::{cast::CastExact, TransmuteFrom, SizeEq, invariant::Valid};
741743

742744
// SAFETY: The caller promises that `T` and `$wrapper<T>` have the same
743745
// bit validity.
744746
unsafe impl<T $(: ?$optbound)?> TransmuteFrom<T, Valid, Valid> for $wrapper<T> {}
745747
// SAFETY: See previous safety comment.
746748
unsafe impl<T $(: ?$optbound)?> TransmuteFrom<$wrapper<T>, Valid, Valid> for T {}
749+
// SAFETY: The caller promises that a `T` to `$wrapper<T>` cast is
750+
// size-preserving.
747751
define_cast!(unsafe { $vis CastA<T $(: ?$optbound)? > = T => $wrapper<T> });
748-
// SAFETY: The caller promises that `T` and `$wrapper<T>` satisfy
749-
// `SizeEq`.
750-
unsafe impl<T $(: ?$optbound)?> SizeEq<T> for $wrapper<T> {
752+
// SAFETY: The caller promises that a `T` to `$wrapper<T>` cast is
753+
// size-preserving.
754+
unsafe impl<T $(: ?$optbound)?> CastExact<T, $wrapper<T>> for CastA {}
755+
impl<T $(: ?$optbound)?> SizeEq<T> for $wrapper<T> {
751756
type CastFrom = CastA;
752757
}
758+
// SAFETY: The caller promises that a `$wrapper<T>` to `T` cast is
759+
// size-preserving.
753760
define_cast!(unsafe { $vis CastB<T $(: ?$optbound)? > = $wrapper<T> => T });
754-
// SAFETY: See previous safety comment.
755-
unsafe impl<T $(: ?$optbound)?> SizeEq<$wrapper<T>> for T {
761+
// SAFETY: The caller promises that a `$wrapper<T>` to `T` cast is
762+
// size-preserving.
763+
unsafe impl<T $(: ?$optbound)?> CastExact<$wrapper<T>, T> for CastB {}
764+
impl<T $(: ?$optbound)?> SizeEq<$wrapper<T>> for T {
756765
type CastFrom = CastB;
757766
}
758767
}};
@@ -763,9 +772,7 @@ macro_rules! impl_transitive_transmute_from {
763772
const _: () = {
764773
use crate::pointer::{TransmuteFrom, SizeEq, invariant::Valid};
765774

766-
// SAFETY: Since `$u: SizeEq<$t>` and `$v: SizeEq<U>`, this impl is
767-
// transitively sound.
768-
unsafe impl<$($tyvar $(: ?$optbound)?)?> SizeEq<$t> for $v
775+
impl<$($tyvar $(: ?$optbound)?)?> SizeEq<$t> for $v
769776
where
770777
$u: SizeEq<$t>,
771778
$v: SizeEq<$u>,
@@ -796,12 +803,10 @@ macro_rules! impl_size_eq {
796803
const _: () = {
797804
use $crate::{pointer::{cast::CastUnsized, SizeEq}};
798805

799-
// SAFETY: See inline.
800-
unsafe impl SizeEq<$t> for $u {
806+
impl SizeEq<$t> for $u {
801807
type CastFrom = CastUnsized;
802808
}
803-
// SAFETY: See previous safety comment.
804-
unsafe impl SizeEq<$u> for $t {
809+
impl SizeEq<$u> for $t {
805810
type CastFrom = CastUnsized;
806811
}
807812
};
@@ -847,13 +852,18 @@ macro_rules! unsafe_with_size_eq {
847852
// no added semantics.
848853
unsafe impl<T: ?Sized> InvariantsEq<$dst<T>> for T {}
849854

855+
// SAFETY: `$src<T>` is a `#[repr(transparent)]` wrapper around `T`, and
856+
// so this cast exactly preserves the set of referent bytes.
850857
define_cast!(unsafe { SrcCast<T: ?Sized> = $src<T> => T });
858+
// SAFETY: See previous safety comment.
859+
unsafe impl<T: ?Sized> crate::pointer::cast::CastExact<$src<T>, T> for SrcCast {}
860+
// SAFETY: `$dst<U>` is a `#[repr(transparent)]` wrapper around `U`, and
861+
// so this cast exactly preserves the set of referent bytes.
851862
define_cast!(unsafe { DstCast<U: ?Sized> = U => $dst<U> });
863+
// SAFETY: See previous safety comment.
864+
unsafe impl<U: ?Sized> crate::pointer::cast::CastExact<U, $dst<U>> for DstCast {}
852865

853-
// SAFETY: `crate::layout::CastFrom` preserves the set of referent
854-
// bytes, as does `SrcCast`, and so this `TransitiveProject` preserves
855-
// the set of referent bytes.
856-
unsafe impl<T: ?Sized, U: ?Sized> SizeEq<$src<T>> for $dst<U>
866+
impl<T: ?Sized, U: ?Sized> SizeEq<$src<T>> for $dst<U>
857867
where
858868
T: KnownLayout<PointerMetadata = usize>,
859869
U: KnownLayout<PointerMetadata = usize>,

0 commit comments

Comments
 (0)