Skip to content

Commit

Permalink
Add AliasingSafe framework (#1224)
Browse files Browse the repository at this point in the history
This commit adds the `pointer::aliasing_safety::AliasingSafe` trait,
which is implemented for pointer conversions which do not violate
aliasing. This can happen either because the aliasing is exclusive or
because neither type contains `UnsafeCell`s.

This paves the way for us to remove `Immutable` bounds from some of our
API, including from some derives.

Makes progress on #251

Co-authored-by: Jack Wrenn <jswrenn@amazon.com>
  • Loading branch information
joshlf and jswrenn authored May 9, 2024
1 parent dddb53c commit 1c3c3da
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 40 deletions.
36 changes: 20 additions & 16 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ use core::{
},
};

use crate::pointer::invariant;
use crate::pointer::{invariant, BecauseExclusive, BecauseImmutable};

#[cfg(any(feature = "alloc", test))]
extern crate alloc;
Expand Down Expand Up @@ -1177,7 +1177,7 @@ pub unsafe trait TryFromBytes {
Self: KnownLayout + Immutable,
{
util::assert_dst_is_not_zst::<Self>();
match Ptr::from_ref(candidate).try_cast_into_no_leftover::<Self>() {
match Ptr::from_ref(candidate).try_cast_into_no_leftover::<Self, BecauseImmutable>() {
Ok(candidate) => {
// 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
Expand All @@ -1189,7 +1189,9 @@ pub unsafe trait TryFromBytes {
// condition will not happen.
match candidate.try_into_valid() {
Ok(valid) => Ok(valid.as_ref()),
Err(e) => Err(e.map_src(|src| src.as_bytes().as_ref()).into()),
Err(e) => {
Err(e.map_src(|src| src.as_bytes::<BecauseImmutable>().as_ref()).into())
}
}
}
Err(e) => Err(e.map_src(Ptr::as_ref).into()),
Expand Down Expand Up @@ -1428,7 +1430,7 @@ pub unsafe trait TryFromBytes {
Self: KnownLayout + Immutable, // TODO(#251): Remove the `Immutable` bound.
{
util::assert_dst_is_not_zst::<Self>();
match Ptr::from_mut(bytes).try_cast_into_no_leftover::<Self>() {
match Ptr::from_mut(bytes).try_cast_into_no_leftover::<Self, BecauseExclusive>() {
Ok(candidate) => {
// 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
Expand All @@ -1440,7 +1442,9 @@ pub unsafe trait TryFromBytes {
// condition will not happen.
match candidate.try_into_valid() {
Ok(candidate) => Ok(candidate.as_mut()),
Err(e) => Err(e.map_src(|src| src.as_bytes().as_mut()).into()),
Err(e) => {
Err(e.map_src(|src| src.as_bytes::<BecauseExclusive>().as_mut()).into())
}
}
}
Err(e) => Err(e.map_src(Ptr::as_mut).into()),
Expand Down Expand Up @@ -1707,7 +1711,7 @@ fn try_ref_from_prefix_suffix<T: TryFromBytes + KnownLayout + Immutable + ?Sized
candidate: &[u8],
cast_type: CastType,
) -> Result<(&T, &[u8]), TryCastError<&[u8], T>> {
match Ptr::from_ref(candidate).try_cast_into::<T>(cast_type) {
match Ptr::from_ref(candidate).try_cast_into::<T, BecauseImmutable>(cast_type) {
Ok((candidate, prefix_suffix)) => {
// 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
Expand All @@ -1719,19 +1723,19 @@ fn try_ref_from_prefix_suffix<T: TryFromBytes + KnownLayout + Immutable + ?Sized
// condition will not happen.
match candidate.try_into_valid() {
Ok(valid) => Ok((valid.as_ref(), prefix_suffix.as_ref())),
Err(e) => Err(e.map_src(|src| src.as_bytes().as_ref()).into()),
Err(e) => Err(e.map_src(|src| src.as_bytes::<BecauseImmutable>().as_ref()).into()),
}
}
Err(e) => Err(e.map_src(Ptr::as_ref).into()),
}
}

#[inline(always)]
fn try_mut_from_prefix_suffix<T: TryFromBytes + KnownLayout + Immutable + ?Sized>(
fn try_mut_from_prefix_suffix<T: TryFromBytes + KnownLayout + ?Sized>(
candidate: &mut [u8],
cast_type: CastType,
) -> Result<(&mut T, &mut [u8]), TryCastError<&mut [u8], T>> {
match Ptr::from_mut(candidate).try_cast_into::<T>(cast_type) {
match Ptr::from_mut(candidate).try_cast_into::<T, BecauseExclusive>(cast_type) {
Ok((candidate, prefix_suffix)) => {
// 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
Expand All @@ -1743,7 +1747,7 @@ fn try_mut_from_prefix_suffix<T: TryFromBytes + KnownLayout + Immutable + ?Sized
// condition will not happen.
match candidate.try_into_valid() {
Ok(valid) => Ok((valid.as_mut(), prefix_suffix.as_mut())),
Err(e) => Err(e.map_src(|src| src.as_bytes().as_mut()).into()),
Err(e) => Err(e.map_src(|src| src.as_bytes::<BecauseExclusive>().as_mut()).into()),
}
}
Err(e) => Err(e.map_src(Ptr::as_mut).into()),
Expand Down Expand Up @@ -2332,7 +2336,7 @@ pub unsafe trait FromBytes: FromZeros {
Self: KnownLayout + Immutable,
{
util::assert_dst_is_not_zst::<Self>();
match Ptr::from_ref(bytes).try_cast_into_no_leftover() {
match Ptr::from_ref(bytes).try_cast_into_no_leftover::<_, BecauseImmutable>() {
Ok(ptr) => Ok(ptr.bikeshed_recall_valid().as_ref()),
Err(err) => Err(err.map_src(|src| src.as_ref())),
}
Expand Down Expand Up @@ -2407,7 +2411,7 @@ pub unsafe trait FromBytes: FromZeros {
{
util::assert_dst_is_not_zst::<Self>();
let (slf, suffix) = Ptr::from_ref(bytes)
.try_cast_into(CastType::Prefix)
.try_cast_into::<_, BecauseImmutable>(CastType::Prefix)
.map_err(|err| err.map_src(|s| s.as_ref()))?;
Ok((slf.bikeshed_recall_valid().as_ref(), suffix.as_ref()))
}
Expand Down Expand Up @@ -2467,7 +2471,7 @@ pub unsafe trait FromBytes: FromZeros {
{
util::assert_dst_is_not_zst::<Self>();
let (slf, prefix) = Ptr::from_ref(bytes)
.try_cast_into(CastType::Suffix)
.try_cast_into::<_, BecauseImmutable>(CastType::Suffix)
.map_err(|err| err.map_src(|s| s.as_ref()))?;
Ok((prefix.as_ref(), slf.bikeshed_recall_valid().as_ref()))
}
Expand Down Expand Up @@ -2534,7 +2538,7 @@ pub unsafe trait FromBytes: FromZeros {
Self: IntoBytes + KnownLayout + Immutable,
{
util::assert_dst_is_not_zst::<Self>();
match Ptr::from_mut(bytes).try_cast_into_no_leftover() {
match Ptr::from_mut(bytes).try_cast_into_no_leftover::<_, BecauseExclusive>() {
Ok(ptr) => Ok(ptr.bikeshed_recall_valid().as_mut()),
Err(err) => Err(err.map_src(|src| src.as_mut())),
}
Expand Down Expand Up @@ -2610,7 +2614,7 @@ pub unsafe trait FromBytes: FromZeros {
{
util::assert_dst_is_not_zst::<Self>();
let (slf, suffix) = Ptr::from_mut(bytes)
.try_cast_into(CastType::Prefix)
.try_cast_into::<_, BecauseExclusive>(CastType::Prefix)
.map_err(|err| err.map_src(|s| s.as_mut()))?;
Ok((slf.bikeshed_recall_valid().as_mut(), suffix.as_mut()))
}
Expand Down Expand Up @@ -2679,7 +2683,7 @@ pub unsafe trait FromBytes: FromZeros {
{
util::assert_dst_is_not_zst::<Self>();
let (slf, prefix) = Ptr::from_mut(bytes)
.try_cast_into(CastType::Suffix)
.try_cast_into::<_, BecauseExclusive>(CastType::Suffix)
.map_err(|err| err.map_src(|s| s.as_mut()))?;
Ok((prefix.as_mut(), slf.bikeshed_recall_valid().as_mut()))
}
Expand Down
89 changes: 89 additions & 0 deletions src/pointer/aliasing_safety.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2024 The Fuchsia Authors
//
// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
// This file may not be copied, modified, or distributed except according to
// those terms.

//! Machinery for statically proving the "aliasing-safety" of a `Ptr`.
use crate::{invariant, Immutable};

/// Pointer conversions which do not violate aliasing.
///
/// `U: AliasingSafe<T, A, R>` implies that a pointer conversion from `T` to `U`
/// does not violate the aliasing invariant, `A`. This can be because `A` is
/// [`Exclusive`] or because neither `T` nor `U` permit interior mutability.
///
/// # Safety
///
/// `U: AliasingSafe<T, A, R>` if either of the following conditions holds:
/// - `A` is [`Exclusive`]
/// - `T` and `U` both implement [`Immutable`]
///
/// [`Exclusive`]: crate::pointer::invariant::Exclusive
#[doc(hidden)]
pub unsafe trait AliasingSafe<T: ?Sized, A: invariant::Aliasing, R: AliasingSafeReason> {}

/// Used to prevent user implementations of `AliasingSafeReason`.
mod sealed {
pub trait Sealed {}

impl Sealed for super::BecauseExclusive {}
impl Sealed for super::BecauseImmutable {}
impl<S: Sealed> Sealed for (S,) {}
}

#[doc(hidden)]
pub trait AliasingSafeReason: sealed::Sealed {}
impl<R: AliasingSafeReason> AliasingSafeReason for (R,) {}

/// The conversion is safe because only one live `Ptr` or reference may exist to
/// the referent bytes at a time.
#[derive(Copy, Clone, Debug)]
#[doc(hidden)]
pub enum BecauseExclusive {}
impl AliasingSafeReason for BecauseExclusive {}

/// The conversion is safe because no live `Ptr`s or references permit mutation.
#[derive(Copy, Clone, Debug)]
#[doc(hidden)]
pub enum BecauseImmutable {}
impl AliasingSafeReason for BecauseImmutable {}

/// SAFETY: `T: AliasingSafe<Exclusive, BecauseExclusive>` because for all
/// `Ptr<'a, T, I>` such that `I::Aliasing = Exclusive`, there cannot exist
/// other live references to the memory referenced by `Ptr`.
unsafe impl<T: ?Sized, U: ?Sized> AliasingSafe<T, invariant::Exclusive, BecauseExclusive> for U {}

/// SAFETY: `U: AliasingSafe<T, A, BecauseNoCell>` because for all `Ptr<'a, T,
/// I>` and `Ptr<'a, U, I>` such that `I::Aliasing = A`, all live references and
/// live `Ptr`s agree, by invariant on `Immutable`, that the referenced bytes
/// contain no `UnsafeCell`s, and thus do not permit mutation except via
/// exclusive aliasing.
unsafe impl<A, T: ?Sized, U: ?Sized> AliasingSafe<T, A, BecauseImmutable> for U
where
A: invariant::Aliasing,
T: Immutable,
U: Immutable,
{
}

/// This ensures that `U: AliasingSafe<T, A>` implies `T: AliasingSafe<U, A>` in
/// a manner legible to rustc, which in turn means we can write simpler bounds in
/// some places.
///
/// SAFETY: Per `U: AliasingSafe<T, A, R>`, either:
/// - `A` is `Exclusive`
/// - `T` and `U` both implement `Immutable`
///
/// Neither property depends on which of `T` and `U` are in the `Self` position
/// vs the first type parameter position.
unsafe impl<A, T: ?Sized, U: ?Sized, R> AliasingSafe<U, A, (R,)> for T
where
A: invariant::Aliasing,
R: AliasingSafeReason,
U: AliasingSafe<T, A, R>,
{
}
4 changes: 3 additions & 1 deletion src/pointer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

//! Abstractions over raw pointers.
mod aliasing_safety;
mod ptr;

pub use aliasing_safety::{AliasingSafe, BecauseExclusive, BecauseImmutable};
pub use ptr::{invariant, Ptr};

use crate::Unaligned;
Expand Down Expand Up @@ -70,5 +72,5 @@ where
I: invariant::Invariants<Validity = invariant::Initialized>,
I::Aliasing: invariant::AtLeast<invariant::Shared>,
{
ptr.as_bytes().as_ref().iter().all(|&byte| byte == 0)
ptr.as_bytes::<BecauseImmutable>().as_ref().iter().all(|&byte| byte == 0)
}
53 changes: 37 additions & 16 deletions src/pointer/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

use core::ptr::NonNull;

use crate::{util::AsAddress, CastType, Immutable, KnownLayout};
use crate::{util::AsAddress, CastType, KnownLayout};

/// Module used to gate access to [`Ptr`]'s fields.
mod def {
Expand Down Expand Up @@ -910,7 +910,10 @@ mod _transitions {
/// Casts of the referent type.
mod _casts {
use super::*;
use crate::{layout::MetadataCastError, AlignmentError, CastError, PointerMetadata, SizeError};
use crate::{
layout::MetadataCastError, pointer::aliasing_safety::*, AlignmentError, CastError,
PointerMetadata, SizeError,
};

impl<'a, T, I> Ptr<'a, T, I>
where
Expand Down Expand Up @@ -996,11 +999,14 @@ mod _casts {
where
T: 'a + KnownLayout + ?Sized,
I: Invariants<Validity = Initialized>,
T: Immutable,
{
/// Casts this pointer-to-initialized into a pointer-to-bytes.
#[allow(clippy::wrong_self_convention)]
pub(crate) fn as_bytes(self) -> Ptr<'a, [u8], (I::Aliasing, Aligned, Valid)> {
pub(crate) fn as_bytes<R>(self) -> Ptr<'a, [u8], (I::Aliasing, Aligned, Valid)>
where
[u8]: AliasingSafe<T, I::Aliasing, R>,
R: AliasingSafeReason,
{
let bytes = match T::size_of_val_raw(self.as_non_null()) {
Some(bytes) => bytes,
// SAFETY: `KnownLayout::size_of_val_raw` promises to always
Expand All @@ -1016,8 +1022,10 @@ mod _casts {
// pointer's address, and `bytes` is the length of `p`, so the
// returned pointer addresses the same bytes as `p`
// - `slice_from_raw_parts_mut` and `.cast` both preserve provenance
// - `T` and `[u8]` trivially contain `UnsafeCell`s at identical
// ranges [u8]`, because both are `Immutable`.
// - Because `[u8]: AliasingSafe<T, I::Aliasing, _>`, either:
// - `I::Aliasing` is `Exclusive`
// - `T` and `[u8]` are both `Immutable`, in which case they
// trivially contain `UnsafeCell`s at identical locations
let ptr: Ptr<'a, [u8], _> = unsafe {
self.cast_unsized(|p: *mut T| {
#[allow(clippy::as_conversions)]
Expand Down Expand Up @@ -1112,13 +1120,17 @@ mod _casts {
/// - If this is a prefix cast, `ptr` has the same address as `self`.
/// - If this is a suffix cast, `remainder` has the same address as
/// `self`.
pub(crate) fn try_cast_into<U: 'a + ?Sized + KnownLayout + Immutable>(
pub(crate) fn try_cast_into<U, R>(
self,
cast_type: CastType,
) -> Result<
(Ptr<'a, U, (I::Aliasing, Aligned, Initialized)>, Ptr<'a, [u8], I>),
CastError<Self, U>,
> {
>
where
R: AliasingSafeReason,
U: 'a + ?Sized + KnownLayout + AliasingSafe<[u8], I::Aliasing, R>,
{
crate::util::assert_dst_is_not_zst::<U>();
// PANICS: By invariant, the byte range addressed by `self.ptr` does
// not wrap around the address space. This implies that the sum of
Expand Down Expand Up @@ -1172,9 +1184,12 @@ mod _casts {
// does not wrap around the address space, so does `ptr`.
// 5. Since, by invariant, `target` refers to an allocation which
// is guaranteed to live for at least `'a`, so does `ptr`.
// 6. Since, by invariant, `target` conforms to the aliasing
// invariant of `I::Aliasing` with regards to its referent
// bytes, so does `ptr`.
// 6. Since `U: AliasingSafe<[u8], I::Aliasing, _>`, either:
// - `I::Aliasing` is `Exclusive`, in which case both `src`
// and `ptr` conform to `Exclusive`
// - `I::Aliasing` is `Shared` or `Any` and both `U` and
// `[u8]` are `Immutable`. In this case, neither pointer
// permits mutation, and so `Shared` aliasing is satisfied.
// 7. `ptr` conforms to the alignment invariant of `Aligned` because
// it is derived from `validate_cast_and_convert_metadata`, which
// promises that the object described by `target` is validly
Expand All @@ -1198,9 +1213,13 @@ mod _casts {
/// references the same byte range as `self`.
#[allow(unused)]
#[inline(always)]
pub(crate) fn try_cast_into_no_leftover<U: 'a + ?Sized + KnownLayout + Immutable>(
pub(crate) fn try_cast_into_no_leftover<U, R>(
self,
) -> Result<Ptr<'a, U, (I::Aliasing, Aligned, Initialized)>, CastError<Self, U>> {
) -> Result<Ptr<'a, U, (I::Aliasing, Aligned, Initialized)>, CastError<Self, U>>
where
U: 'a + ?Sized + KnownLayout + AliasingSafe<[u8], I::Aliasing, R>,
R: AliasingSafeReason,
{
// TODO(#67): Remove this allow. See NonNulSlicelExt for more
// details.
#[allow(unstable_name_collisions)]
Expand Down Expand Up @@ -1459,7 +1478,7 @@ mod tests {
use static_assertions::{assert_impl_all, assert_not_impl_any};

use super::*;
use crate::{util::testutil::AU64, FromBytes};
use crate::{pointer::BecauseImmutable, util::testutil::AU64, FromBytes, Immutable};

#[test]
fn test_split_at() {
Expand Down Expand Up @@ -1546,7 +1565,7 @@ mod tests {

for cast_type in [CastType::Prefix, CastType::Suffix] {
if let Ok((slf, remaining)) =
Ptr::from_ref(bytes).try_cast_into::<T>(cast_type)
Ptr::from_ref(bytes).try_cast_into::<T, BecauseImmutable>(cast_type)
{
// SAFETY: All bytes in `bytes` have been
// initialized.
Expand All @@ -1563,7 +1582,9 @@ mod tests {
}
}

if let Ok(slf) = Ptr::from_ref(bytes).try_cast_into_no_leftover::<T>() {
if let Ok(slf) =
Ptr::from_ref(bytes).try_cast_into_no_leftover::<T, BecauseImmutable>()
{
// SAFETY: All bytes in `bytes` have been
// initialized.
let len = unsafe { validate_and_get_len(slf) };
Expand Down
Loading

0 comments on commit 1c3c3da

Please sign in to comment.