Skip to content

Commit 64a08b3

Browse files
joshlfjswrenn
andcommitted
[pointer] Support generic TransmuteFrom framework
This commit removes the `TransparentWrapper` trait and the `Ptr::transparent_wrapper_into_inner` method. It replaces them with a new family of transmutation traits which encode more generic transmutation (from any `T` to any `U`) and a set of `Ptr` methods which use those traits to bound transmutation operations. In particular: - `Dst: TransmuteFrom<Src>` denotes that a by-value transmutation is sound - `Dst: TryTransmuteFromPtr<Src>` denotes that a transmutation is sound so long as it can be guaranteed that the source is bit-valid for the destination; this is used by e.g. `Ptr::try_into_valid`, which performs runtime validation of bit validity - `Dst: TransmuteFromPtr<Src>` is equivalent to `TransmuteFrom<Src> + TryTransmuteFromPtr<Src>` Some type arguments are omitted in this summary. In particular, all three traits also take validity invariant parameters for both the source and destination types. Also, the `[Try]TransmuteFromPtr` traits take an aliasing parameter. In order to support these traits, we introduce a generalization of `Read` known as `MutationCompatible`. `T: MutationCompatible<U, A>` denotes that *either* `T: Read<A>` and `U: Read<A>` *or* `T` and `U` have the same interior mutation semantics (formally, it is sound for `&T` and `&U` to reference the same referent - safe code operating on these references cannot cause undefined behavior). This is a refinement of the "`UnsafeCell` agreement" concept that we have used before, but it supports types which store but don't actually use `UnsafeCell`s. For example, given a hypothetical `ReadOnly<T>`, the following bound holds: usize: MutationCompatible<ReadOnly<AtomicUsize>, Exclusive> This commit also takes a different approach from the one originally envisioned in #1945. In particular, it turns out that we don't need a full type-level mapping concept. Instead, we need a *predicate* over transitions to determine which ones are valid (e.g., it is valid to go from a `Valid` `MaybeUninit<T>` to an `Uninit` `MaybeUninit<T>`). By contrast, the invariant mapping concept suggests that each source validity has *exactly one* destination validity. This commit makes progress on #1940 by supporting unsized transmutations, but we don't yet support size shrinking or expanding transmutations. This commit obsoletes #1359, as that issue was predicated upon the existence of `TransparentWrapper`, which this commit removes. This commit closes #1226, which suggests supporting `UnsafeCell` agreement. Closes #1945 Closes #1359 Closes #2226 Closes #1226 Closes #1866 Makes progress on #1359 Co-authored-by: Jack Wrenn <[email protected]> gherrit-pr-id: Iad14813bc6d933312bc8d7a1ddcf1aafc7126938
1 parent 52f65db commit 64a08b3

18 files changed

+1442
-1093
lines changed

src/impls.rs

Lines changed: 344 additions & 87 deletions
Large diffs are not rendered by default.

src/lib.rs

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ use core::alloc::Layout;
387387

388388
// Used by `TryFromBytes::is_bit_valid`.
389389
#[doc(hidden)]
390-
pub use crate::pointer::{invariant::BecauseImmutable, Maybe, MaybeAligned, Ptr};
390+
pub use crate::pointer::{invariant::BecauseImmutable, Maybe, Ptr};
391391
// Used by `KnownLayout`.
392392
#[doc(hidden)]
393393
pub use crate::layout::*;
@@ -805,6 +805,17 @@ pub unsafe trait KnownLayout {
805805
// resulting size would not fit in a `usize`.
806806
meta.size_for_metadata(Self::LAYOUT)
807807
}
808+
809+
#[doc(hidden)]
810+
#[must_use]
811+
#[inline(always)]
812+
fn cast_from_raw<P: KnownLayout<PointerMetadata = Self::PointerMetadata> + ?Sized>(
813+
ptr: NonNull<P>,
814+
) -> NonNull<Self> {
815+
let data = ptr.cast::<u8>();
816+
let meta = P::pointer_to_metadata(ptr.as_ptr());
817+
Self::raw_from_ptr_len(data, meta)
818+
}
808819
}
809820

