Skip to content

Commit

Permalink
[WIP] Transmute
Browse files Browse the repository at this point in the history
Co-authored-by: Jack Wrenn <jswrenn@amazon.com>
gherrit-pr-id: I8d5d162c1b6fe43e3dcb90a6dc5bf58a7a203bf8
  • Loading branch information
joshlf and jswrenn committed Feb 20, 2025
1 parent 8ee868d commit 02f5982
Show file tree
Hide file tree
Showing 8 changed files with 784 additions and 124 deletions.
14 changes: 14 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,20 @@ impl<Src, Dst: ?Sized + TryFromBytes> ValidityError<Src, Dst> {
ValidityError { src: f(self.src), dst: SendSyncPhantomData::default() }
}

/// Changes the destination type.
///
/// # Safety
///
/// `NewDst` must have the same validity - as implemented by
/// [`TryFromBytes::is_bit_valid`] - as `Dst`.
pub(crate) unsafe fn with_dst<NewDst: TryFromBytes>(self) -> ValidityError<Src, NewDst> {
// SAFETY: There is currently no invariant required of `dst`, so this
// method's safety precondition is unnecessary. However, we require it
// to be forwards-compatible with a point in time where we add an
// invariant to the `Dst` type.
ValidityError { src: self.src, dst: SendSyncPhantomData::default() }
}

