Skip to content

Commit

Permalink
Implement TryFromBytes for unsized UnsafeCell (#1619)
Browse files Browse the repository at this point in the history
Makes progress on #251
  • Loading branch information
joshlf authored Sep 8, 2024
1 parent 623a5ee commit 175ad69
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 68 deletions.
56 changes: 4 additions & 52 deletions src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -643,17 +643,14 @@ impl_for_transparent_wrapper!(T: ?Sized + IntoBytes => IntoBytes for ManuallyDro
impl_for_transparent_wrapper!(T: ?Sized + Unaligned => Unaligned for ManuallyDrop<T>);
assert_unaligned!(ManuallyDrop<()>, ManuallyDrop<u8>);

// TODO(#5): Implement `FromZeros` and `FromBytes` when `T: ?Sized`.
impl_for_transparent_wrapper!(T: FromZeros => FromZeros for UnsafeCell<T>);
impl_for_transparent_wrapper!(T: FromBytes => FromBytes for UnsafeCell<T>);
impl_for_transparent_wrapper!(T: ?Sized + FromZeros => FromZeros for UnsafeCell<T>);
impl_for_transparent_wrapper!(T: ?Sized + FromBytes => FromBytes for UnsafeCell<T>);
impl_for_transparent_wrapper!(T: ?Sized + IntoBytes => IntoBytes for UnsafeCell<T>);
impl_for_transparent_wrapper!(T: ?Sized + Unaligned => Unaligned for UnsafeCell<T>);
assert_unaligned!(UnsafeCell<()>, UnsafeCell<u8>);

// SAFETY: See safety comment in `is_bit_valid` impl.
//
// TODO(#5): Try to add `T: ?Sized` bound.
unsafe impl<T: TryFromBytes> TryFromBytes for UnsafeCell<T> {
unsafe impl<T: TryFromBytes + ?Sized> TryFromBytes for UnsafeCell<T> {
#[allow(clippy::missing_inline_in_public_items)]
fn only_derive_is_allowed_to_implement_this_trait()
where
Expand Down Expand Up @@ -682,56 +679,11 @@ unsafe impl<T: TryFromBytes> TryFromBytes for UnsafeCell<T> {
// chance to fix it quickly.
let c = candidate.into_exclusive_or_post_monomorphization_error();

// We wrap in `Unalign` here so that we can get a vanilla Rust reference
// below, which in turn allows us to call `UnsafeCell::get_mut`.
//
// SAFETY:
// - `.cast` preserves address. `Unalign` and `MaybeUninit` both have
// the same size as the types they wrap [1]. Thus, this cast will
// preserve the size of the pointer. As a result, the cast will
// address the same bytes as `c`.
// - `.cast` preserves provenance.
// - Since both the source and destination types are wrapped in
// `UnsafeCell`, all bytes of both types are inside of `UnsafeCell`s,
// and so the byte ranges covered by `UnsafeCell`s are identical in
// both types. Since the pointers refer to the same byte ranges,
// the same is true of the pointers' referents as well.
//
// [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.
let c = unsafe {
c.cast_unsized(|c: *mut UnsafeCell<T>| c.cast::<UnsafeCell<Unalign<MaybeUninit<T>>>>())
};
// SAFETY: `MaybeUninit` has no validity requirements.
let c = unsafe { c.assume_valid() };
let c = c.bikeshed_recall_aligned();
// This is the crucial step at which we use `UnsafeCell::get_mut` to go
// from `UnsafeCell<U>` to `U` (where `U = Unalign<MaybeUninit<T>>`).
// Now that we've gotten rid of the `UnsafeCell`, we can delegate to
// `T::is_bit_valid`.
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).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
// that guarantee is still upheld.
let c = unsafe { c.assume_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<'_, UnsafeCell<T>, (_, _, invariant::Initialized)> = candidate;

// SAFETY: Since `UnsafeCell<T>` and `T` have the same layout and bit
// validity, `UnsafeCell<T>` is bit-valid exactly when its wrapped `T`
// is. Thus, this is a sound implementation of
// `UnsafeCell::is_bit_valid`.
T::is_bit_valid(c.forget_exclusive())
T::is_bit_valid(c.get_mut())
}
}

Expand Down
97 changes: 82 additions & 15 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -717,13 +717,25 @@ mod _transitions {
/// # Safety
///
/// The caller promises that `self` satisfies the invariants `H`.
pub(super) unsafe fn assume_invariants<H: Invariants>(self) -> Ptr<'a, T, H> {
unsafe fn assume_invariants<H: Invariants>(self) -> Ptr<'a, T, H> {
// SAFETY: The caller has promised to satisfy all parameterized
// invariants of `Ptr`. `Ptr`'s other invariants are satisfied
// by-contract by the source `Ptr`.
unsafe { Ptr::new(self.as_non_null()) }
}

/// Helps the type system unify two distinct invariant types which are
/// actually the same.
pub(super) fn unify_invariants<
H: Invariants<Aliasing = I::Aliasing, Alignment = I::Alignment, Validity = I::Validity>,
>(
self,
) -> Ptr<'a, T, H> {
// SAFETY: The associated type bounds on `H` ensure that the
// invariants are unchanged.
unsafe { self.assume_invariants::<H>() }
}

/// Assumes that `self` satisfies the aliasing requirement of `A`.
///
/// # Safety
Expand Down Expand Up @@ -1288,22 +1300,76 @@ mod _casts {
} else {
// Undo the cast so we can return the original bytes.
let slf = slf.as_bytes();
// Restore the initial invariants of `self`.
// Restore the initial alignment invariant of `self`.
//
// SAFETY: The referent type of `slf` is now equal to
// that of `self`, but the invariants nominally differ.
// Since `slf` and `self` refer to the same memory and
// no actions have been taken that would violate the
// original invariants on `self`, it is sound to apply
// the invariants of `self` onto `slf`.
let slf = unsafe { slf.assume_invariants() };
// that of `self`, but the alignment invariants
// nominally differ. Since `slf` and `self` refer to the
// same memory and no actions have been taken that would
// violate the original invariants on `self`, it is
// sound to apply the alignment invariant of `self` onto
// `slf`.
let slf = unsafe { slf.assume_alignment::<I::Alignment>() };
let slf = slf.unify_invariants();
Err(CastError::Size(SizeError::<_, U>::new(slf)))
}
}
Err(err) => Err(err),
}
}
}

