Skip to content

Commit

Permalink
Initial commit of Ptr pointer type
Browse files Browse the repository at this point in the history
`Ptr` is like `NonNull`, but has many restrictions which make it so that
using a `Ptr` in unsafe code requires much simpler soundness proofs. In
particular, in a future commit, we will add support for a
`try_cast_into<U>` method where `U: ?Sized + KnownLayout`, which is a
building block of `TryFromBytes`.

Makes progress on #29
  • Loading branch information
joshlf committed Oct 12, 2023
1 parent e87fa92 commit 090a08f
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 4 deletions.
21 changes: 18 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,18 @@ extern crate alloc;
#[cfg(feature = "alloc")]
use {
alloc::{boxed::Box, vec::Vec},
core::{alloc::Layout, ptr::NonNull},
core::alloc::Layout,
};

// For each polyfill, as soon as the corresponding feature is stable, the
// polyfill import will be unused because method/function resolution will prefer
// the inherent method/function over a trait method/function. Thus, we suppress
// the `unused_imports` warning.
//
// See the documentation on `util::polyfills` for more information.
#[allow(unused_imports)]
use crate::util::polyfills::NonNullExt as _;

// This is a hack to allow zerocopy-derive derives to work in this crate. They
// assume that zerocopy is linked as an extern crate, so they access items from
// it as `zerocopy::Xxx`. This makes that still work.
Expand Down Expand Up @@ -352,8 +361,11 @@ impl SizeInfo {
}
}

#[cfg_attr(test, derive(Copy, Clone, Debug))]
enum _CastType {
#[doc(hidden)]
#[derive(Copy, Clone)]
#[cfg_attr(test, derive(Debug))]
#[allow(missing_debug_implementations)]
pub enum _CastType {
_Prefix,
_Suffix,
}
Expand Down Expand Up @@ -458,6 +470,9 @@ impl DstLayout {
///
/// # Panics
///
/// `validate_cast_and_convert_metadata` will panic if `self` describes a
/// DST whose trailing slice element is zero-sized.
///
/// If `addr + bytes_len` overflows `usize`,
/// `validate_cast_and_convert_metadata` may panic, or it may return
/// incorrect results. No guarantees are made about when
Expand Down
176 changes: 175 additions & 1 deletion src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,150 @@
#[path = "third_party/rust/layout.rs"]
pub(crate) mod core_layout;

use core::{mem, num::NonZeroUsize};
use core::{
fmt::{Debug, Formatter},
marker::PhantomData,
mem,
num::NonZeroUsize,
ptr::NonNull,
};

/// A raw pointer with more restrictions.
///
/// `Ptr<T>` is similar to `NonNull<T>`, but it is more restrictive in the
/// following ways:
/// - It must derive from a valid allocation
/// - It must reference a byte range which is contained inside the allocation
/// from which it derives
/// - As a consequence, the byte range it references must have a size which
/// does not overflow `isize`
/// - It must satisfy `T`'s alignment requirement
///
/// Thanks to these restrictions, it is easier to prove the soundness of some
/// operations using `Ptr`s.
///
/// `Ptr<'a, T>` is [covariant] in `'a` and `T`.
///
/// [covariant]: https://doc.rust-lang.org/reference/subtyping.html
pub(crate) struct Ptr<'a, T: 'a + ?Sized> {
// INVARIANTS:
// - `ptr` is derived from some valid Rust allocation, `A`
// - `ptr` has the same provenance as `A`
// - `ptr` addresses a byte range which is entirely contained in `A`
// - `ptr` addresses a byte range which is not longer than `isize::MAX`
// - `ptr` addresses a byte range which does not wrap around the address
// space
// - `ptr` is validly-aligned for `T`
// - `A` is guaranteed to live for at least `'a`
// - `T: 'a`
ptr: NonNull<T>,
_lifetime: PhantomData<&'a ()>,
}

impl<'a, T: ?Sized> Copy for Ptr<'a, T> {}
impl<'a, T: ?Sized> Clone for Ptr<'a, T> {
fn clone(&self) -> Self {
*self
}
}

impl<'a, T: ?Sized> Ptr<'a, T> {
/// Returns a shared reference to the value.
///
/// # Safety
///
/// TODO(#29), TODO(#429): What is the right way to articulate the safety
/// invariant here? I can see two possible approaches:
/// - Mimic the invariants on [`NonNull::as_ref`] so that it's easy to write
/// the safety comment on the inner call to `self.ptr.as_ref()`.
/// - Simply say that it's the caller's responsibility to ensure that the
/// resulting reference is valid.
///
/// These two approaches should in principle be equivalent, but since our
/// memory model is undefined, there are some subtleties here. See, e.g.:
/// <https://github.com/rust-lang/unsafe-code-guidelines/issues/463#issuecomment-1736771593>
///
/// # Old draft of Safety section
///
/// - The referenced memory must contain a validly-initialized `T` for the
/// duration of `'a`. Note that this requires that any interior mutation
/// (i.e. via [`UnsafeCell`]) performed after this method call leave the
/// memory region always containing a valid `T`.
/// - The referenced memory must not also by referenced by any mutable
/// references during the lifetime `'a`.
/// - There must not exist any references to the same memory region which
/// contain `UnsafeCell`s at byte ranges which are not identical to the
/// byte ranges at which `T` contains `UnsafeCell`s.
///
/// TODO: What about reads/mutation via raw pointers? Presumably these can
/// happen under the following conditions:
/// - Mutation only occurs inside `UnsafeCell`s
/// - Reads only happen using `UnsafeCell`s in places where there are
/// `UnsafeCell`s in `T` (otherwise, those reads could be unsound due to
/// assuming no concurrent mutation)
///
/// [`UnsafeCell`]: core::cell::UnsafeCell
pub(crate) unsafe fn _as_ref(&self) -> &'a T {
// TODO(#429): Add a safety comment. This will depend on how we resolve
// the question about how to define the safety invariants on this
// method.
//
// Old draft of safety comment:
// - By invariant, `self.ptr` is properly-aligned for `T`.
// - By invariant, `self.ptr` is "dereferenceable" in that it points to
// a single allocation
// - By invariant, the allocation is live for `'a`
// - The caller promises that no mutable references exist to this region
// during `'a`
// - The caller promises that `UnsafeCell`s match exactly
// - The caller promises that the memory region contains a
// validly-intialized `T`
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
self.ptr.as_ref()
}
}
}

