Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 8 additions & 20 deletions src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,29 +436,17 @@ mod atomics {
// the same size and bit validity.
unsafe impl<$($tyvar)?> TransmuteFrom<$prim, Valid, Valid> for $atomic {}

// SAFETY: The caller promised that `$atomic` and `$prim` have
// the same size.
unsafe impl<$($tyvar)?> SizeEq<$atomic> for $prim {
type CastFrom = $crate::pointer::cast::CastSized;
impl<$($tyvar)?> SizeEq<$atomic> for $prim {
type CastFrom = $crate::pointer::cast::CastSizedExact;
}
// SAFETY: See previous safety comment.
unsafe impl<$($tyvar)?> SizeEq<$prim> for $atomic {
type CastFrom = $crate::pointer::cast::CastSized;
impl<$($tyvar)?> SizeEq<$prim> for $atomic {
type CastFrom = $crate::pointer::cast::CastSizedExact;
}
// SAFETY: The caller promised that `$atomic` and `$prim` have
// the same size. `UnsafeCell<T>` has the same size as `T` [1].
//
// [1] Per https://doc.rust-lang.org/1.85.0/std/cell/struct.UnsafeCell.html#memory-layout:
//
// `UnsafeCell<T>` has the same in-memory representation as
// its inner type `T`. A consequence of this guarantee is that
// it is possible to convert between `T` and `UnsafeCell<T>`.
unsafe impl<$($tyvar)?> SizeEq<$atomic> for UnsafeCell<$prim> {
type CastFrom = $crate::pointer::cast::CastSized;
impl<$($tyvar)?> SizeEq<$atomic> for UnsafeCell<$prim> {
type CastFrom = $crate::pointer::cast::CastSizedExact;
}
// SAFETY: See previous safety comment.
unsafe impl<$($tyvar)?> SizeEq<UnsafeCell<$prim>> for $atomic {
type CastFrom = $crate::pointer::cast::CastSized;
impl<$($tyvar)?> SizeEq<UnsafeCell<$prim>> for $atomic {
type CastFrom = $crate::pointer::cast::CastSizedExact;
}

// SAFETY: The caller promised that `$atomic` and `$prim` have
Expand Down
16 changes: 15 additions & 1 deletion src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,20 @@ mod cast_from {
{
}

// SAFETY: FIXME(#1818, #2701): THIS IS UNSOUND. However, given how it's
// used today, it can't cause UB. In particular, the static assertions below
// only check that source alignment is not less than destination alignment,
// but not that they are equal. This means that a pointer cast could result
// in a referent with less trailing padding. This technically violates the
// safety invariant of `CastExact`, but can only result in UB if the padding
// bytes are read, which they never are. Obviously we should still fix this.
unsafe impl<Src, Dst> crate::pointer::cast::CastExact<Src, Dst> for CastFrom<Dst>
where
Src: KnownLayout<PointerMetadata = usize> + ?Sized,
Dst: KnownLayout<PointerMetadata = usize> + ?Sized,
{
}

// SAFETY: `project` produces a pointer which refers to the same referent
// bytes as its input, or to a subset of them (see inline comments for a
// more detailed proof of this). It does this using provenance-preserving
Expand All @@ -771,7 +785,7 @@ mod cast_from {
/// implement soundly.
//
// FIXME(#1817): Support Sized->Unsized and Unsized->Sized casts
fn project(src: PtrInner<'_, Src>) -> *mut Dst {
fn project_inner(src: PtrInner<'_, Src>) -> *mut Dst {
// At compile time (specifically, post-monomorphization time), we
// need to compute two things:
// - Whether, given *any* `*Src`, it is possible to construct a
Expand Down
4 changes: 1 addition & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4390,9 +4390,7 @@ pub unsafe trait FromBytes: FromZeros {
let source = Ptr::from_mut(source);
let maybe_slf = source.try_cast_into_no_leftover::<_, BecauseImmutable>(Some(count));
match maybe_slf {
Ok(slf) => Ok(slf
.recall_validity::<_, (_, (_, (BecauseExclusive, BecauseExclusive)))>()
.as_mut()),
Ok(slf) => Ok(slf.recall_validity::<_, (_, (_, BecauseExclusive))>().as_mut()),
Err(err) => Err(err.map_src(|s| s.as_mut())),
}
}
Expand Down
32 changes: 2 additions & 30 deletions src/pointer/inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,39 +176,11 @@ impl<'a, T: ?Sized> PtrInner<'a, T> {
unsafe { Self::new(ptr) }
}

/// # Safety
///
/// The caller may assume that the resulting `PtrInner` addresses a subset
/// of the bytes of `self`'s referent.
/// A shorthand for `C::project(self)`.
#[must_use]
#[inline(always)]
pub fn project<U: ?Sized, C: cast::Project<T, U>>(self) -> PtrInner<'a, U> {
let projected_raw = C::project(self);

// SAFETY: `self`'s referent lives at a `NonNull` address, and is either
// zero-sized or lives in an allocation. In either case, it does not
// wrap around the address space [1], and so none of the addresses
// contained in it or one-past-the-end of it are null.
//
// By invariant on `C: Project`, `C::project` is a provenance-preserving
// projection which preserves or shrinks the set of referent bytes, so
// `projected_raw` references a subset of `self`'s referent, and so it
// cannot be null.
//
// [1] https://doc.rust-lang.org/1.92.0/std/ptr/index.html#allocation
let projected_non_null = unsafe { NonNull::new_unchecked(projected_raw) };

// SAFETY: As described in the preceding safety comment, `projected_raw`,
// and thus `projected_non_null`, addresses a subset of `self`'s
// referent. Thus, `projected_non_null` either:
// - Addresses zero bytes or,
// - Addresses a subset of the referent of `self`. In this case, `self`
// has provenance for its referent, which lives in an allocation.
// Since `projected_non_null` was constructed using a sequence of
// provenance-preserving operations, it also has provenance for its
// referent and that referent lives in an allocation. By invariant on
// `self`, that allocation lives for `'a`.
unsafe { PtrInner::new(projected_non_null) }
C::project(self)
}
}

Expand Down
139 changes: 126 additions & 13 deletions src/pointer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,45 @@ pub mod cast {
///
/// The returned pointer refers to a non-strict subset of the bytes of
/// `src`'s referent, and has the same provenance as `src`.
fn project(src: PtrInner<'_, Src>) -> *mut Dst;
#[must_use]
fn project_inner(src: PtrInner<'_, Src>) -> *mut Dst;

/// Projects a [`PtrInner`] from `Src` to `Dst`.
///
/// # Safety
///
/// The caller may assume that the resulting `PtrInner` addresses a
/// subset of the bytes of `src`'s referent.
#[must_use]
#[inline(always)]
fn project(src: PtrInner<'_, Src>) -> PtrInner<'_, Dst> {
let projected_raw = Self::project_inner(src);

// SAFETY: `src`'s referent lives at a `NonNull` address, and is
// either zero-sized or lives in an allocation. In either case, it
// does not wrap around the address space [1], and so none of the
// addresses contained in it or one-past-the-end of it are null.
//
// By invariant on `Self: Project`, `Self::project` is a
// provenance-preserving projection which preserves or shrinks the
// set of referent bytes, so `projected_raw` references a subset of
// `src`'s referent, and so it cannot be null.
//
// [1] https://doc.rust-lang.org/1.92.0/std/ptr/index.html#allocation
let projected_non_null = unsafe { core::ptr::NonNull::new_unchecked(projected_raw) };

// SAFETY: As described in the preceding safety comment, `projected_raw`,
// and thus `projected_non_null`, addresses a subset of `src`'s
// referent. Thus, `projected_non_null` either:
// - Addresses zero bytes or,
// - Addresses a subset of the referent of `src`. In this case, `src`
// has provenance for its referent, which lives in an allocation.
// Since `projected_non_null` was constructed using a sequence of
// provenance-preserving operations, it also has provenance for its
// referent and that referent lives in an allocation. By invariant on
// `src`, that allocation lives for `'a`.
unsafe { PtrInner::new(projected_non_null) }
}
}

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

/// A [`Cast`] which does not shrink the set of referent bytes.
///
/// # Safety
///
/// A `CastExact` projection must preserve the set of referent bytes.
pub unsafe trait CastExact<Src: ?Sized, Dst: ?Sized>: Cast<Src, Dst> {}

/// A no-op pointer cast.
#[derive(Default, Copy, Clone)]
#[allow(missing_debug_implementations)]
Expand All @@ -82,14 +127,17 @@ pub mod cast {
// bytes.
unsafe impl<T: ?Sized> Project<T, T> for IdCast {
#[inline(always)]
fn project(src: PtrInner<'_, T>) -> *mut T {
fn project_inner(src: PtrInner<'_, T>) -> *mut T {
src.as_ptr()
}
}

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

// SAFETY: The `Project::project` impl preserves referent size.
unsafe impl<T: ?Sized> CastExact<T, T> for IdCast {}

/// A pointer cast which preserves or shrinks the set of referent bytes of
/// a statically-sized referent.
///
Expand All @@ -107,7 +155,7 @@ pub mod cast {
// operations preserve provenance.
unsafe impl<Src, Dst> Project<Src, Dst> for CastSized {
#[inline(always)]
fn project(src: PtrInner<'_, Src>) -> *mut Dst {
fn project_inner(src: PtrInner<'_, Src>) -> *mut Dst {
static_assert!(Src, Dst => mem::size_of::<Src>() >= mem::size_of::<Dst>());
src.as_ptr().cast::<Dst>()
}
Expand All @@ -116,6 +164,37 @@ pub mod cast {
// SAFETY: The `Project::project` impl preserves referent address.
unsafe impl<Src, Dst> Cast<Src, Dst> for CastSized {}

/// A pointer cast which preserves the set of referent bytes of a
/// statically-sized referent.
///
/// # Safety
///
/// The implementation of [`Project`] uses a compile-time assertion to
/// guarantee that `Dst` has the same size as `Src`. Thus, `CastSizedExact`
/// has a sound implementation of [`Project`] for all `Src` and `Dst` – the
/// caller may pass any `Src` and `Dst` without being responsible for
/// soundness.
#[allow(missing_debug_implementations, missing_copy_implementations)]
pub enum CastSizedExact {}

// SAFETY: By the `static_assert!`, `Dst` has the same size as `Src`,
// and so all casts preserve the set of referent bytes. All operations
// preserve provenance.
unsafe impl<Src, Dst> Project<Src, Dst> for CastSizedExact {
#[inline(always)]
fn project_inner(src: PtrInner<'_, Src>) -> *mut Dst {
static_assert!(Src, Dst => mem::size_of::<Src>() == mem::size_of::<Dst>());
src.as_ptr().cast::<Dst>()
}
}

// SAFETY: The `Project::project` impl preserves referent address.
unsafe impl<Src, Dst> Cast<Src, Dst> for CastSizedExact {}

// SAFETY: By the `static_assert!`, `Project::project` impl preserves
// referent size.
unsafe impl<Src, Dst> CastExact<Src, Dst> for CastSizedExact {}

/// A pointer cast which preserves or shrinks the set of referent bytes of
/// a dynamically-sized referent.
///
Expand All @@ -129,30 +208,34 @@ pub mod cast {
#[allow(missing_debug_implementations, missing_copy_implementations)]
pub enum CastUnsized {}

// SAFETY: The `static_assert!` ensures that `Src` and `Dst` have the same
// `SizeInfo`. Thus, casting preserves the set of referent bytes. All
// operations are provenance-preserving.
// SAFETY: By the `static_assert!`, `Src` and `Dst` are either:
// - Both sized and equal in size
// - Both slice DSTs with the same alignment, trailing slice offset, and
// element size. These ensure that any given pointer metadata encodes the
// same size for both `Src` and `Dst` (note that the alignment is required
// as it affects the amount of trailing padding).
unsafe impl<Src, Dst> Project<Src, Dst> for CastUnsized
where
Src: ?Sized + KnownLayout,
Dst: ?Sized + KnownLayout<PointerMetadata = Src::PointerMetadata>,
{
#[inline(always)]
fn project(src: PtrInner<'_, Src>) -> *mut Dst {
fn project_inner(src: PtrInner<'_, Src>) -> *mut Dst {
// FIXME:
// - Is the alignment check necessary for soundness? It's not
// necessary for the soundness of the `Project` impl, but what
// about the soundness of particular use sites?
// - Do we want this to support shrinking casts as well?
// - Do we want this to support shrinking casts as well? If so,
// we'll need to remove the `CastExact` impl.
static_assert!(Src: ?Sized + KnownLayout, Dst: ?Sized + KnownLayout => {
let t = <Src as KnownLayout>::LAYOUT;
let u = <Dst as KnownLayout>::LAYOUT;
t.align.get() >= u.align.get() && match (t.size_info, u.size_info) {
match (t.size_info, u.size_info) {
(SizeInfo::Sized { size: t }, SizeInfo::Sized { size: u }) => t == u,
(
SizeInfo::SliceDst(TrailingSliceLayout { offset: t_offset, elem_size: t_elem_size }),
SizeInfo::SliceDst(TrailingSliceLayout { offset: u_offset, elem_size: u_elem_size })
) => t_offset == u_offset && t_elem_size == u_elem_size,
) => t.align.get() >= u.align.get() && t_offset == u_offset && t_elem_size == u_elem_size,
_ => false,
}
});
Expand All @@ -170,6 +253,20 @@ pub mod cast {
{
}

// SAFETY: By the `static_assert!` in `Project::project`, `Src` and `Dst`
// are either:
// - Both sized and equal in size
// - Both slice DSTs with the same alignment, trailing slice offset, and
// element size. These ensure that any given pointer metadata encodes the
// same size for both `Src` and `Dst` (note that the alignment is required
// as it affects the amount of trailing padding).
unsafe impl<Src, Dst> CastExact<Src, Dst> for CastUnsized
where
Src: ?Sized + KnownLayout,
Dst: ?Sized + KnownLayout<PointerMetadata = Src::PointerMetadata>,
{
}

/// A field projection
///
/// A `Projection` is a [`Project`] which implements projection by
Expand All @@ -188,7 +285,7 @@ pub mod cast {
T: HasField<F, VARIANT_ID, FIELD_ID>,
{
#[inline(always)]
fn project(src: PtrInner<'_, T>) -> *mut T::Type {
fn project_inner(src: PtrInner<'_, T>) -> *mut T::Type {
T::project(src)
}
}
Expand Down Expand Up @@ -221,7 +318,7 @@ pub mod cast {
UV: Project<U, V>,
{
#[inline(always)]
fn project(t: PtrInner<'_, T>) -> *mut V {
fn project_inner(t: PtrInner<'_, T>) -> *mut V {
t.project::<_, TU>().project::<_, UV>().as_ptr()
}
}
Expand All @@ -239,6 +336,19 @@ pub mod cast {
{
}

// SAFETY: Since the `Project::project` impl delegates to `TU::project` and
// `UV::project`, and since `TU` and `UV` are `CastExact`, the `Project::project`
// impl preserves the set of referent bytes.
unsafe impl<T, U, V, TU, UV> CastExact<T, V> for TransitiveProject<U, TU, UV>
where
T: ?Sized,
U: ?Sized,
V: ?Sized,
TU: CastExact<T, U>,
UV: CastExact<U, V>,
{
}

/// A cast from `T` to `[u8]`.
pub(crate) struct AsBytesCast;

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

// SAFETY: The `Project::project` impl preserves referent address.
unsafe impl<T: ?Sized + KnownLayout> Cast<T, [u8]> for AsBytesCast {}

// SAFETY: The `Project::project` impl preserves the set of referent bytes.
unsafe impl<T: ?Sized + KnownLayout> CastExact<T, [u8]> for AsBytesCast {}
}
5 changes: 5 additions & 0 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -966,13 +966,17 @@ mod _casts {
T: 'a + KnownLayout + ?Sized,
I: Invariants<Validity = Initialized>,
{
// FIXME: Is there any way to teach Rust that, for all `T, A, R`, `T:
// Read<A, R>` implies `[u8]: Read<A, R>`?

/// Casts this pointer-to-initialized into a pointer-to-bytes.
#[allow(clippy::wrong_self_convention)]
#[must_use]
#[inline]
pub fn as_bytes<R>(self) -> Ptr<'a, [u8], (I::Aliasing, Aligned, Valid)>
where
T: Read<I::Aliasing, R>,
[u8]: Read<I::Aliasing, R>,
I::Aliasing: Reference,
{
let ptr = self.cast::<_, AsBytesCast, _>();
Expand Down Expand Up @@ -1125,6 +1129,7 @@ mod _casts {
where
I::Aliasing: Reference,
U: 'a + ?Sized + KnownLayout + Read<I::Aliasing, R>,
[u8]: Read<I::Aliasing, R>,
{
// FIXME(#67): Remove this allow. See NonNulSlicelExt for more
// details.
Expand Down
Loading
Loading