impl<'a, T, I> Ptr<'a, core::cell::UnsafeCell<T>, I>
where
T: 'a + ?Sized,
I: Invariants<Aliasing = Exclusive>,
{
/// Converts this `Ptr` into a pointer to the underlying data.
///
/// This call borrows the `UnsafeCell` mutably (at compile-time) which
/// guarantees that we possess the only reference.
///
/// This is like [`UnsafeCell::get_mut`], but for `Ptr`.
///
/// [`UnsafeCell::get_mut`]: core::cell::UnsafeCell::get_mut
#[must_use]
#[inline(always)]
pub fn get_mut(self) -> Ptr<'a, T, I> {
// SAFETY:
// - The closure uses an `as` cast, which preserves address range
// and provenance.
// - We require `I: Invariants<Aliasing = Exclusive>`, so we are not
// required to uphold `UnsafeCell` equality.
#[allow(clippy::as_conversions)]
let ptr = unsafe { self.cast_unsized(|p| p as *mut T) };

// SAFETY: `UnsafeCell<T>` has the same alignment as `T` [1],
// and so if `self` is guaranteed to be aligned, then so is the
// returned `Ptr`.
//
// [1] Per https://doc.rust-lang.org/1.81.0/core/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>`.
let ptr = unsafe { ptr.assume_alignment::<I::Alignment>() };

// SAFETY: `UnsafeCell<T>` has the same bit validity as `T` [1], and
// so if `self` has a particular validity invariant, then the same
// holds of the returned `Ptr`. Technically the term
// "representation" doesn't guarantee this, but the subsequent
// sentence in the documentation makes it clear that this is the
// intention.
//
// [1] Per https://doc.rust-lang.org/1.81.0/core/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>`.
let ptr = unsafe { ptr.assume_validity::<I::Validity>() };
ptr.unify_invariants()
}
}
}

/// Projections through the referent.
Expand Down Expand Up @@ -1467,16 +1533,17 @@ mod _project {
// its (inclusive) lower bound. Thus, no index is a member of both
// ranges.

// SAFETY: We never change invariants other than aliasing.
//
// By the preceding lemma, `left` and `right` do not alias. We do
// not construct any other `Ptr`s or references which alias `left`
// or `right`. Thus, the only `Ptr`s or references which alias
// `left` or `right` are outside of this method. By invariant,
// SAFETY: By the preceding lemma, `left` and `right` do not alias.
// We do not construct any other `Ptr`s or references which alias
// `left` or `right`. Thus, the only `Ptr`s or references which
// alias `left` or `right` are outside of this method. By invariant,
// `self` obeys the aliasing invariant `I::Aliasing` with respect to
// those other `Ptr`s or references, and so `left` and `right` do as
// well.
unsafe { (left.assume_invariants::<I>(), right.assume_invariants::<I>()) }
let (left, right) = unsafe {
(left.assume_aliasing::<I::Aliasing>(), right.assume_aliasing::<I::Aliasing>())
};
(left.unify_invariants(), right.unify_invariants())
}

/// Iteratively projects the elements `Ptr<T>` from `Ptr<[T]>`.
Expand Down
2 changes: 1 addition & 1 deletion src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ unsafe impl<T, I: Invariants> TransparentWrapper<I> for Wrapping<T> {
// - Per [1], `UnsafeCell<T>` has the same size as `T`.
// - See inline comments for other safety justifications.
//
// [1] Per https://doc.rust-lang.org/core/cell/struct.UnsafeCell.html#memory-layout:
// [1] Per https://doc.rust-lang.org/1.81.0/core/cell/struct.UnsafeCell.html#memory-layout:
//
// `UnsafeCell<T>` has the same in-memory representation as its inner type
// `T`.
Expand Down

0 comments on commit 175ad69

Please sign in to comment.