Skip to content

[pointer][WIP] Validity in referent #2406

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
441 changes: 295 additions & 146 deletions src/impls.rs

Large diffs are not rendered by default.

75 changes: 56 additions & 19 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ use core::alloc::Layout;

// Used by `TryFromBytes::is_bit_valid`.
#[doc(hidden)]
pub use crate::pointer::{invariant::BecauseImmutable, Maybe, MaybeAligned, Ptr};
pub use crate::pointer::{invariant::BecauseImmutable, Maybe, Ptr};
// Used by `KnownLayout`.
#[doc(hidden)]
pub use crate::layout::*;
Expand Down Expand Up @@ -805,6 +805,17 @@ pub unsafe trait KnownLayout {
// resulting size would not fit in a `usize`.
meta.size_for_metadata(Self::LAYOUT)
}

#[doc(hidden)]
#[must_use]
#[inline(always)]
fn cast_from_raw<P: KnownLayout<PointerMetadata = Self::PointerMetadata> + ?Sized>(
ptr: NonNull<P>,
) -> NonNull<Self> {
let data = ptr.cast::<u8>();
let meta = P::pointer_to_metadata(ptr.as_ptr());
Self::raw_from_ptr_len(data, meta)
}
}

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

// Since we don't have `T: KnownLayout`, we hack around that by using
// `Wrapping<T>`, which implements `KnownLayout` even if `T` doesn't.
//
// 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.
// issues, as we have not generated any invalid state which we need to fix
// before returning.
//
// Note that one panic or post-monomorphization error condition is
// 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.
if !T::is_bit_valid(c_ptr.forget_aligned()) {
// Note that one panic or post-monomorphization error condition is 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.
if !Wrapping::<T>::is_bit_valid(c_ptr.forget_aligned()) {
return Err(ValidityError::new(source).into());
}

// SAFETY: We just validated that `candidate` contains a valid `T`.
fn _assert_same_size_and_validity<T>()
where
Wrapping<T>: pointer::TransmuteFrom<T, invariant::Valid, invariant::Valid>,
T: pointer::TransmuteFrom<Wrapping<T>, invariant::Valid, invariant::Valid>,
{
}

_assert_same_size_and_validity::<T>();

// SAFETY: We just validated that `candidate` contains a valid
// `Wrapping<T>`, which has the same size and bit validity as `T`, as
// guaranteed by the preceding type assertion.
Ok(unsafe { candidate.assume_init() })
}

Expand Down Expand Up @@ -3547,7 +3577,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.recall_validity().as_ref()),
Err(err) => Err(err.map_src(|src| src.as_ref())),
}
}
Expand Down Expand Up @@ -3783,7 +3813,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.recall_validity().as_mut()),
Err(err) => Err(err.map_src(|src| src.as_mut())),
}
}
Expand Down Expand Up @@ -4022,7 +4052,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.recall_validity().as_ref()),
Err(err) => Err(err.map_src(|s| s.as_ref())),
}
}
Expand Down Expand Up @@ -4253,7 +4283,9 @@ 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
.recall_validity::<_, (_, (_, (BecauseExclusive, BecauseExclusive)))>()
.as_mut()),
Err(err) => Err(err.map_src(|s| s.as_mut())),
}
}
Expand Down Expand Up @@ -4603,7 +4635,12 @@ pub unsafe trait FromBytes: FromZeros {

let ptr = Ptr::from_mut(&mut buf);
// SAFETY: After `buf.zero()`, `buf` consists entirely of initialized,
// zeroed bytes.
// zeroed bytes. Since `MaybeUninit` has no validity requirements, `ptr`
// cannot be used to write values which will violate `buf`'s bit
// validity. Since `ptr` has `Exclusive` aliasing, nothing other than
// `ptr` may be used to mutate `ptr`'s referent, and so its bit validity
// cannot be violated even though `buf` may have more permissive bit
// validity than `ptr`.
let ptr = unsafe { ptr.assume_validity::<invariant::Initialized>() };
let ptr = ptr.as_bytes::<BecauseExclusive>();
src.read_exact(ptr.as_mut())?;
Expand Down Expand Up @@ -4706,7 +4743,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.recall_validity().as_ref(), prefix_suffix.as_ref()))
}

/// Interprets the given affix of the given bytes as a `&mut Self` without
Expand All @@ -4718,15 +4755,15 @@ 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.recall_validity().as_mut(), prefix_suffix.as_mut()))
}

/// Analyzes whether a type is [`IntoBytes`].
Expand Down
38 changes: 8 additions & 30 deletions src/pointer/inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,34 +488,6 @@ impl<'a> PtrInner<'a, [u8]> {
}
}

#[allow(clippy::needless_lifetimes)]
impl<'a, T> PtrInner<'a, T> {
/// Performs an unaligned read of `self`'s referent.
///
/// # Safety
///
/// `self` must point to a properly initialized value of type `T`, and
/// reading a copy of `T` must not violate `T`'s safety invariants.
///
/// `self`'s referent must not be concurrently modified during this call.
pub(crate) unsafe fn read_unaligned(self) -> T {
let raw = self.as_non_null().as_ptr();
// SAFETY: The caller promises that `self` points to a bit-valid `T` and
// that reading a copy of it won't violate `T`'s safety invariants. The
// caller promises that `self`'s referent won't be concurrently modified
// during this operation.
//
// `raw` is valid for reads:
// - `self.as_non_null()` returns a `NonNull`, which is guaranteed to be
// non-null.
// - By invariant on `PtrInner`, `raw` is is either zero-sized or:
// - ...is within bounds of a single allocated object which lives for
// at least `'a`.
// - ...has valid provenance for that object.
unsafe { core::ptr::read_unaligned(raw) }
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -530,9 +502,15 @@ mod tests {
// SAFETY: `i` is in bounds by construction.
let (l, r) = unsafe { ptr.split_at(i) };
// SAFETY: Points to a valid value by construction.
let l_sum: usize = l.iter().map(|ptr| unsafe { ptr.read_unaligned() }).sum();
let l_sum: usize = l
.iter()
.map(|ptr| unsafe { core::ptr::read_unaligned(ptr.as_non_null().as_ptr()) })
.sum();
// SAFETY: Points to a valid value by construction.
let r_sum: usize = r.iter().map(|ptr| unsafe { ptr.read_unaligned() }).sum();
let r_sum: usize = r
.iter()
.map(|ptr| unsafe { core::ptr::read_unaligned(ptr.as_non_null().as_ptr()) })
.sum();
assert_eq!(l_sum, i);
assert_eq!(r_sum, N - i);
assert_eq!(l_sum + r_sum, N);
Expand Down
Loading
Loading