Skip to content

Commit

Permalink
[WIP][ptr] Add TransparentWrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
joshlf committed Feb 27, 2024
1 parent afe0d40 commit 7602838
Show file tree
Hide file tree
Showing 4 changed files with 289 additions and 135 deletions.
46 changes: 5 additions & 41 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1337,7 +1337,8 @@ pub unsafe trait TryFromBytes {
Self: Sized + NoCell, // TODO(#251): Remove the `NoCell` bound.
{
let candidate = MaybeUninit::<Self>::read_from(bytes)?;
let c_ptr = Ptr::from_maybe_uninit_ref(&candidate);
let c_ptr = Ptr::from_ref(&candidate);
let c_ptr = c_ptr.transparent_wrapper_into_inner();
// SAFETY: `c_ptr` has no uninitialized sub-ranges because it derived
// from `candidate`, which in turn derives from `bytes: &[u8]`.
let c_ptr = unsafe { c_ptr.assume_validity::<invariant::Initialized>() };
Expand Down Expand Up @@ -3649,33 +3650,7 @@ safety_comment! {
unsafe_impl!(
T: ?Sized + TryFromBytes => TryFromBytes for ManuallyDrop<T>;
|candidate: Maybe<ManuallyDrop<T>>| {
// SAFETY:
// - Since `ManuallyDrop<T>` has the same layout as `T` [1], this
// cast preserves size.
// - This is implemented as a raw pointer cast as required by
// `cast_unsized`.
// - `T` and `ManuallyDrop<T>` have `UnsafeCell`s at the same byte
// ranges [2].
//
//
// [1] Per https://doc.rust-lang.org/nightly/core/mem/struct.ManuallyDrop.html:
//
// `ManuallyDrop<T>` is guaranteed to have the same layout and bit
// validity as `T`.
//
// [2] TODO(#896): Write safety proof before next stable release.
#[allow(clippy::as_conversions)]
let c = unsafe { candidate.cast_unsized(|p: *mut ManuallyDrop<T>| p as *mut T) };

// SAFETY: Since the original `candidate` had the validity
// `Initialized`, and since `c` refers to the same bytes, `c` does
// too.
let c = unsafe { c.assume_validity::<invariant::Initialized>() };
// Confirm that `Maybe` is a type alias for `Ptr` with the validity
// invariant `Initialized`. Our safety proof depends upon this
// invariant, and it might change at some point. If that happens, we
// want this function to stop compiling.
let _: Ptr<'_, ManuallyDrop<T>, (_, _, invariant::Initialized)> = candidate;
let c = candidate.transparent_wrapper_into_inner();

// SAFETY: Since `ManuallyDrop<T>` has the same bit validity as `T`
// [1], it is bit-valid exactly if its bytes are a bit-valid `T`.
Expand Down Expand Up @@ -3771,19 +3746,8 @@ unsafe impl<T: TryFromBytes> TryFromBytes for UnsafeCell<T> {
let c: &mut Unalign<MaybeUninit<T>> = c.as_mut().get_mut();
// This converts from an aligned `Unalign<MaybeUninit<T>>` pointer to an
// unaligned `MaybeUninit<T>` pointer.
let c: Ptr<'_, MaybeUninit<T>, _> = Ptr::from_mut(c).into_inner();

// SAFETY: `MaybeUninit<T>` has the same size as `T` [1]. Thus, this
// cast will preserve the size of the pointer. `MaybeUninit<T>` has
// `UnsafeCell`s at the same byte ranges as `T` [2].
//
// [1] Per https://doc.rust-lang.org/stable/core/mem/union.MaybeUninit.html#layout-1:
//
// MaybeUninit<T> is guaranteed to have the same size, alignment, and
// ABI as T.
//
// [2] TODO(#896): Write safety proof before next stable release.
let c: Ptr<'_, T, _> = unsafe { c.cast_unsized(|c: *mut MaybeUninit<T>| c.cast::<T>()) };
let c: Ptr<'_, MaybeUninit<T>, _> = Ptr::from_mut(c).transparent_wrapper_into_inner();
let c: Ptr<'_, T, _> = c.transparent_wrapper_into_inner();

// SAFETY: The original `candidate` argument has `Initialized` validity.
// None of the subsequent operations modify the memory itself, and so
Expand Down
4 changes: 2 additions & 2 deletions src/pointer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ use crate::Unaligned;
/// to [`TryFromBytes::is_bit_valid`].
///
/// [`TryFromBytes::is_bit_valid`]: crate::TryFromBytes::is_bit_valid
pub type Maybe<'a, T, Aliasing = invariant::Shared, Alignment = invariant::AnyAlignment> =
pub type Maybe<'a, T, Aliasing = invariant::Shared, Alignment = invariant::Any> =
Ptr<'a, T, (Aliasing, Alignment, invariant::Initialized)>;

/// A semi-user-facing wrapper type representing a maybe-aligned reference, for
/// use in [`TryFromBytes::is_bit_valid`].
///
/// [`TryFromBytes::is_bit_valid`]: crate::TryFromBytes::is_bit_valid
pub type MaybeAligned<'a, T, Aliasing = invariant::Shared, Alignment = invariant::AnyAlignment> =
pub type MaybeAligned<'a, T, Aliasing = invariant::Shared, Alignment = invariant::Any> =
Ptr<'a, T, (Aliasing, Alignment, invariant::Valid)>;

// These methods are defined on the type alias, `MaybeAligned`, so as to bring
Expand Down
153 changes: 62 additions & 91 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ macro_rules! define_system {
$($set: super::$set,)*
{}

impl Sealed for super::Any {}

$($(
impl Sealed for super::$elem {}
)*)*
Expand Down Expand Up @@ -201,6 +203,10 @@ macro_rules! define_system {
const NAME: &'static str;
}

impl $set for Any {
const NAME: &'static str = stringify!(Any);
}

$(
$(#[$elem_attr])*
#[allow(missing_copy_implementations, missing_debug_implementations)]
Expand All @@ -221,7 +227,7 @@ macro_rules! define_system {
// (instead of just the trailing, repeating impl block), we
// ensure that we don't define `at_least` traits that only have
// a trivial implementation (e.g., `at_least::Exclusive for
// Exclusive`, `at_least::AnyValidity for AnyValidity`, etc).
// Exclusive`, etc).
$(
#[doc = concat!(
"[",
Expand Down Expand Up @@ -249,15 +255,17 @@ macro_rules! define_system {
/// Invariants are encoded as ([`Aliasing`][invariant::Aliasing],
/// [`Alignment`][invariant::Alignment], [`Validity`][invariant::Validity])
/// triples implementing the [`Invariants`] trait.
#[doc(hidden)]
pub mod invariant {
/// No requirement - any invariant is allowed.
#[allow(missing_copy_implementations, missing_debug_implementations)]
pub enum Any {}

define_system! {
/// The invariants of a [`Ptr`][super::Ptr].
Invariants {
/// The aliasing invariant of a [`Ptr`][super::Ptr].
Aliasing {
/// No aliasing invariant.
AnyAliasing,

/// The `Ptr<'a, T>` adheres to the aliasing rules of a `&'a T`.
///
/// The referent of a shared-aliased `Ptr` may be concurrently
Expand All @@ -281,18 +289,13 @@ pub mod invariant {

/// The alignment invariant of a [`Ptr`][super::Ptr].
Alignment {
/// The referent is not necessarily aligned.
AnyAlignment,
/// The referent is aligned: for `Ptr<T>`, the referent's
/// address is a multiple of the `T`'s alignment.
Aligned,
}

/// The validity invariant of a [`Ptr`][super::Ptr].
Validity {
/// The referent is not necessarily initialized.
AnyValidity,

/// The byte ranges initialized in `T` are also initialized in
/// the referent.
///
Expand Down Expand Up @@ -385,9 +388,8 @@ mod _external {

/// Methods for converting to and from `Ptr` and Rust's safe reference types.
mod _conversions {
use core::mem::MaybeUninit;

use super::*;
use crate::util::{AlignmentVariance, TransparentWrapper, ValidityVariance};

/// `&'a T` → `Ptr<'a, T>`
impl<'a, T> Ptr<'a, T, (invariant::Shared, invariant::Aligned, invariant::Valid)>
Expand Down Expand Up @@ -604,45 +606,45 @@ mod _conversions {
}
}

/// `&'a MaybeUninit<T>` → `Ptr<'a, T>`
impl<'a, T> Ptr<'a, T, (invariant::Shared, invariant::Aligned, invariant::AnyValidity)>
/// `Ptr<'a, T = Wrapper<U>>` → `Ptr<'a, U>`
impl<'a, T, I> Ptr<'a, T, I>
where
T: 'a,
T: 'a + TransparentWrapper<I> + ?Sized,
I: Invariants,
{
/// Constructs a `Ptr` from a shared reference to a [`MaybeUninit<T>`].
///
/// [`MaybeUninit<T>`]: MaybeUninit
#[doc(hidden)]
#[inline]
pub fn from_maybe_uninit_ref(mu: &'a MaybeUninit<T>) -> Self {
let ptr = Ptr::from_ref(mu);

/// Converts the `Ptr` to a transparent wrapper type into a `Ptr` to the
/// wrapped inner type.
pub(crate) fn transparent_wrapper_into_inner(
self,
) -> Ptr<
'a,
T::Inner,
(
I::Aliasing,
<T::AlignmentVariance as AlignmentVariance<I::Alignment>>::Applied,
<T::ValidityVariance as ValidityVariance<I::Validity>>::Applied,
),
> {
// SAFETY:
// - Since `MaybeUninit<T>` has the same size as `T` [1],
// this cast does not increase the referent's size.
// - Since `MaybeUninit<T>` has the same layout as `T` [2], `T` has
// `UnsafeCell`s at exactly the same ranges as `MaybeUninit<T>`.
//
// [1] Per https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#layout-1:
//
// `MaybeUninit<T>` is guaranteed to have the same size,
// alignment, and ABI as `T`
//
// [2] TODO(https://github.com/rust-lang/rust/pull/121215)
//
// TODO(#896): Finish this safety proof (namely, [2]) before the
// next stable release.
#[allow(clippy::as_conversions)]
let ptr = unsafe { ptr.cast_unsized(|p| p.cast::<T>()) };
// SAFETY: Since `MaybeUninit<T>` has the same alignment as `T` [1],
// the fact that `mu` is aligned to `MaybeUninit<T>` means that it
// is aligned to `T`.
//
// [1] Per https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#layout-1:
//
// `MaybeUninit<T>` is guaranteed to have the same size,
// alignment, and ABI as `T`
unsafe { ptr.assume_alignment::<invariant::Aligned>() }
// - By invariant on `TransparentWrapper::cast_into_inner`:
// - This cast preserves the referent's size.
// - This cast preserves provenance.
// - By invariant on `TransparentWrapper`, `T` and `T::Inner` have
// `UnsafeCell`s at the same byte ranges.
let c = unsafe { self.cast_unsized(|p| T::cast_into_inner(p)) };
// SAFETY: By invariant on `TransparentWrapper`, since `self`
// satisfies the alignment invariant `I::Alignment`, `c` (of type
// `T::Inner`) satisfies the given "applied" alignment invariant.
let c = unsafe {
c.assume_alignment::<<T::AlignmentVariance as AlignmentVariance<I::Alignment>>::Applied>()
};
// SAFETY: By invariant on `TransparentWrapper`, since `self`
// satisfies the validity invariant `I::Validity`, `c` (of type
// `T::Inner`) satisfies the given "applied" validity invariant.
let c = unsafe {
c.assume_validity::<<T::ValidityVariance as ValidityVariance<I::Validity>>::Applied>()
};
c
}
}
}
Expand Down Expand Up @@ -870,10 +872,8 @@ mod _transitions {
/// Forgets that `Ptr`'s referent is validly-aligned for `T`.
#[doc(hidden)]
#[inline]
pub fn forget_aligned(
self,
) -> Ptr<'a, T, (I::Aliasing, invariant::AnyAlignment, I::Validity)> {
// SAFETY: `AnyAlignment` is less restrictive than `Aligned`.
pub fn forget_aligned(self) -> Ptr<'a, T, (I::Aliasing, invariant::Any, I::Validity)> {
// SAFETY: `Any` is less restrictive than `Aligned`.
unsafe { Ptr::from_ptr(self) }
}
}
Expand All @@ -882,7 +882,6 @@ mod _transitions {
/// Casts of the referent type.
mod _casts {
use super::*;
use crate::Unalign;

impl<'a, T, I> Ptr<'a, T, I>
where
Expand Down Expand Up @@ -929,7 +928,7 @@ mod _casts {
pub unsafe fn cast_unsized<U: 'a + ?Sized, F: FnOnce(*mut T) -> *mut U>(
self,
cast: F,
) -> Ptr<'a, U, (I::Aliasing, invariant::AnyAlignment, invariant::AnyValidity)>
) -> Ptr<'a, U, (I::Aliasing, invariant::Any, invariant::Any)>
where
U: 'a,
{
Expand Down Expand Up @@ -962,9 +961,9 @@ mod _casts {
// 6. `ptr` conforms to the aliasing invariant of `I::Aliasing`
// because casting does not impact the aliasing invariant.
// 7. `ptr`, trivially, conforms to the alignment invariant of
// `AnyAlignment`.
// `Any`.
// 8. `ptr`, trivially, conforms to the validity invariant of
// `AnyValidity`.
// `Any`.
// 9. During the lifetime 'a, no code will reference or load or
// store this memory region treating `UnsafeCell`s as existing at
// different ranges than they exist in `U`. This is true by
Expand Down Expand Up @@ -1100,7 +1099,7 @@ mod _casts {
pub(crate) fn try_cast_into<U: 'a + ?Sized + KnownLayout>(
&self,
cast_type: _CastType,
) -> Option<(Ptr<'a, U, (I::Aliasing, invariant::Aligned, invariant::AnyValidity)>, usize)>
) -> Option<(Ptr<'a, U, (I::Aliasing, invariant::Aligned, invariant::Any)>, usize)>
where
U: NoCell,
{
Expand Down Expand Up @@ -1175,7 +1174,7 @@ mod _casts {
// promises that the object described by `split_at` is validly
// aligned for `U`.
// 8. `ptr`, trivially, conforms to the validity invariant of
// `AnyValidity`.
// `Any`.
// 9. During the lifetime 'a, no code will reference or load or
// store this memory region treating `UnsafeCell`s as existing at
// different ranges than they exist in `U`. This is true by
Expand All @@ -1199,7 +1198,7 @@ mod _casts {
#[inline(always)]
pub(crate) fn try_cast_into_no_leftover<U: 'a + ?Sized + KnownLayout>(
&self,
) -> Option<Ptr<'a, U, (I::Aliasing, invariant::Aligned, invariant::AnyValidity)>>
) -> Option<Ptr<'a, U, (I::Aliasing, invariant::Aligned, invariant::Any)>>
where
U: NoCell,
{
Expand All @@ -1212,30 +1211,6 @@ mod _casts {
}
}
}

impl<'a, T, I> Ptr<'a, Unalign<T>, I>
where
T: 'a,
I: Invariants,
{
/// Converts a possibly-aligned pointer to [`Unalign<T>`] to an
/// unaligned pointer to `T`.
///
/// [`Unalign<T>`]: crate::Unalign
#[inline]
pub(crate) fn into_inner(
self,
) -> Ptr<'a, T, (I::Aliasing, invariant::AnyAlignment, I::Validity)> {
// SAFETY: `Unalign<T>` has the same size as `T`, and so this cast
// preserves size. It also has the same layout as `T` (not including
// alignment), and so `UnsafeCell`s exist at the same byte ranges in
// `Unalign<T>` and `T`.
let ptr = unsafe { self.cast_unsized(|p: *mut Unalign<T>| p.cast::<T>()) };
// SAFETY: `Unalign<T>` has the same validity as `T`, and so the
// preceding cast preserved validity.
unsafe { ptr.assume_validity::<I::Validity>() }
}
}
}

/// Projections through the referent.
Expand Down Expand Up @@ -1272,7 +1247,7 @@ mod _project {
pub unsafe fn project<U: 'a + ?Sized>(
self,
projector: impl FnOnce(*mut T) -> *mut U,
) -> Ptr<'a, U, (I::Aliasing, invariant::AnyAlignment, invariant::Initialized)> {
) -> Ptr<'a, U, (I::Aliasing, invariant::Any, invariant::Initialized)> {
// SAFETY: `projector` is provided with `self` casted to a raw
// pointer.
let field = projector(self.as_non_null().as_ptr());
Expand Down Expand Up @@ -1315,7 +1290,7 @@ mod _project {
// 6. `field` conforms to the aliasing invariant of `I::Aliasing`
// because projection does not impact the aliasing invariant.
// 7. `field`, trivially, conforms to the alignment invariant of
// `AnyAlignment`.
// `Any`.
// 8. By type bound on `I::Validity`, `self` satisfies the
// "as-initialized" property relative to `T`. The returned `Ptr`
// has the validity `AsInitialized`. The caller promises that `T`
Expand Down Expand Up @@ -1516,11 +1491,7 @@ mod tests {

// SAFETY: The bytes in `slf` must be initialized.
unsafe fn validate_and_get_len<T: ?Sized + KnownLayout + FromBytes>(
slf: Ptr<
'_,
T,
(invariant::Shared, invariant::Aligned, invariant::AnyValidity),
>,
slf: Ptr<'_, T, (invariant::Shared, invariant::Aligned, invariant::Any)>,
) -> usize {
// SAFETY:
// - Since all bytes in `slf` are initialized and
Expand Down Expand Up @@ -1606,11 +1577,11 @@ mod tests {
// Test that the correct invariant relationships hold.
use super::invariant::*;

assert_not_impl_any!(AnyAliasing: at_least::Shared);
assert_not_impl_any!(Any: at_least::Shared);
assert_impl_all!(Shared: at_least::Shared);
assert_impl_all!(Exclusive: at_least::Shared);

assert_not_impl_any!(AnyValidity: at_least::AsInitialized);
assert_not_impl_any!(Any: at_least::AsInitialized);
assert_impl_all!(AsInitialized: at_least::AsInitialized);
assert_impl_all!(Initialized: at_least::AsInitialized);
assert_impl_all!(Valid: at_least::AsInitialized);
Expand Down
Loading

0 comments on commit 7602838

Please sign in to comment.