Skip to content

Commit 11883a4

Browse files
joshlfjswrenn
andcommitted
Add AliasingSafe framework
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 <[email protected]>
1 parent dddb53c commit 11883a4

File tree

5 files changed

+158
-40
lines changed

5 files changed

+158
-40
lines changed

src/lib.rs

+20-16
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ use core::{
322322
},
323323
};
324324

325-
use crate::pointer::invariant;
325+
use crate::pointer::{invariant, BecauseExclusive, BecauseImmutable};
326326

327327
#[cfg(any(feature = "alloc", test))]
328328
extern crate alloc;
@@ -1177,7 +1177,7 @@ pub unsafe trait TryFromBytes {
11771177
Self: KnownLayout + Immutable,
11781178
{
11791179
util::assert_dst_is_not_zst::<Self>();
1180-
match Ptr::from_ref(candidate).try_cast_into_no_leftover::<Self>() {
1180+
match Ptr::from_ref(candidate).try_cast_into_no_leftover::<Self, BecauseImmutable>() {
11811181
Ok(candidate) => {
11821182
// This call may panic. If that happens, it doesn't cause any soundness
11831183
// issues, as we have not generated any invalid state which we need to
@@ -1189,7 +1189,9 @@ pub unsafe trait TryFromBytes {
11891189
// condition will not happen.
11901190
match candidate.try_into_valid() {
11911191
Ok(valid) => Ok(valid.as_ref()),
1192-
Err(e) => Err(e.map_src(|src| src.as_bytes().as_ref()).into()),
1192+
Err(e) => {
1193+
Err(e.map_src(|src| src.as_bytes::<BecauseImmutable>().as_ref()).into())
1194+
}
11931195
}
11941196
}
11951197
Err(e) => Err(e.map_src(Ptr::as_ref).into()),
@@ -1428,7 +1430,7 @@ pub unsafe trait TryFromBytes {
14281430
Self: KnownLayout + Immutable, // TODO(#251): Remove the `Immutable` bound.
14291431
{
14301432
util::assert_dst_is_not_zst::<Self>();
1431-
match Ptr::from_mut(bytes).try_cast_into_no_leftover::<Self>() {
1433+
match Ptr::from_mut(bytes).try_cast_into_no_leftover::<Self, BecauseExclusive>() {
14321434
Ok(candidate) => {
14331435
// This call may panic. If that happens, it doesn't cause any soundness
14341436
// issues, as we have not generated any invalid state which we need to
@@ -1440,7 +1442,9 @@ pub unsafe trait TryFromBytes {
14401442
// condition will not happen.
14411443
match candidate.try_into_valid() {
14421444
Ok(candidate) => Ok(candidate.as_mut()),
1443-
Err(e) => Err(e.map_src(|src| src.as_bytes().as_mut()).into()),
1445+
Err(e) => {
1446+
Err(e.map_src(|src| src.as_bytes::<BecauseExclusive>().as_mut()).into())
1447+
}
14441448
}
14451449
}
14461450
Err(e) => Err(e.map_src(Ptr::as_mut).into()),
@@ -1707,7 +1711,7 @@ fn try_ref_from_prefix_suffix<T: TryFromBytes + KnownLayout + Immutable + ?Sized
17071711
candidate: &[u8],
17081712
cast_type: CastType,
17091713
) -> Result<(&T, &[u8]), TryCastError<&[u8], T>> {
1710-
match Ptr::from_ref(candidate).try_cast_into::<T>(cast_type) {
1714+
match Ptr::from_ref(candidate).try_cast_into::<T, BecauseImmutable>(cast_type) {
17111715
Ok((candidate, prefix_suffix)) => {
17121716
// This call may panic. If that happens, it doesn't cause any soundness
17131717
// issues, as we have not generated any invalid state which we need to
@@ -1719,19 +1723,19 @@ fn try_ref_from_prefix_suffix<T: TryFromBytes + KnownLayout + Immutable + ?Sized
17191723
// condition will not happen.
17201724
match candidate.try_into_valid() {
17211725
Ok(valid) => Ok((valid.as_ref(), prefix_suffix.as_ref())),
1722-
Err(e) => Err(e.map_src(|src| src.as_bytes().as_ref()).into()),
1726+
Err(e) => Err(e.map_src(|src| src.as_bytes::<BecauseImmutable>().as_ref()).into()),
17231727
}
17241728
}
17251729
Err(e) => Err(e.map_src(Ptr::as_ref).into()),
17261730
}
17271731
}
17281732

17291733
#[inline(always)]
1730-
fn try_mut_from_prefix_suffix<T: TryFromBytes + KnownLayout + Immutable + ?Sized>(
1734+
fn try_mut_from_prefix_suffix<T: TryFromBytes + KnownLayout + ?Sized>(
17311735
candidate: &mut [u8],
17321736
cast_type: CastType,
17331737
) -> Result<(&mut T, &mut [u8]), TryCastError<&mut [u8], T>> {
1734-
match Ptr::from_mut(candidate).try_cast_into::<T>(cast_type) {
1738+
match Ptr::from_mut(candidate).try_cast_into::<T, BecauseExclusive>(cast_type) {
17351739
Ok((candidate, prefix_suffix)) => {
17361740
// This call may panic. If that happens, it doesn't cause any soundness
17371741
// issues, as we have not generated any invalid state which we need to
@@ -1743,7 +1747,7 @@ fn try_mut_from_prefix_suffix<T: TryFromBytes + KnownLayout + Immutable + ?Sized
17431747
// condition will not happen.
17441748
match candidate.try_into_valid() {
17451749
Ok(valid) => Ok((valid.as_mut(), prefix_suffix.as_mut())),
1746-
Err(e) => Err(e.map_src(|src| src.as_bytes().as_mut()).into()),
1750+
Err(e) => Err(e.map_src(|src| src.as_bytes::<BecauseExclusive>().as_mut()).into()),
17471751
}
17481752
}
17491753
Err(e) => Err(e.map_src(Ptr::as_mut).into()),
@@ -2332,7 +2336,7 @@ pub unsafe trait FromBytes: FromZeros {
23322336
Self: KnownLayout + Immutable,
23332337
{
23342338
util::assert_dst_is_not_zst::<Self>();
2335-
match Ptr::from_ref(bytes).try_cast_into_no_leftover() {
2339+
match Ptr::from_ref(bytes).try_cast_into_no_leftover::<_, BecauseImmutable>() {
23362340
Ok(ptr) => Ok(ptr.bikeshed_recall_valid().as_ref()),
23372341
Err(err) => Err(err.map_src(|src| src.as_ref())),
23382342
}
@@ -2407,7 +2411,7 @@ pub unsafe trait FromBytes: FromZeros {
24072411
{
24082412
util::assert_dst_is_not_zst::<Self>();
24092413
let (slf, suffix) = Ptr::from_ref(bytes)
2410-
.try_cast_into(CastType::Prefix)
2414+
.try_cast_into::<_, BecauseImmutable>(CastType::Prefix)
24112415
.map_err(|err| err.map_src(|s| s.as_ref()))?;
24122416
Ok((slf.bikeshed_recall_valid().as_ref(), suffix.as_ref()))
24132417
}
@@ -2467,7 +2471,7 @@ pub unsafe trait FromBytes: FromZeros {
24672471
{
24682472
util::assert_dst_is_not_zst::<Self>();
24692473
let (slf, prefix) = Ptr::from_ref(bytes)
2470-
.try_cast_into(CastType::Suffix)
2474+
.try_cast_into::<_, BecauseImmutable>(CastType::Suffix)
24712475
.map_err(|err| err.map_src(|s| s.as_ref()))?;
24722476
Ok((prefix.as_ref(), slf.bikeshed_recall_valid().as_ref()))
24732477
}
@@ -2534,7 +2538,7 @@ pub unsafe trait FromBytes: FromZeros {
25342538
Self: IntoBytes + KnownLayout + Immutable,
25352539
{
25362540
util::assert_dst_is_not_zst::<Self>();
2537-
match Ptr::from_mut(bytes).try_cast_into_no_leftover() {
2541+
match Ptr::from_mut(bytes).try_cast_into_no_leftover::<_, BecauseExclusive>() {
25382542
Ok(ptr) => Ok(ptr.bikeshed_recall_valid().as_mut()),
25392543
Err(err) => Err(err.map_src(|src| src.as_mut())),
25402544
}
@@ -2610,7 +2614,7 @@ pub unsafe trait FromBytes: FromZeros {
26102614
{
26112615
util::assert_dst_is_not_zst::<Self>();
26122616
let (slf, suffix) = Ptr::from_mut(bytes)
2613-
.try_cast_into(CastType::Prefix)
2617+
.try_cast_into::<_, BecauseExclusive>(CastType::Prefix)
26142618
.map_err(|err| err.map_src(|s| s.as_mut()))?;
26152619
Ok((slf.bikeshed_recall_valid().as_mut(), suffix.as_mut()))
26162620
}
@@ -2679,7 +2683,7 @@ pub unsafe trait FromBytes: FromZeros {
26792683
{
26802684
util::assert_dst_is_not_zst::<Self>();
26812685
let (slf, prefix) = Ptr::from_mut(bytes)
2682-
.try_cast_into(CastType::Suffix)
2686+
.try_cast_into::<_, BecauseExclusive>(CastType::Suffix)
26832687
.map_err(|err| err.map_src(|s| s.as_mut()))?;
26842688
Ok((prefix.as_mut(), slf.bikeshed_recall_valid().as_mut()))
26852689
}

src/pointer/aliasing_safety.rs

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2024 The Fuchsia Authors
2+
//
3+
// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4+
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5+
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6+
// This file may not be copied, modified, or distributed except according to
7+
// those terms.
8+
9+
//! Machinery for statically proving the "aliasing-safety" of a `Ptr`.
10+
11+
use crate::{invariant, Immutable};
12+
13+
/// Pointer conversions which do not violate aliasing.
14+
///
15+
/// `U: AliasingSafe<T, A, R>` implies that a pointer conversion from `T` to `U`
16+
/// does not violate the aliasing invariant, `A`. This can be because `A` is
17+
/// [`Exclusive`] or because neither `T` nor `U` permit interior mutability.
18+
///
19+
/// # Safety
20+
///
21+
/// `U: AliasingSafe<T, A, R>` if either of the following conditions holds:
22+
/// - `A` is [`Exclusive`]
23+
/// - `T` and `U` both implement [`Immutable`]
24+
#[doc(hidden)]
25+
pub unsafe trait AliasingSafe<T: ?Sized, A: invariant::Aliasing, R: AliasingSafeReason> {}
26+
27+
/// Used to prevent user implementations of `AliasingSafeReason`.
28+
mod sealed {
29+
pub trait Sealed {}
30+
31+
impl Sealed for super::BecauseExclusive {}
32+
impl Sealed for super::BecauseImmutable {}
33+
impl<S: Sealed> Sealed for (S,) {}
34+
}
35+
36+
#[doc(hidden)]
37+
pub trait AliasingSafeReason: sealed::Sealed {}
38+
impl<R: AliasingSafeReason> AliasingSafeReason for (R,) {}
39+
40+
/// The conversion is safe because only one live `Ptr` or reference may exist to
41+
/// the referent bytes at a time.
42+
#[derive(Copy, Clone, Debug)]
43+
#[doc(hidden)]
44+
pub enum BecauseExclusive {}
45+
impl AliasingSafeReason for BecauseExclusive {}
46+
47+
/// The conversion is safe because no live `Ptr`s or references permit mutation.
48+
#[derive(Copy, Clone, Debug)]
49+
#[doc(hidden)]
50+
pub enum BecauseImmutable {}
51+
impl AliasingSafeReason for BecauseImmutable {}
52+
53+
/// # Safety
54+
///
55+
/// `T: AliasingSafe<Exclusive, BecauseExclusive>` because for all `Ptr<'a, T, I>`
56+
/// such that `I::Aliasing = Exclusive`, there cannot exist other live
57+
/// references to the memory referenced by `Ptr`.
58+
#[doc(hidden)]
59+
unsafe impl<T: ?Sized, U: ?Sized> AliasingSafe<T, invariant::Exclusive, BecauseExclusive> for U {}
60+
61+
/// # Safety
62+
///
63+
/// `U: AliasingSafe<T, A, BecauseNoCell>` because for all `Ptr<'a, T, I>` and
64+
/// `Ptr<'a, U, I>` such that `I::Aliasing = A`, all live references and live
65+
/// `Ptr`s agree, by invariant on `Immutable`, that the referenced bytes contain
66+
/// no `UnsafeCell`s, and thus do not permit mutation except via exclusive
67+
/// aliasing.
68+
unsafe impl<A, T: ?Sized, U: ?Sized> AliasingSafe<T, A, BecauseImmutable> for U
69+
where
70+
A: invariant::Aliasing,
71+
T: Immutable,
72+
U: Immutable,
73+
{
74+
}
75+
76+
// This ensures that `U: AliasingSafe<T, A>` implies `T: AliasingSafe<U, A>` in
77+
// a manner legible to rustc, which in turn means we can write simpler bounds in
78+
// some places.
79+
unsafe impl<A, T: ?Sized, U: ?Sized, R> AliasingSafe<U, A, (R,)> for T
80+
where
81+
A: invariant::Aliasing,
82+
R: AliasingSafeReason,
83+
U: AliasingSafe<T, A, R>,
84+
{
85+
}

src/pointer/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88

99
//! Abstractions over raw pointers.
1010
11+
mod aliasing_safety;
1112
mod ptr;
1213

14+
pub use aliasing_safety::{AliasingSafe, BecauseExclusive, BecauseImmutable};
1315
pub use ptr::{invariant, Ptr};
1416

1517
use crate::Unaligned;
@@ -70,5 +72,5 @@ where
7072
I: invariant::Invariants<Validity = invariant::Initialized>,
7173
I::Aliasing: invariant::AtLeast<invariant::Shared>,
7274
{
73-
ptr.as_bytes().as_ref().iter().all(|&byte| byte == 0)
75+
ptr.as_bytes::<BecauseImmutable>().as_ref().iter().all(|&byte| byte == 0)
7476
}

src/pointer/ptr.rs

+37-16
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
use core::ptr::NonNull;
1010

11-
use crate::{util::AsAddress, CastType, Immutable, KnownLayout};
11+
use crate::{util::AsAddress, CastType, KnownLayout};
1212

1313
/// Module used to gate access to [`Ptr`]'s fields.
1414
mod def {
@@ -910,7 +910,10 @@ mod _transitions {
910910
/// Casts of the referent type.
911911
mod _casts {
912912
use super::*;
913-
use crate::{layout::MetadataCastError, AlignmentError, CastError, PointerMetadata, SizeError};
913+
use crate::{
914+
layout::MetadataCastError, pointer::aliasing_safety::*, AlignmentError, CastError,
915+
PointerMetadata, SizeError,
916+
};
914917

915918
impl<'a, T, I> Ptr<'a, T, I>
916919
where
@@ -996,11 +999,14 @@ mod _casts {
996999
where
9971000
T: 'a + KnownLayout + ?Sized,
9981001
I: Invariants<Validity = Initialized>,
999-
T: Immutable,
10001002
{
10011003
/// Casts this pointer-to-initialized into a pointer-to-bytes.
10021004
#[allow(clippy::wrong_self_convention)]
1003-
pub(crate) fn as_bytes(self) -> Ptr<'a, [u8], (I::Aliasing, Aligned, Valid)> {
1005+
pub(crate) fn as_bytes<R>(self) -> Ptr<'a, [u8], (I::Aliasing, Aligned, Valid)>
1006+
where
1007+
[u8]: AliasingSafe<T, I::Aliasing, R>,
1008+
R: AliasingSafeReason,
1009+
{
10041010
let bytes = match T::size_of_val_raw(self.as_non_null()) {
10051011
Some(bytes) => bytes,
10061012
// SAFETY: `KnownLayout::size_of_val_raw` promises to always
@@ -1016,8 +1022,10 @@ mod _casts {
10161022
// pointer's address, and `bytes` is the length of `p`, so the
10171023
// returned pointer addresses the same bytes as `p`
10181024
// - `slice_from_raw_parts_mut` and `.cast` both preserve provenance
1019-
// - `T` and `[u8]` trivially contain `UnsafeCell`s at identical
1020-
// ranges [u8]`, because both are `Immutable`.
1025+
// - Because `[u8]: AliasingSafe<T, I::Aliasing, _>`, either:
1026+
// - `I::Aliasing` is `Exclusive`
1027+
// - `T` and `[u8]` are both `Immutable`, in which case they
1028+
// trivially contain `UnsafeCell`s at identical locations
10211029
let ptr: Ptr<'a, [u8], _> = unsafe {
10221030
self.cast_unsized(|p: *mut T| {
10231031
#[allow(clippy::as_conversions)]
@@ -1112,13 +1120,17 @@ mod _casts {
11121120
/// - If this is a prefix cast, `ptr` has the same address as `self`.
11131121
/// - If this is a suffix cast, `remainder` has the same address as
11141122
/// `self`.
1115-
pub(crate) fn try_cast_into<U: 'a + ?Sized + KnownLayout + Immutable>(
1123+
pub(crate) fn try_cast_into<U, R>(
11161124
self,
11171125
cast_type: CastType,
11181126
) -> Result<
11191127
(Ptr<'a, U, (I::Aliasing, Aligned, Initialized)>, Ptr<'a, [u8], I>),
11201128
CastError<Self, U>,
1121-
> {
1129+
>
1130+
where
1131+
R: AliasingSafeReason,
1132+
U: 'a + ?Sized + KnownLayout + AliasingSafe<[u8], I::Aliasing, R>,
1133+
{
11221134
crate::util::assert_dst_is_not_zst::<U>();
11231135
// PANICS: By invariant, the byte range addressed by `self.ptr` does
11241136
// not wrap around the address space. This implies that the sum of
@@ -1172,9 +1184,12 @@ mod _casts {
11721184
// does not wrap around the address space, so does `ptr`.
11731185
// 5. Since, by invariant, `target` refers to an allocation which
11741186
// is guaranteed to live for at least `'a`, so does `ptr`.
1175-
// 6. Since, by invariant, `target` conforms to the aliasing
1176-
// invariant of `I::Aliasing` with regards to its referent
1177-
// bytes, so does `ptr`.
1187+
// 6. Since `U: AliasingSafe<[u8], I::Aliasing, _>`, either:
1188+
// - `I::Aliasing` is `Exclusive`, in which case both `src`
1189+
// and `ptr` conform to `Exclusive`
1190+
// - `I::Aliasing` is `Shared` or `Any` and both `U` and
1191+
// `[u8]` are `Immutable`. In this case, neither pointer
1192+
// permits mutation, and so `Shared` aliasing is satisfied.
11781193
// 7. `ptr` conforms to the alignment invariant of `Aligned` because
11791194
// it is derived from `validate_cast_and_convert_metadata`, which
11801195
// promises that the object described by `target` is validly
@@ -1198,9 +1213,13 @@ mod _casts {
11981213
/// references the same byte range as `self`.
11991214
#[allow(unused)]
12001215
#[inline(always)]
1201-
pub(crate) fn try_cast_into_no_leftover<U: 'a + ?Sized + KnownLayout + Immutable>(
1216+
pub(crate) fn try_cast_into_no_leftover<U, R>(
12021217
self,
1203-
) -> Result<Ptr<'a, U, (I::Aliasing, Aligned, Initialized)>, CastError<Self, U>> {
1218+
) -> Result<Ptr<'a, U, (I::Aliasing, Aligned, Initialized)>, CastError<Self, U>>
1219+
where
1220+
U: 'a + ?Sized + KnownLayout + AliasingSafe<[u8], I::Aliasing, R>,
1221+
R: AliasingSafeReason,
1222+
{
12041223
// TODO(#67): Remove this allow. See NonNulSlicelExt for more
12051224
// details.
12061225
#[allow(unstable_name_collisions)]
@@ -1459,7 +1478,7 @@ mod tests {
14591478
use static_assertions::{assert_impl_all, assert_not_impl_any};
14601479

14611480
use super::*;
1462-
use crate::{util::testutil::AU64, FromBytes};
1481+
use crate::{pointer::BecauseImmutable, util::testutil::AU64, FromBytes, Immutable};
14631482

14641483
#[test]
14651484
fn test_split_at() {
@@ -1546,7 +1565,7 @@ mod tests {
15461565

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

1566-
if let Ok(slf) = Ptr::from_ref(bytes).try_cast_into_no_leftover::<T>() {
1585+
if let Ok(slf) =
1586+
Ptr::from_ref(bytes).try_cast_into_no_leftover::<T, BecauseImmutable>()
1587+
{
15671588
// SAFETY: All bytes in `bytes` have been
15681589
// initialized.
15691590
let len = unsafe { validate_and_get_len(slf) };

0 commit comments

Comments
 (0)