Skip to content
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
55 changes: 49 additions & 6 deletions zerocopy/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -812,7 +812,8 @@ const _: () = {
#[inline(always)]
fn project(slf: PtrInner<'_, Self>) -> *mut T {
// SAFETY: `ManuallyDrop<T>` has the same layout and bit validity as
// `T` [1].
// `T` [1]. The cast preserves the address of the non-null input and
// its provenance, including when the referent is zero-sized.
//
// [1] Per https://doc.rust-lang.org/1.85.0/std/mem/struct.ManuallyDrop.html:
//
Expand Down Expand Up @@ -1095,12 +1096,26 @@ mod tuples {

#[inline(always)]
fn project(slf: crate::PtrInner<'_, Self>) -> *mut Self::Type {
if core::mem::size_of::<Self>() == 0 {
// The tuple and this field have size zero. This cast preserves
// the input's non-null address and provenance, and the empty
// referent is a subset of the input. No field-offset assumption
// or allocation is needed to meet the projection contract.
return slf.as_ptr().cast::<$CurrT>();
}
let slf = slf.as_non_null().as_ptr();
// SAFETY: `PtrInner` promises it references either a zero-sized
// byte range, or else will reference a byte range that is
// entirely contained within an allocated object. In either
// case, this guarantees that `(*slf).$CurrI` is in-bounds of
// `slf`.
// SAFETY: The non-zero-sized tuple lies within one allocation by
// invariant on `PtrInner`. Its field address is in that allocation
// or one-past its end, and computing it does not wrap. Allocations
// have non-null bases and nonwrapping address ranges [1], so the
// result is non-null even for a zero-sized field. Field projection
// preserves provenance and selects a subset of the tuple's bytes.
//
// [1] Per https://doc.rust-lang.org/1.92.0/std/ptr/index.html#allocation:
//
// base is not equal to null()
// ...
// base + size <= usize::MAX
unsafe { core::ptr::addr_of_mut!((*slf).$CurrI) }
}
}
Expand Down Expand Up @@ -1393,6 +1408,34 @@ mod simd {
mod tests {
use super::*;

#[test]
fn zst_field_projections_are_non_null() {
type Tuple = ((), ());
let value = ((), ());
let pointer = PtrInner::from_ref(&value);
let projected = <Tuple as HasField<(), (), { STRUCT_VARIANT_ID }, 1>>::project(pointer);
assert_eq!(projected, pointer.as_ptr().cast::<()>());

let pointer = core::ptr::NonNull::<Tuple>::dangling();
// SAFETY: `Tuple` is zero-sized. `PtrInner::new` requires no
// allocation or live provenance for a zero-sized referent.
let pointer = unsafe { PtrInner::new(pointer) };
let projected = <Tuple as HasField<(), (), { STRUCT_VARIANT_ID }, 1>>::project(pointer);
assert_eq!(projected, pointer.as_ptr().cast::<()>());

// A zero-sized field of a non-zero-sized tuple.
let value = (0u8, ());
let pointer = PtrInner::from_ref(&value);
let projected = <(u8, ()) as HasField<(), (), { STRUCT_VARIANT_ID }, 1>>::project(pointer);
assert!(!projected.is_null());

let value = ReadOnly::new(((), ()));
let pointer = PtrInner::from_ref(&value);
let projected =
<ReadOnly<Tuple> as HasField<(), (), { STRUCT_VARIANT_ID }, 1>>::project(pointer);
assert_eq!(projected, pointer.as_ptr().cast::<ReadOnly<()>>());
}

#[test]
fn test_impls() {
// A type that can supply test cases for testing `TryFromBytes::is_safe`.
Expand Down
5 changes: 3 additions & 2 deletions zerocopy/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -783,8 +783,9 @@ mod cast_from {

// SAFETY: `project` produces a pointer which refers to the same referent
// bytes as its input, or to a subset of them (see inline comments for a
// more detailed proof of this). It does this using provenance-preserving
// operations.
// more detailed proof of this). The input data-pointer cast and
// `KnownLayout::raw_from_ptr_len` both preserve its address and
// provenance. Thus the result is non-null even for a zero-sized referent.
unsafe impl<Src, Dst> crate::pointer::cast::Project<Src, Dst> for CastFrom<Dst>
where
Src: KnownLayout + ?Sized,
Expand Down
4 changes: 2 additions & 2 deletions zerocopy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1330,8 +1330,8 @@ pub unsafe trait HasField<Client, Field, const VARIANT_ID: i128, const FIELD_ID:
///
/// # Safety
///
/// The returned pointer refers to a non-strict subset of the bytes of
/// `slf`'s referent, and has the same provenance as `slf`.
/// The returned pointer is non-null, refers to a non-strict subset of the
/// bytes of `slf`'s referent, and has the same provenance as `slf`.
#[must_use]
fn project(slf: PtrInner<'_, Self>) -> *mut Self::Type;
}
Expand Down
103 changes: 41 additions & 62 deletions zerocopy/src/pointer/inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,29 +187,15 @@ impl<'a, T: ?Sized> PtrInner<'a, T> {
pub fn project<U: ?Sized, C: cast::Project<T, U>>(self) -> PtrInner<'a, U> {
let projected_raw = C::project(self);

// SAFETY: `self`'s referent lives at a `NonNull` address, and is either
// zero-sized or lives in an allocation. In either case, it does not
// wrap around the address space [1], and so none of the addresses
// contained in it or one-past-the-end of it are null.
//
// By invariant on `C: Project`, `C::project` is a provenance-preserving
// projection which preserves or shrinks the set of referent bytes, so
// `projected_raw` references a subset of `self`'s referent, and so it
// cannot be null.
//
// [1] https://doc.rust-lang.org/1.92.0/std/ptr/index.html#allocation
// SAFETY: By postcondition on `C::project`, `projected_raw` is non-null.
let projected_non_null = unsafe { NonNull::new_unchecked(projected_raw) };

// SAFETY: As described in the preceding safety comment, `projected_raw`,
// and thus `projected_non_null`, addresses a subset of `self`'s
// referent. Thus, `projected_non_null` either:
// - Addresses zero bytes or,
// - Addresses a subset of the referent of `self`. In this case, `self`
// has provenance for its referent, which lives in an allocation.
// Since `projected_non_null` was constructed using a sequence of
// provenance-preserving operations, it also has provenance for its
// referent and that referent lives in an allocation. By invariant on
// `self`, that allocation lives for `'a`.
// SAFETY: `C::project` promises that the result addresses a subset of
// `self`'s referent and preserves its provenance. `NonNull::new_unchecked`
// preserves that address and provenance. If the projected referent is
// non-zero-sized, it therefore lies within the same allocation as
// `self`'s referent, with valid provenance. By invariant on `self`, that
// allocation lives for `'a`. A zero-sized result needs no allocation.
unsafe { PtrInner::new(projected_non_null) }
}
}
Expand All @@ -234,9 +220,11 @@ where
///
/// # Safety
///
/// The caller promises that if `self`'s referent is not zero sized, then
/// a pointer constructed from its address with the given `meta` metadata
/// will address a subset of the allocation pointed to by `self`.
/// The caller promises that if a pointer constructed from `self`'s address
/// with the given `meta` metadata has a non-zero-sized referent, then that
/// pointer addresses a subset of the allocation pointed to by `self`, has
/// valid provenance for that allocation, and that allocation lives for
/// `'a`.
#[inline]
#[must_use]
pub unsafe fn with_meta(self, meta: T::PointerMetadata) -> Self
Expand All @@ -245,30 +233,12 @@ where
{
let raw = T::raw_from_ptr_len(self.as_non_null().cast(), meta);

// SAFETY:
//
// Lemma 0: `raw` either addresses zero bytes, or addresses a subset of
// the allocation pointed to by `self` and has the same
// provenance as `self`. Proof: `raw` is constructed using
// provenance-preserving operations, and the caller has
// promised that, if `self`'s referent is not zero-sized, the
// resulting pointer addresses a subset of the allocation
// pointed to by `self`.
//
// 0. Per Lemma 0 and by invariant on `self`, if `ptr`'s referent is not
// zero sized, then `ptr` is derived from some valid Rust allocation,
// `A`.
// 1. Per Lemma 0 and by invariant on `self`, if `ptr`'s referent is not
// zero sized, then `ptr` has valid provenance for `A`.
// 2. Per Lemma 0 and by invariant on `self`, if `ptr`'s referent is not
// zero sized, then `ptr` addresses a byte range which is entirely
// contained in `A`.
// 3. Per Lemma 0 and by invariant on `self`, `ptr` addresses a byte
// range whose length fits in an `isize`.
// 4. Per Lemma 0 and by invariant on `self`, `ptr` addresses a byte
// range which does not wrap around the address space.
// 5. Per Lemma 0 and by invariant on `self`, if `ptr`'s referent is not
// zero sized, then `A` is guaranteed to live for at least `'a`.
// SAFETY: `raw_from_ptr_len` preserves the input address and provenance
// and uses `meta` for the resulting pointer's metadata. Thus `raw` is
// the pointer described by this method's precondition. If its referent
// is non-zero-sized, the caller promises valid provenance for that
// referent within a Rust allocation which lives for `'a`, satisfying
// both invariants of `PtrInner::new`. Otherwise, no allocation is needed.
unsafe { PtrInner::new(raw) }
}
}
Expand Down Expand Up @@ -305,7 +275,10 @@ where
let l_len = l_len.get();

// SAFETY: The caller promises that `l_len.get() <= self.meta()`.
// Trivially, `0 <= l_len`.
// Trivially, `0 <= l_len`, so the resulting referent is a prefix of
// `self`'s referent. Thus, if it is non-zero-sized, it is within the
// same allocation, has `self`'s provenance, and that allocation lives
// for `'a` by invariant on `self`.
let left = unsafe { self.with_meta(l_len) };

let right = self.trailing_slice();
Expand Down Expand Up @@ -436,7 +409,11 @@ impl<'a, T> PtrInner<'a, [T]> {

/// Iteratively projects the elements `PtrInner<T>` from `PtrInner<[T]>`.
#[inline]
pub fn iter(&self) -> impl Iterator<Item = PtrInner<'a, T>> {
#[must_use = "iterators are lazy and do nothing unless consumed"]
#[allow(clippy::implied_bounds_in_impls)]
pub fn iter(
&self,
) -> impl Iterator<Item = PtrInner<'a, T>> + ExactSizeIterator + DoubleEndedIterator {
// FIXME(#429): Once `NonNull::cast` documents that it preserves
// provenance, cite those docs.
let base = self.as_non_null().cast::<T>().as_ptr();
Expand All @@ -445,7 +422,7 @@ impl<'a, T> PtrInner<'a, [T]> {
// `NonNull::get_unchecked_mut`.

// SAFETY: If the following conditions are not satisfied
// `pointer::cast` may induce Undefined Behavior [1]:
// `pointer::add` may induce Undefined Behavior [1]:
//
// > - The computed offset, `count * size_of::<T>()` bytes, must not
// > overflow `isize``.
Expand All @@ -455,23 +432,24 @@ impl<'a, T> PtrInner<'a, [T]> {
// > bounds of that allocated object. In particular, this range
// > must not “wrap around” the edge of the address space.
//
// [1] https://doc.rust-lang.org/std/primitive.pointer.html#method.add
// [1] https://doc.rust-lang.org/1.92.0/std/primitive.pointer.html#method.add
//
// We satisfy both of these conditions here:
// - By invariant on `Ptr`, `self` addresses a byte range whose
// length fits in an `isize`. Since `elem` is contained in `self`,
// the computed offset of `elem` must fit within `isize.`
// - By invariant on `PtrInner`, `self` addresses a byte range whose
// length fits in an `isize`. Since `i < self.meta()`, the
// computed offset of `elem` fits within `isize`.
// - If the computed offset is non-zero, then this means that the
// referent is not zero-sized. In this case, `base` points to an
// allocated object (by invariant on `self`). Thus:
// - By contract, `self.meta()` accurately reflects the number of
// elements in the slice. `i` is in bounds of `c.meta()` by
// elements in the slice. `i` is in bounds of `self.meta()` by
// construction, and so the result of this addition cannot
// overflow past the end of the allocation referred to by `c`.
// - By invariant on `Ptr`, `self` addresses a byte range which
// does not wrap around the address space. Since `elem` is
// contained in `self`, the computed offset of `elem` must wrap
// around the address space.
// overflow past the end of the allocation referred to by
// `self`.
// - By invariant on `PtrInner`, `self` addresses a byte range
// which does not wrap around the address space. Since `elem`
// is contained in `self`, the computation of `elem` cannot
// wrap around the address space either.
//
// FIXME(#429): Once `pointer::add` documents that it preserves
// provenance, cite those docs.
Expand Down Expand Up @@ -639,7 +617,8 @@ mod tests {
let ptr = PtrInner::from_ref(dst);
assert_eq!(ptr.meta().get(), 16);

// SAFETY: 8 is less than 16
// SAFETY: The resulting eight-byte referent is a subset of `ptr`'s
// 16-byte referent, has its provenance, and lives for the same lifetime.
let ptr = unsafe { ptr.with_meta(8) };

assert_eq!(ptr.meta().get(), 8);
Expand Down
39 changes: 25 additions & 14 deletions zerocopy/src/pointer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ pub mod cast {
///
/// # Safety
///
/// The returned pointer refers to a non-strict subset of the bytes of
/// `src`'s referent, and has the same provenance as `src`.
/// The returned pointer is non-null, refers to a non-strict subset of
/// the bytes of `src`'s referent, and has the same provenance as `src`.
Comment on lines +71 to +72

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Prove non-nullness in every projection implementation

BLOCKING: Strengthening this unsafe postcondition makes non-nullness an obligation of every Project and HasField implementation, but several proofs still establish only the old subset/provenance contract: wrappers.rs:796-799 explicitly restates only those two conditions, while the tuple projection in impls.rs:1099-1104 handles zero-sized referents without showing that the computed field address remains non-null. This matters especially for zero-sized outputs, where being a byte subset is vacuous, because PtrInner::project now passes every returned pointer directly to NonNull::new_unchecked. Audit each handwritten and macro-generated implementation and add an address-preservation or nonwrapping field-offset argument covering ZSTs, rather than updating only the derive-generated struct/union implementation. zerocopy/agent_docs/reviewing.mdL34-L43

Useful? React with 👍 / 👎.

fn project(src: PtrInner<'_, Src>) -> *mut Dst;
}

Expand All @@ -95,8 +95,9 @@ pub mod cast {
pub struct IdCast;

// SAFETY: `project` returns its argument unchanged, and so it is a
// provenance-preserving projection which preserves the set of referent
// bytes.
// provenance-preserving projection which preserves the address and set of
// referent bytes. The input is non-null by invariant on `PtrInner`, so
// the result is non-null regardless of referent size.
unsafe impl<T: ?Sized> Project<T, T> for IdCast {
#[inline(always)]
fn project(src: PtrInner<'_, T>) -> *mut T {
Expand All @@ -123,8 +124,9 @@ pub mod cast {
pub enum CastSized {}

// SAFETY: By the `static_assert!`, `Dst` is no larger than `Src`,
// and so all casts preserve or shrink the set of referent bytes. All
// operations preserve provenance.
// and so all casts preserve or shrink the set of referent bytes. The
// pointer cast preserves the input's non-null address and provenance,
// including when the destination is zero-sized.
unsafe impl<Src, Dst> Project<Src, Dst> for CastSized {
#[inline(always)]
fn project(src: PtrInner<'_, Src>) -> *mut Dst {
Expand All @@ -150,8 +152,9 @@ pub mod cast {
pub enum CastSizedExact {}

// SAFETY: By the `static_assert!`, `Dst` has the same size as `Src`,
// and so all casts preserve the set of referent bytes. All operations
// preserve provenance.
// and so all casts preserve the set of referent bytes. The pointer cast
// preserves the input's non-null address and provenance, including for
// zero-sized referents.
unsafe impl<Src, Dst> Project<Src, Dst> for CastSizedExact {
#[inline(always)]
fn project(src: PtrInner<'_, Src>) -> *mut Dst {
Expand Down Expand Up @@ -187,6 +190,9 @@ pub mod cast {
// given pointer metadata encodes the same size for both `Src` and `Dst`
// (note that the alignment is required as it affects the amount of
// trailing padding). Thus, `project` preserves the set of referent bytes.
// The data-pointer cast and `KnownLayout::raw_from_ptr_len` preserve the
// input's address and provenance, so the result is non-null, including
// when the referent is zero-sized.
unsafe impl<Src, Dst> Project<Src, Dst> for CastUnsized
where
Src: ?Sized + KnownLayout,
Expand Down Expand Up @@ -319,9 +325,11 @@ pub mod cast {
_u: PhantomData<U>,
}

// SAFETY: Since `TU::project` and `UV::project` are each
// provenance-preserving operations which preserve or shrink the set of
// referent bytes, so is their composition.
// SAFETY: `TU::project` and `UV::project` each promise a non-null result,
// the same provenance, and a subset of the input's referent bytes.
// `PtrInner::project` preserves those properties between the calls, so
// their composition satisfies all three postconditions, including for
// zero-sized intermediate and final referents.
unsafe impl<T, U, V, TU, UV> Project<T, V> for TransitiveProject<U, TU, UV>
where
T: ?Sized,
Expand Down Expand Up @@ -367,8 +375,9 @@ pub mod cast {
pub struct AsBytesCast;

// SAFETY: `project` constructs a pointer with the same address as `src`
// and with a referent of the same size as `*src`. It does this using
// provenance-preserving operations.
// and with a referent of the same size as `*src`. This preserves the
// input's non-null address, even for an empty slice. All operations
// preserve provenance.
//
// FIXME(https://github.com/rust-lang/unsafe-code-guidelines/issues/594):
// Technically, this proof assumes that `*src` is contiguous (the same is
Expand Down Expand Up @@ -401,7 +410,9 @@ pub mod cast {
pub struct CastToUnit;

// SAFETY: The `project` implementation projects to a subset of its
// argument's referent using provenance-preserving operations.
// argument's referent using an address- and provenance-preserving cast.
// The empty output referent is a subset of any input referent, and the
// input's non-null address is preserved.
unsafe impl<T: ?Sized> Project<T, ()> for CastToUnit {
#[inline(always)]
fn project(src: PtrInner<'_, T>) -> *mut () {
Expand Down
Loading
Loading