impl<'a, T: 'a + ?Sized> From<&'a T> for Ptr<'a, T> {
#[inline(always)]
fn from(t: &'a T) -> Ptr<'a, T> {
// SAFETY: `t` points to a valid Rust allocation, `A`, by construction.
// Thus:
// - `ptr` is derived from `A`
// - Since we use `NonNull::from`, which preserves provenance, `ptr` has
// the same provenance as `A`
// - Since `NonNull::from` creates a pointer which addresses the same
// bytes as `t`, `ptr` addresses a byte range entirely contained in
// (in this case, identical to) `A`
// - Since `t: &T`, it addresses no more than `isize::MAX` bytes. [1]
// - Since `t: &T`, it addresses a byte range which does not wrap around
// the address space. [2]
// - Since it is constructed from a valid `&T`, `ptr` is validly-aligned
// for `T`
// - Since `t: &'a T`, the allocation `A` is guaranteed to live for at
// least `'a`
// - `T: 'a` by trait bound
//
// TODO(#429), TODO(https://github.com/rust-lang/rust/issues/116181):
// Once it's documented, reference the guarantee that `NonNull::from`
// preserves provenance.
//
// TODO(#429),
// TODO(https://github.com/rust-lang/unsafe-code-guidelines/issues/465):
// - [1] Where does the reference document that allocations fit in
// `isize`?
// - [2] Where does the reference document that allocations don't wrap
// around the address space?
Ptr { ptr: NonNull::from(t), _lifetime: PhantomData }
}
}

impl<'a, T: 'a + ?Sized> Debug for Ptr<'a, T> {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
self.ptr.fmt(f)
}
}

pub(crate) trait AsAddress {
fn addr(self) -> usize;
Expand Down Expand Up @@ -77,6 +220,37 @@ pub(crate) const fn _round_down_to_next_multiple_of_alignment(
n & mask
}

/// Since we support multiple versions of Rust, there are often features which
/// have been stabilized in the most recent stable release which do not yet
/// exist (stably) on our MSRV. This module provides polyfills for those
/// features so that we can write more "modern" code, and just remove the
/// polyfill once our MSRV supports the corresponding feature. Without this,
/// we'd have to write worse/more verbose code and leave TODO comments sprinkled
/// throughout the codebase to update to the new pattern once it's stabilized.
///
/// Each trait is imported as `_` at the crate root; each polyfill should "just
/// work" at usage sites.
pub(crate) mod polyfills {
use core::ptr::{self, NonNull};

// A polyfill for `NonNull::slice_from_raw_parts` that we can use before our
// MSRV is 1.70, when that function was stabilized.
//
// TODO(#67): Once our MSRV is 1.70, remove this.
pub(crate) trait NonNullExt<T> {
fn slice_from_raw_parts(data: Self, len: usize) -> NonNull<[T]>;
}

impl<T> NonNullExt<T> for NonNull<T> {
#[inline(always)]
fn slice_from_raw_parts(data: Self, len: usize) -> NonNull<[T]> {
let ptr = ptr::slice_from_raw_parts_mut(data.as_ptr(), len);
// SAFETY: `ptr` is converted from `data`, which is non-null.
unsafe { NonNull::new_unchecked(ptr) }
}
}
}

#[cfg(test)]
pub(crate) mod testutil {
use core::fmt::{self, Display, Formatter};
Expand Down

0 comments on commit 090a08f

Please sign in to comment.