810821
/// The metadata associated with a [`KnownLayout`] type.
@@ -2843,29 +2854,43 @@ unsafe fn try_read_from<S, T: TryFromBytes>(
28432854
// We use `from_mut` despite not mutating via `c_ptr` so that we don't need
28442855
// to add a `T: Immutable` bound.
28452856
let c_ptr = Ptr::from_mut(&mut candidate);
2846-
let c_ptr = c_ptr.transparent_wrapper_into_inner();
28472857
// SAFETY: `c_ptr` has no uninitialized sub-ranges because it derived from
28482858
// `candidate`, which the caller promises is entirely initialized. Since
28492859
// `candidate` is a `MaybeUninit`, it has no validity requirements, and so
2850-
// no values written to `c_ptr` can violate its validity. Since `c_ptr` has
2851-
// `Exclusive` aliasing, no mutations may happen except via `c_ptr` so long
2852-
// as it is live, so we don't need to worry about the fact that `c_ptr` may
2853-
// have more restricted validity than `candidate`.
2860+
// no values written to an `Initialized` `c_ptr` can violate its validity.
2861+
// Since `c_ptr` has `Exclusive` aliasing, no mutations may happen except
2862+
// via `c_ptr` so long as it is live, so we don't need to worry about the
2863+
// fact that `c_ptr` may have more restricted validity than `candidate`.
28542864
let c_ptr = unsafe { c_ptr.assume_validity::<invariant::Initialized>() };
2865+
let c_ptr = c_ptr.transmute();
28552866

2867+
// Since we don't have `T: KnownLayout`, we hack around that by using
2868+
// `Wrapping<T>`, which implements `KnownLayout` even if `T` doesn't.
2869+
//
28562870
// This call may panic. If that happens, it doesn't cause any soundness
2857-
// issues, as we have not generated any invalid state which we need to
2858-
// fix before returning.
2871+
// issues, as we have not generated any invalid state which we need to fix
2872+
// before returning.
28592873
//
2860-
// Note that one panic or post-monomorphization error condition is
2861-
// calling `try_into_valid` (and thus `is_bit_valid`) with a shared
2862-
// pointer when `Self: !Immutable`. Since `Self: Immutable`, this panic
2863-
// condition will not happen.
2864-
if !T::is_bit_valid(c_ptr.forget_aligned()) {
2874+
// Note that one panic or post-monomorphization error condition is calling
2875+
// `try_into_valid` (and thus `is_bit_valid`) with a shared pointer when
2876+
// `Self: !Immutable`. Since `Self: Immutable`, this panic condition will
2877+
// not happen.
2878+
if !Wrapping::<T>::is_bit_valid(c_ptr.forget_aligned()) {
28652879
return Err(ValidityError::new(source).into());
28662880
}
28672881

2868-
// SAFETY: We just validated that `candidate` contains a valid `T`.
2882+
fn _assert_same_size_and_validity<T>()
2883+
where
2884+
Wrapping<T>: pointer::TransmuteFrom<T, invariant::Valid, invariant::Valid>,
2885+
T: pointer::TransmuteFrom<Wrapping<T>, invariant::Valid, invariant::Valid>,
2886+
{
2887+
}
2888+
2889+
_assert_same_size_and_validity::<T>();
2890+
2891+
// SAFETY: We just validated that `candidate` contains a valid
2892+
// `Wrapping<T>`, which has the same size and bit validity as `T`, as
2893+
// guaranteed by the preceding type assertion.
28692894
Ok(unsafe { candidate.assume_init() })
28702895
}
28712896

@@ -3552,7 +3577,7 @@ pub unsafe trait FromBytes: FromZeros {
35523577
{
35533578
static_assert_dst_is_not_zst!(Self);
35543579
match Ptr::from_ref(source).try_cast_into_no_leftover::<_, BecauseImmutable>(None) {
3555-
Ok(ptr) => Ok(ptr.bikeshed_recall_valid().as_ref()),
3580+
Ok(ptr) => Ok(ptr.recall_validity().as_ref()),
35563581
Err(err) => Err(err.map_src(|src| src.as_ref())),
35573582
}
35583583
}
@@ -3788,7 +3813,7 @@ pub unsafe trait FromBytes: FromZeros {
37883813
{
37893814
static_assert_dst_is_not_zst!(Self);
37903815
match Ptr::from_mut(source).try_cast_into_no_leftover::<_, BecauseExclusive>(None) {
3791-
Ok(ptr) => Ok(ptr.bikeshed_recall_valid().as_mut()),
3816+
Ok(ptr) => Ok(ptr.recall_validity().as_mut()),
37923817
Err(err) => Err(err.map_src(|src| src.as_mut())),
37933818
}
37943819
}
@@ -4027,7 +4052,7 @@ pub unsafe trait FromBytes: FromZeros {
40274052
let source = Ptr::from_ref(source);
40284053
let maybe_slf = source.try_cast_into_no_leftover::<_, BecauseImmutable>(Some(count));
40294054
match maybe_slf {
4030-
Ok(slf) => Ok(slf.bikeshed_recall_valid().as_ref()),
4055+
Ok(slf) => Ok(slf.recall_validity().as_ref()),
40314056
Err(err) => Err(err.map_src(|s| s.as_ref())),
40324057
}
40334058
}
@@ -4258,7 +4283,9 @@ pub unsafe trait FromBytes: FromZeros {
42584283
let source = Ptr::from_mut(source);
42594284
let maybe_slf = source.try_cast_into_no_leftover::<_, BecauseImmutable>(Some(count));
42604285
match maybe_slf {
4261-
Ok(slf) => Ok(slf.bikeshed_recall_valid().as_mut()),
4286+
Ok(slf) => Ok(slf
4287+
.recall_validity::<_, (_, (_, (BecauseExclusive, BecauseExclusive)))>()
4288+
.as_mut()),
42624289
Err(err) => Err(err.map_src(|s| s.as_mut())),
42634290
}
42644291
}
@@ -4716,7 +4743,7 @@ fn ref_from_prefix_suffix<T: FromBytes + KnownLayout + Immutable + ?Sized>(
47164743
let (slf, prefix_suffix) = Ptr::from_ref(source)
47174744
.try_cast_into::<_, BecauseImmutable>(cast_type, meta)
47184745
.map_err(|err| err.map_src(|s| s.as_ref()))?;
4719-
Ok((slf.bikeshed_recall_valid().as_ref(), prefix_suffix.as_ref()))
4746+
Ok((slf.recall_validity().as_ref(), prefix_suffix.as_ref()))
47204747
}
47214748

47224749
/// Interprets the given affix of the given bytes as a `&mut Self` without
@@ -4728,15 +4755,15 @@ fn ref_from_prefix_suffix<T: FromBytes + KnownLayout + Immutable + ?Sized>(
47284755
/// If there are insufficient bytes, or if that affix of `source` is not
47294756
/// appropriately aligned, this returns `Err`.
47304757
#[inline(always)]
4731-
fn mut_from_prefix_suffix<T: FromBytes + KnownLayout + ?Sized>(
4758+
fn mut_from_prefix_suffix<T: FromBytes + IntoBytes + KnownLayout + ?Sized>(
47324759
source: &mut [u8],
47334760
meta: Option<T::PointerMetadata>,
47344761
cast_type: CastType,
47354762
) -> Result<(&mut T, &mut [u8]), CastError<&mut [u8], T>> {
47364763
let (slf, prefix_suffix) = Ptr::from_mut(source)
47374764
.try_cast_into::<_, BecauseExclusive>(cast_type, meta)
47384765
.map_err(|err| err.map_src(|s| s.as_mut()))?;
4739-
Ok((slf.bikeshed_recall_valid().as_mut(), prefix_suffix.as_mut()))
4766+
Ok((slf.recall_validity().as_mut(), prefix_suffix.as_mut()))
47404767
}
47414768

47424769
/// Analyzes whether a type is [`IntoBytes`].

src/pointer/inner.rs

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -488,34 +488,6 @@ impl<'a> PtrInner<'a, [u8]> {
488488
}
489489
}
490490

491-
#[allow(clippy::needless_lifetimes)]
492-
impl<'a, T> PtrInner<'a, T> {
493-
/// Performs an unaligned read of `self`'s referent.
494-
///
495-
/// # Safety
496-
///
497-
/// `self` must point to a properly initialized value of type `T`, and
498-
/// reading a copy of `T` must not violate `T`'s safety invariants.
499-
///
500-
/// `self`'s referent must not be concurrently modified during this call.
501-
pub(crate) unsafe fn read_unaligned(self) -> T {
502-
let raw = self.as_non_null().as_ptr();
503-
// SAFETY: The caller promises that `self` points to a bit-valid `T` and
504-
// that reading a copy of it won't violate `T`'s safety invariants. The
505-
// caller promises that `self`'s referent won't be concurrently modified
506-
// during this operation.
507-
//
508-
// `raw` is valid for reads:
509-
// - `self.as_non_null()` returns a `NonNull`, which is guaranteed to be
510-
// non-null.
511-
// - By invariant on `PtrInner`, `raw` is is either zero-sized or:
512-
// - ...is within bounds of a single allocated object which lives for
513-
// at least `'a`.
514-
// - ...has valid provenance for that object.
515-
unsafe { core::ptr::read_unaligned(raw) }
516-
}
517-
}
518-
519491
#[cfg(test)]
520492
mod tests {
521493
use super::*;
@@ -530,9 +502,17 @@ mod tests {
530502
// SAFETY: `i` is in bounds by construction.
531503
let (l, r) = unsafe { ptr.split_at(i) };
532504
// SAFETY: Points to a valid value by construction.
533-
let l_sum: usize = l.iter().map(|ptr| unsafe { ptr.read_unaligned() }).sum();
505+
#[allow(clippy::undocumented_unsafe_blocks)] // Clippy false positive
506+
let l_sum: usize = l
507+
.iter()
508+
.map(|ptr| unsafe { core::ptr::read_unaligned(ptr.as_non_null().as_ptr()) })
509+
.sum();
534510
// SAFETY: Points to a valid value by construction.
535-
let r_sum: usize = r.iter().map(|ptr| unsafe { ptr.read_unaligned() }).sum();
511+
#[allow(clippy::undocumented_unsafe_blocks)] // Clippy false positive
512+
let r_sum: usize = r
513+
.iter()
514+
.map(|ptr| unsafe { core::ptr::read_unaligned(ptr.as_non_null().as_ptr()) })
515+
.sum();
536516
assert_eq!(l_sum, i);
537517
assert_eq!(r_sum, N - i);
538518
assert_eq!(l_sum + r_sum, N);

src/pointer/invariant.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,11 @@ pub trait Reference: Aliasing + Sealed {}
9696
/// The `Ptr<'a, T>` adheres to the aliasing rules of a `&'a T`.
9797
///
9898
/// The referent of a shared-aliased `Ptr` may be concurrently referenced by any
99-
/// number of shared-aliased `Ptr` or `&T` references, and may not be
100-
/// concurrently referenced by any exclusively-aliased `Ptr`s or `&mut T`
101-
/// references. The referent must not be mutated, except via [`UnsafeCell`]s.
99+
/// number of shared-aliased `Ptr` or `&T` references, or by any number of
100+
/// `Ptr<U>` or `&U` references as permitted by `T`'s library safety invariants,
101+
/// and may not be concurrently referenced by any exclusively-aliased `Ptr`s or
102+
/// `&mut` references. The referent must not be mutated, except via
103+
/// [`UnsafeCell`]s, and only when permitted by `T`'s library safety invariants.
102104
///
103105
/// [`UnsafeCell`]: core::cell::UnsafeCell
104106
pub enum Shared {}
@@ -178,7 +180,8 @@ pub enum Initialized {}
178180
// required to uphold).
179181
unsafe impl Validity for Initialized {}
180182

181-
/// The referent of a `Ptr<T>` is bit-valid for `T`.
183+
/// The referent of a `Ptr<T>` is valid for `T`, upholding bit validity and any
184+
/// library safety invariants.
182185
pub enum Valid {}
183186
// SAFETY: `Valid`'s validity is well-defined for all `T: ?Sized`, and is not a
184187
// function of any property of `T` other than its bit validity.

src/pointer/mod.rs

Lines changed: 6 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ mod inner;
1212
#[doc(hidden)]
1313
pub mod invariant;
1414
mod ptr;
15+
mod transmute;
1516

1617
#[doc(hidden)]
17-
pub use invariant::{BecauseExclusive, BecauseImmutable, Read};
18+
pub(crate) use transmute::*;
1819
#[doc(hidden)]
19-
pub use ptr::Ptr;
20-
21-
use crate::Unaligned;
20+
pub use {
21+
invariant::{BecauseExclusive, BecauseImmutable, Read},
22+
ptr::Ptr,
23+
};
2224

2325
/// A shorthand for a maybe-valid, maybe-aligned reference. Used as the argument
2426
/// to [`TryFromBytes::is_bit_valid`].
@@ -27,55 +29,6 @@ use crate::Unaligned;
2729
pub type Maybe<'a, T, Aliasing = invariant::Shared, Alignment = invariant::Unaligned> =
2830
Ptr<'a, T, (Aliasing, Alignment, invariant::Initialized)>;
2931

30-
/// A semi-user-facing wrapper type representing a maybe-aligned reference, for
31-
/// use in [`TryFromBytes::is_bit_valid`].
32-
///
33-
/// [`TryFromBytes::is_bit_valid`]: crate::TryFromBytes::is_bit_valid
34-
pub type MaybeAligned<'a, T, Aliasing = invariant::Shared, Alignment = invariant::Unaligned> =
35-
Ptr<'a, T, (Aliasing, Alignment, invariant::Valid)>;
36-
37-
// These methods are defined on the type alias, `MaybeAligned`, so as to bring
38-
// them to the forefront of the rendered rustdoc for that type alias.
39-
impl<'a, T, Aliasing, Alignment> MaybeAligned<'a, T, Aliasing, Alignment>
40-
where
41-
T: 'a + ?Sized,
42-
Aliasing: invariant::Aliasing,
43-
Alignment: invariant::Alignment,
44-
{
45-
/// Reads the value from `MaybeAligned`.
46-
#[must_use]
47-
#[inline]
48-
pub fn read_unaligned<R>(self) -> T
49-
where
50-
T: Copy,
51-
T: invariant::Read<Aliasing, R>,
52-
{
53-
// SAFETY: By invariant on `MaybeAligned`, `self` contains
54-
// validly-initialized data for `T`. By `T: Read<Aliasing>`, we are
55-
// permitted to perform a read of `self`'s referent.
56-
unsafe { self.as_inner().read_unaligned() }
57-
}
58-
}
59-
60-
impl<'a, T, Aliasing, Alignment> MaybeAligned<'a, T, Aliasing, Alignment>
61-
where
62-
T: 'a + ?Sized,
63-
Aliasing: invariant::Reference,
64-
Alignment: invariant::Alignment,
65-
{
66-
/// Views the value as an aligned reference.
67-
///
68-
/// This is only available if `T` is [`Unaligned`].
69-
#[must_use]
70-
#[inline]
71-
pub fn unaligned_as_ref(self) -> &'a T
72-
where
73-
T: Unaligned,
74-
{
75-
self.bikeshed_recall_aligned().as_ref()
76-
}
77-
}
78-
7932
/// Checks if the referent is zeroed.
8033
pub(crate) fn is_zeroed<T, I>(ptr: Ptr<'_, T, I>) -> bool
8134
where

0 commit comments

Comments
 (0)