/// Converts the error into a general [`ConvertError`].
pub(crate) const fn into<A, S>(self) -> ConvertError<A, S, Self> {
ConvertError::Validity(self)
Expand Down
30 changes: 18 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,10 @@ use core::{
#[cfg(feature = "std")]
use std::io;

use crate::pointer::invariant::{self, BecauseExclusive};
use crate::pointer::{
invariant::{self, BecauseExclusive},
transmute::BecauseRead,
};

#[cfg(any(feature = "alloc", test))]
extern crate alloc;
Expand Down Expand Up @@ -1790,7 +1793,7 @@ pub unsafe trait TryFromBytes {
// calling `try_into_valid` (and thus `is_bit_valid`) with a shared
// pointer when `Self: !Immutable`. Since `Self: Immutable`, this panic
// condition will not happen.
match source.try_into_valid() {
match source.try_into_valid::<(pointer::transmute::BecauseRead, _)>() {
Ok(source) => Ok(source.as_mut()),
Err(e) => {
Err(e.map_src(|src| src.as_bytes::<BecauseExclusive>().as_mut()).into())
Expand Down Expand Up @@ -2372,7 +2375,7 @@ pub unsafe trait TryFromBytes {
// calling `try_into_valid` (and thus `is_bit_valid`) with a shared
// pointer when `Self: !Immutable`. Since `Self: Immutable`, this panic
// condition will not happen.
match source.try_into_valid() {
match source.try_into_valid::<(BecauseRead, _)>() {
Ok(source) => Ok(source.as_mut()),
Err(e) => {
Err(e.map_src(|src| src.as_bytes::<BecauseExclusive>().as_mut()).into())
Expand Down Expand Up @@ -2779,7 +2782,7 @@ fn try_ref_from_prefix_suffix<T: TryFromBytes + KnownLayout + Immutable + ?Sized
}

#[inline(always)]
fn try_mut_from_prefix_suffix<T: IntoBytes + TryFromBytes + KnownLayout + ?Sized>(
fn try_mut_from_prefix_suffix<T: TryFromBytes + IntoBytes + KnownLayout + ?Sized>(
candidate: &mut [u8],
cast_type: CastType,
meta: Option<T::PointerMetadata>,
Expand All @@ -2794,7 +2797,7 @@ fn try_mut_from_prefix_suffix<T: IntoBytes + TryFromBytes + KnownLayout + ?Sized
// calling `try_into_valid` (and thus `is_bit_valid`) with a shared
// pointer when `Self: !Immutable`. Since `Self: Immutable`, this panic
// condition will not happen.
match candidate.try_into_valid() {
match candidate.try_into_valid::<(pointer::transmute::BecauseRead, _)>() {
Ok(valid) => Ok((valid.as_mut(), prefix_suffix.as_mut())),
Err(e) => Err(e.map_src(|src| src.as_bytes::<BecauseExclusive>().as_mut()).into()),
}
Expand Down Expand Up @@ -3508,7 +3511,7 @@ pub unsafe trait FromBytes: FromZeros {
{
static_assert_dst_is_not_zst!(Self);
match Ptr::from_ref(source).try_cast_into_no_leftover::<_, BecauseImmutable>(None) {
Ok(ptr) => Ok(ptr.bikeshed_recall_valid().as_ref()),
Ok(ptr) => Ok(ptr.transmute().as_ref()),
Err(err) => Err(err.map_src(|src| src.as_ref())),
}
}
Expand Down Expand Up @@ -3744,7 +3747,7 @@ pub unsafe trait FromBytes: FromZeros {
{
static_assert_dst_is_not_zst!(Self);
match Ptr::from_mut(source).try_cast_into_no_leftover::<_, BecauseExclusive>(None) {
Ok(ptr) => Ok(ptr.bikeshed_recall_valid().as_mut()),
Ok(ptr) => Ok(ptr.bikeshed_recall_valid::<(BecauseRead, BecauseExclusive)>().as_mut()),
Err(err) => Err(err.map_src(|src| src.as_mut())),
}
}
Expand Down Expand Up @@ -3983,7 +3986,7 @@ pub unsafe trait FromBytes: FromZeros {
let source = Ptr::from_ref(source);
let maybe_slf = source.try_cast_into_no_leftover::<_, BecauseImmutable>(Some(count));
match maybe_slf {
Ok(slf) => Ok(slf.bikeshed_recall_valid().as_ref()),
Ok(slf) => Ok(slf.transmute().as_ref()),
Err(err) => Err(err.map_src(|s| s.as_ref())),
}
}
Expand Down Expand Up @@ -4214,7 +4217,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.bikeshed_recall_valid().as_mut()),
Ok(slf) => Ok(slf.bikeshed_recall_valid::<(BecauseRead, BecauseExclusive)>().as_mut()),
Err(err) => Err(err.map_src(|s| s.as_mut())),
}
}
Expand Down Expand Up @@ -4590,7 +4593,7 @@ fn ref_from_prefix_suffix<T: FromBytes + KnownLayout + Immutable + ?Sized>(
let (slf, prefix_suffix) = Ptr::from_ref(source)
.try_cast_into::<_, BecauseImmutable>(cast_type, meta)
.map_err(|err| err.map_src(|s| s.as_ref()))?;
Ok((slf.bikeshed_recall_valid().as_ref(), prefix_suffix.as_ref()))
Ok((slf.transmute().as_ref(), prefix_suffix.as_ref()))
}

/// Interprets the given affix of the given bytes as a `&mut Self` without
Expand All @@ -4602,15 +4605,18 @@ fn ref_from_prefix_suffix<T: FromBytes + KnownLayout + Immutable + ?Sized>(
/// If there are insufficient bytes, or if that affix of `source` is not
/// appropriately aligned, this returns `Err`.
#[inline(always)]
fn mut_from_prefix_suffix<T: FromBytes + KnownLayout + ?Sized>(
fn mut_from_prefix_suffix<T: FromBytes + IntoBytes + KnownLayout + ?Sized>(
source: &mut [u8],
meta: Option<T::PointerMetadata>,
cast_type: CastType,
) -> Result<(&mut T, &mut [u8]), CastError<&mut [u8], T>> {
let (slf, prefix_suffix) = Ptr::from_mut(source)
.try_cast_into::<_, BecauseExclusive>(cast_type, meta)
.map_err(|err| err.map_src(|s| s.as_mut()))?;
Ok((slf.bikeshed_recall_valid().as_mut(), prefix_suffix.as_mut()))
Ok((
slf.transmute::<invariant::Valid<T>, _, (BecauseRead, _), _>().as_mut(),
prefix_suffix.as_mut(),
))
}

/// Analyzes whether a type is [`IntoBytes`].
Expand Down
1 change: 1 addition & 0 deletions src/pointer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod inner;
#[doc(hidden)]
pub mod invariant;
mod ptr;
pub(crate) mod transmute;

#[doc(hidden)]
pub use invariant::{BecauseExclusive, BecauseImmutable, Read};
Expand Down
151 changes: 139 additions & 12 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,10 @@ mod _external {
/// Methods for converting to and from `Ptr` and Rust's safe reference types.
mod _conversions {
use super::*;
use crate::util::{AlignmentVariance, Covariant, TransparentWrapper, ValidityVariance};
use crate::{
pointer::transmute::{CastFrom, TransmuteFromAlignment, TransmuteFromPtr},
util::{AlignmentVariance, Covariant, TransparentWrapper, ValidityVariance},
};

/// `&'a T` → `Ptr<'a, T>`
impl<'a, T: ?Sized> Ptr<'a, Valid<T>, (Shared, Aligned)> {
Expand Down Expand Up @@ -337,6 +340,54 @@ mod _conversions {
}
}

/// `Ptr<'a, T>` → `Ptr<'a, U>`
impl<'a, V, I> Ptr<'a, V, I>
where
V: Validity,
I: Invariants,
{
/// # Safety
/// - TODO: `UnsafeCell` agreement
/// - The caller promises that the returned `Ptr` satisfies alignment
/// `A`
/// - The caller promises that the returned `Ptr` satisfies validity `V`
pub(crate) unsafe fn transmute_unchecked<U, A>(self) -> Ptr<'a, U, (I::Aliasing, A)>
where
A: Alignment,
U: Validity,
U::Inner: CastFrom<V::Inner>,
{
// SAFETY:
// - By invariant on `CastFrom::cast_from`:
// - This cast preserves address and referent size, and thus the
// returned pointer addresses the same bytes as `p`
// - This cast preserves provenance
// - TODO: `UnsafeCell` agreement
let ptr =
unsafe { self.cast_unsized_unchecked::<U::Inner, _>(|p| U::Inner::cast_from(p)) };
// SAFETY: The caller promises that alignment is satisfied.
let ptr = unsafe { ptr.assume_alignment() };
// SAFETY: The caller promises that validity is satisfied.
let ptr = unsafe { ptr.assume_validity::<U>() };
ptr.unify_validity()
}

pub(crate) fn transmute<U, A, RT, RA>(self) -> Ptr<'a, U, (I::Aliasing, A)>
where
A: Alignment,
U: TransmuteFromPtr<V, I::Aliasing, RT>,
U::Inner: TransmuteFromAlignment<U::Inner, I::Alignment, A, RA> + CastFrom<V::Inner>,
{
// SAFETY:
// - TODO: `UnsafeCell` agreement
// - By invariant on `TransmuteFromPtr`, it is sound to treat the
// resulting pointer as having alignment `A`
// - By invariant on `TransmuteFromPtr`, it is sound to treat the
// resulting pointer as having validity `V`
unsafe { self.transmute_unchecked() }
}
}

/// `Ptr<'a, T = Wrapper<U>>` → `Ptr<'a, U>`
impl<'a, V, I> Ptr<'a, V, I>
where
Expand Down Expand Up @@ -428,7 +479,10 @@ mod _conversions {
/// State transitions between invariants.
mod _transitions {
use super::*;
use crate::{AlignmentError, TryFromBytes, ValidityError};
use crate::{
pointer::transmute::{CastFrom, TransmuteFromPtr, TryTransmuteFromPtr},
AlignmentError, TryFromBytes, ValidityError,
};

impl<'a, V, I> Ptr<'a, V, I>
where
Expand Down Expand Up @@ -649,6 +703,22 @@ mod _transitions {
}
}

impl<'a, V, I> Ptr<'a, V, I>
where
V: Validity,
I: Invariants,
I::Aliasing: Reference,
{
/// Forgets that `self` is an `Exclusive` pointer, downgrading it to a
/// `Shared` pointer.
#[doc(hidden)]
#[must_use]
#[inline]
pub const fn forget_exclusive(self) -> Ptr<'a, V, I::WithAliasing<Shared>> {
unsafe { self.assume_invariants() }
}
}

impl<'a, T, I> Ptr<'a, Initialized<T>, I>
where
T: ?Sized,
Expand All @@ -660,14 +730,12 @@ mod _transitions {
#[inline]
// TODO(#859): Reconsider the name of this method before making it
// public.
pub const fn bikeshed_recall_valid(self) -> Ptr<'a, Valid<T>, I>
pub fn bikeshed_recall_valid<R>(self) -> Ptr<'a, Valid<T>, I>
where
T: crate::FromBytes,
Valid<T>: TransmuteFromPtr<Initialized<T>, I::Aliasing, R>,
<Valid<T> as Validity>::Inner: CastFrom<T>,
{
// SAFETY: The bound `T: FromBytes` ensures that any initialized
// sequence of bytes is bit-valid for `T`. `V = Initialized<T>`
// ensures that all of the referent bytes are initialized.
unsafe { self.assume_valid() }
self.transmute().unify_invariants()
}

/// Checks that `self`'s referent is validly initialized for `T`,
Expand All @@ -687,7 +755,12 @@ mod _transitions {
mut self,
) -> Result<Ptr<'a, Valid<T>, I>, ValidityError<Self, T>>
where
T: TryFromBytes + Read<I::Aliasing, R>,
T: TryFromBytes, // + Read<I::Aliasing, R>,
Valid<T>: TryTransmuteFromPtr<Initialized<T>, I::Aliasing, R>,
// NOTE: This bound ought to be implied, but leaving it out causes
// Rust to infinite loop during trait solving.
<Valid<T> as Validity>::Inner:
crate::pointer::transmute::CastFrom<<Initialized<T> as Validity>::Inner>,
I::Aliasing: Reference,
{
// This call may panic. If that happens, it doesn't cause any soundness
Expand All @@ -702,12 +775,63 @@ mod _transitions {
}
}
}

impl<'a, V, I> Ptr<'a, V, I>
where
V: Validity,
I: Invariants,
{
/// Attempts to transmute a `Ptr<T>` into a `Ptr<U>`.
///
/// # Panics
///
/// This method will panic if
/// [`U::is_bit_valid`][TryFromBytes::is_bit_valid] panics.
///
/// # Safety
///
/// On success, the returned `Ptr` addresses the same bytes as `self`.
///
/// On error, unsafe code may rely on this method's returned
/// `ValidityError` containing `self`.
#[inline]
pub(crate) fn try_transmute<U, R, RR>(
mut self,
) -> Result<Ptr<'a, U, I::WithAlignment<Unknown>>, ValidityError<Self, U::Inner>>
where
U: Validity + TryTransmuteFromPtr<V, I::Aliasing, R>,
U::Inner: TryFromBytes + CastFrom<V::Inner>,
Initialized<U::Inner>: TransmuteFromPtr<V, I::Aliasing, RR>,
// TODO: The `Sized` bound here is only required in order to call
// `.bikeshed_try_into_aligned`. There are other ways of getting the
// alignment of a type, and we could use these if we need to drop
// this bound.
<Initialized<U::Inner> as Validity>::Inner: Sized + CastFrom<V::Inner>,
I::Aliasing: Reference,
{
let is_bit_valid = {
let ptr = self.reborrow();
let ptr = ptr.transmute::<Initialized<U::Inner>, Unknown, _, _>();
// This call may panic. If that happens, it doesn't cause any
// soundness issues, as we have not generated any invalid state
// which we need to fix before returning.
<U::Inner as TryFromBytes>::is_bit_valid(ptr)
};

if is_bit_valid {
let ptr = unsafe { self.transmute_unchecked() };
Ok(ptr.unify_invariants())
} else {
Err(ValidityError::new(self))
}
}
}
}

/// Casts of the referent type.
mod _casts {
use super::*;
use crate::{CastError, SizeError};
use crate::{pointer::transmute::BecauseRead, CastError, SizeError};

impl<'a, V, I> Ptr<'a, V, I>
where
Expand Down Expand Up @@ -866,7 +990,7 @@ mod _casts {
})
};

ptr.bikeshed_recall_aligned().bikeshed_recall_valid()
ptr.bikeshed_recall_aligned().bikeshed_recall_valid::<(BecauseRead, _)>()
}
}

Expand Down Expand Up @@ -1125,6 +1249,7 @@ mod tests {

mod test_ptr_try_cast_into_soundness {
use super::*;
use crate::IntoBytes;

// This test is designed so that if `Ptr::try_cast_into_xxx` are
// buggy, it will manifest as unsoundness that Miri can detect.
Expand Down Expand Up @@ -1164,7 +1289,9 @@ mod tests {
};

// SAFETY: The bytes in `slf` must be initialized.
unsafe fn validate_and_get_len<T: ?Sized + KnownLayout + FromBytes>(
unsafe fn validate_and_get_len<
T: ?Sized + KnownLayout + FromBytes + Immutable,
>(
slf: Ptr<'_, Initialized<T>, (Shared, Aligned)>,
) -> usize {
let t = slf.bikeshed_recall_valid().as_ref();
Expand Down
Loading

0 comments on commit 02f5982

Please sign in to comment.