Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement TryFromBytes for unsized UnsafeCell #1619

Merged
merged 1 commit into from
Sep 8, 2024
Merged
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
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