Skip to content

Commit 512f376

Browse files
committed
Document alignment requirements of Ptr, PtrMut and OwningPtr (#7151)
# Objective The types in the `bevy_ptr` accidentally did not document anything relating to alignment. This is unsound as many methods rely on the pointer being correctly aligned. ## Solution This PR introduces new safety invariants on the `$ptr::new`, `$ptr::byte_offset` and `$ptr::byte_add` methods requiring them to keep the pointer aligned. This is consistent with the documentation of these pointer types which document them as being "type erased borrows". As it was pointed out (by @JoJoJet in #7117) that working with unaligned pointers can be useful (for example our commands abstraction which does not try to align anything properly, see #7039) this PR also introduces a default type parameter to all the pointer types that specifies whether it has alignment requirements or not. I could not find any code in `bevy_ecs` that would need unaligned pointers right now so this is going unused. --- ## Changelog - Correctly document alignment requirements on `bevy_ptr` types. - Support variants of `bevy_ptr` types that do not require being correctly aligned for the pointee type. ## Migration Guide - Safety invariants on `bevy_ptr` types' `new` `byte_add` and `byte_offset` methods have been changed. All callers should re-audit for soundness.
1 parent a13b6f8 commit 512f376

File tree

1 file changed

+101
-29
lines changed

1 file changed

+101
-29
lines changed

crates/bevy_ptr/src/lib.rs

+101-29
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,37 @@ use core::{
77
cell::UnsafeCell, marker::PhantomData, mem::ManuallyDrop, num::NonZeroUsize, ptr::NonNull,
88
};
99

10+
#[derive(Copy, Clone)]
11+
/// Used as a type argument to [`Ptr`], [`PtrMut`] and [`OwningPtr`] to specify that the pointer is aligned.
12+
pub struct Aligned;
13+
#[derive(Copy, Clone)]
14+
/// Used as a type argument to [`Ptr`], [`PtrMut`] and [`OwningPtr`] to specify that the pointer is not aligned.
15+
pub struct Unaligned;
16+
17+
/// Trait that is only implemented for [`Aligned`] and [`Unaligned`] to work around the lack of ability
18+
/// to have const generics of an enum.
19+
pub trait IsAligned: sealed::Sealed {}
20+
impl IsAligned for Aligned {}
21+
impl IsAligned for Unaligned {}
22+
23+
mod sealed {
24+
pub trait Sealed {}
25+
impl Sealed for super::Aligned {}
26+
impl Sealed for super::Unaligned {}
27+
}
28+
1029
/// Type-erased borrow of some unknown type chosen when constructing this type.
1130
///
1231
/// This type tries to act "borrow-like" which means that:
1332
/// - It should be considered immutable: its target must not be changed while this pointer is alive.
1433
/// - It must always points to a valid value of whatever the pointee type is.
1534
/// - The lifetime `'a` accurately represents how long the pointer is valid for.
35+
/// - Must be sufficiently aligned for the unknown pointee type.
1636
///
1737
/// It may be helpful to think of this type as similar to `&'a dyn Any` but without
1838
/// the metadata and able to point to data that does not correspond to a Rust type.
1939
#[derive(Copy, Clone)]
20-
pub struct Ptr<'a>(NonNull<u8>, PhantomData<&'a u8>);
40+
pub struct Ptr<'a, A: IsAligned = Aligned>(NonNull<u8>, PhantomData<(&'a u8, A)>);
2141

2242
/// Type-erased mutable borrow of some unknown type chosen when constructing this type.
2343
///
@@ -26,10 +46,11 @@ pub struct Ptr<'a>(NonNull<u8>, PhantomData<&'a u8>);
2646
/// aliased mutability.
2747
/// - It must always points to a valid value of whatever the pointee type is.
2848
/// - The lifetime `'a` accurately represents how long the pointer is valid for.
49+
/// - Must be sufficiently aligned for the unknown pointee type.
2950
///
3051
/// It may be helpful to think of this type as similar to `&'a mut dyn Any` but without
3152
/// the metadata and able to point to data that does not correspond to a Rust type.
32-
pub struct PtrMut<'a>(NonNull<u8>, PhantomData<&'a mut u8>);
53+
pub struct PtrMut<'a, A: IsAligned = Aligned>(NonNull<u8>, PhantomData<(&'a mut u8, A)>);
3354

3455
/// Type-erased Box-like pointer to some unknown type chosen when constructing this type.
3556
/// Conceptually represents ownership of whatever data is being pointed to and so is
@@ -42,22 +63,32 @@ pub struct PtrMut<'a>(NonNull<u8>, PhantomData<&'a mut u8>);
4263
/// to aliased mutability and potentially use after free bugs.
4364
/// - It must always points to a valid value of whatever the pointee type is.
4465
/// - The lifetime `'a` accurately represents how long the pointer is valid for.
66+
/// - Must be sufficiently aligned for the unknown pointee type.
4567
///
4668
/// It may be helpful to think of this type as similar to `&'a mut ManuallyDrop<dyn Any>` but
4769
/// without the metadata and able to point to data that does not correspond to a Rust type.
48-
pub struct OwningPtr<'a>(NonNull<u8>, PhantomData<&'a mut u8>);
70+
pub struct OwningPtr<'a, A: IsAligned = Aligned>(NonNull<u8>, PhantomData<(&'a mut u8, A)>);
4971

5072
macro_rules! impl_ptr {
5173
($ptr:ident) => {
52-
impl $ptr<'_> {
74+
impl<'a> $ptr<'a, Aligned> {
75+
/// Removes the alignment requirement of this pointer
76+
pub fn to_unaligned(self) -> $ptr<'a, Unaligned> {
77+
$ptr(self.0, PhantomData)
78+
}
79+
}
80+
81+
impl<A: IsAligned> $ptr<'_, A> {
5382
/// Calculates the offset from a pointer.
5483
/// As the pointer is type-erased, there is no size information available. The provided
5584
/// `count` parameter is in raw bytes.
5685
///
5786
/// *See also: [`ptr::offset`][ptr_offset]*
5887
///
5988
/// # Safety
60-
/// the offset cannot make the existing ptr null, or take it out of bounds for its allocation.
89+
/// - The offset cannot make the existing ptr null, or take it out of bounds for its allocation.
90+
/// - If the `A` type parameter is [`Aligned`] then the offset must not make the resulting pointer
91+
/// be unaligned for the pointee type.
6192
///
6293
/// [ptr_offset]: https://doc.rust-lang.org/std/primitive.pointer.html#method.offset
6394
#[inline]
@@ -75,7 +106,9 @@ macro_rules! impl_ptr {
75106
/// *See also: [`ptr::add`][ptr_add]*
76107
///
77108
/// # Safety
78-
/// the offset cannot make the existing ptr null, or take it out of bounds for its allocation.
109+
/// - The offset cannot make the existing ptr null, or take it out of bounds for its allocation.
110+
/// - If the `A` type parameter is [`Aligned`] then the offset must not make the resulting pointer
111+
/// be unaligned for the pointee type.
79112
///
80113
/// [ptr_add]: https://doc.rust-lang.org/std/primitive.pointer.html#method.add
81114
#[inline]
@@ -85,18 +118,9 @@ macro_rules! impl_ptr {
85118
PhantomData,
86119
)
87120
}
88-
89-
/// Creates a new instance from a raw pointer.
90-
///
91-
/// # Safety
92-
/// The lifetime for the returned item must not exceed the lifetime `inner` is valid for
93-
#[inline]
94-
pub unsafe fn new(inner: NonNull<u8>) -> Self {
95-
Self(inner, PhantomData)
96-
}
97121
}
98122

99-
impl Pointer for $ptr<'_> {
123+
impl<A: IsAligned> Pointer for $ptr<'_, A> {
100124
#[inline]
101125
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
102126
Pointer::fmt(&self.0, f)
@@ -109,20 +133,35 @@ impl_ptr!(Ptr);
109133
impl_ptr!(PtrMut);
110134
impl_ptr!(OwningPtr);
111135

112-
impl<'a> Ptr<'a> {
136+
impl<'a, A: IsAligned> Ptr<'a, A> {
137+
/// Creates a new instance from a raw pointer.
138+
///
139+
/// # Safety
140+
/// - `inner` must point to valid value of whatever the pointee type is.
141+
/// - If the `A` type parameter is [`Aligned`] then `inner` must be sufficiently aligned for the pointee type.
142+
/// - `inner` must have correct provenance to allow reads of the pointee type.
143+
/// - The lifetime `'a` must be constrained such that this [`Ptr`] will stay valid and nothing
144+
/// can mutate the pointee while this [`Ptr`] is live except through an `UnsafeCell`.
145+
#[inline]
146+
pub unsafe fn new(inner: NonNull<u8>) -> Self {
147+
Self(inner, PhantomData)
148+
}
149+
113150
/// Transforms this [`Ptr`] into an [`PtrMut`]
114151
///
115152
/// # Safety
116153
/// Another [`PtrMut`] for the same [`Ptr`] must not be created until the first is dropped.
117154
#[inline]
118-
pub unsafe fn assert_unique(self) -> PtrMut<'a> {
155+
pub unsafe fn assert_unique(self) -> PtrMut<'a, A> {
119156
PtrMut(self.0, PhantomData)
120157
}
121158

122159
/// Transforms this [`Ptr<T>`] into a `&T` with the same lifetime
123160
///
124161
/// # Safety
125-
/// Must point to a valid `T`
162+
/// - `T` must be the erased pointee type for this [`Ptr`].
163+
/// - If the type parameter `A` is `Unaligned` then this pointer must be sufficiently aligned
164+
/// for the pointee type `T`.
126165
#[inline]
127166
pub unsafe fn deref<T>(self) -> &'a T {
128167
&*self.as_ptr().cast()
@@ -148,20 +187,35 @@ impl<'a, T> From<&'a T> for Ptr<'a> {
148187
}
149188
}
150189

151-
impl<'a> PtrMut<'a> {
190+
impl<'a, A: IsAligned> PtrMut<'a, A> {
191+
/// Creates a new instance from a raw pointer.
192+
///
193+
/// # Safety
194+
/// - `inner` must point to valid value of whatever the pointee type is.
195+
/// - If the `A` type parameter is [`Aligned`] then `inner` must be sufficiently aligned for the pointee type.
196+
/// - `inner` must have correct provenance to allow read and writes of the pointee type.
197+
/// - The lifetime `'a` must be constrained such that this [`PtrMut`] will stay valid and nothing
198+
/// else can read or mutate the pointee while this [`PtrMut`] is live.
199+
#[inline]
200+
pub unsafe fn new(inner: NonNull<u8>) -> Self {
201+
Self(inner, PhantomData)
202+
}
203+
152204
/// Transforms this [`PtrMut`] into an [`OwningPtr`]
153205
///
154206
/// # Safety
155207
/// Must have right to drop or move out of [`PtrMut`].
156208
#[inline]
157-
pub unsafe fn promote(self) -> OwningPtr<'a> {
209+
pub unsafe fn promote(self) -> OwningPtr<'a, A> {
158210
OwningPtr(self.0, PhantomData)
159211
}
160212

161213
/// Transforms this [`PtrMut<T>`] into a `&mut T` with the same lifetime
162214
///
163215
/// # Safety
164-
/// Must point to a valid `T`
216+
/// - `T` must be the erased pointee type for this [`PtrMut`].
217+
/// - If the type parameter `A` is [`Unaligned`] then this pointer must be sufficiently aligned
218+
/// for the pointee type `T`.
165219
#[inline]
166220
pub unsafe fn deref_mut<T>(self) -> &'a mut T {
167221
&mut *self.as_ptr().cast()
@@ -177,16 +231,16 @@ impl<'a> PtrMut<'a> {
177231
self.0.as_ptr()
178232
}
179233

180-
/// Gets a `PtrMut` from this with a smaller lifetime.
234+
/// Gets a [`PtrMut`] from this with a smaller lifetime.
181235
#[inline]
182-
pub fn reborrow(&mut self) -> PtrMut<'_> {
236+
pub fn reborrow(&mut self) -> PtrMut<'_, A> {
183237
// SAFE: the ptrmut we're borrowing from is assumed to be valid
184238
unsafe { PtrMut::new(self.0) }
185239
}
186240

187241
/// Gets an immutable reference from this mutable reference
188242
#[inline]
189-
pub fn as_ref(&self) -> Ptr<'_> {
243+
pub fn as_ref(&self) -> Ptr<'_, A> {
190244
// SAFE: The `PtrMut` type's guarantees about the validity of this pointer are a superset of `Ptr` s guarantees
191245
unsafe { Ptr::new(self.0) }
192246
}
@@ -210,11 +264,27 @@ impl<'a> OwningPtr<'a> {
210264
// so it's safe to promote it to an owning pointer.
211265
f(unsafe { PtrMut::from(&mut *temp).promote() })
212266
}
267+
}
268+
impl<'a, A: IsAligned> OwningPtr<'a, A> {
269+
/// Creates a new instance from a raw pointer.
270+
///
271+
/// # Safety
272+
/// - `inner` must point to valid value of whatever the pointee type is.
273+
/// - If the `A` type parameter is [`Aligned`] then `inner` must be sufficiently aligned for the pointee type.
274+
/// - `inner` must have correct provenance to allow read and writes of the pointee type.
275+
/// - The lifetime `'a` must be constrained such that this [`OwningPtr`] will stay valid and nothing
276+
/// else can read or mutate the pointee while this [`OwningPtr`] is live.
277+
#[inline]
278+
pub unsafe fn new(inner: NonNull<u8>) -> Self {
279+
Self(inner, PhantomData)
280+
}
213281

214282
/// Consumes the [`OwningPtr`] to obtain ownership of the underlying data of type `T`.
215283
///
216284
/// # Safety
217-
/// Must point to a valid `T`.
285+
/// - `T` must be the erased pointee type for this [`OwningPtr`].
286+
/// - If the type parameter `A` is `Unaligned` then this pointer must be sufficiently aligned
287+
/// for the pointee type `T`.
218288
#[inline]
219289
pub unsafe fn read<T>(self) -> T {
220290
self.as_ptr().cast::<T>().read()
@@ -223,7 +293,9 @@ impl<'a> OwningPtr<'a> {
223293
/// Consumes the [`OwningPtr`] to drop the underlying data of type `T`.
224294
///
225295
/// # Safety
226-
/// Must point to a valid `T`.
296+
/// - `T` must be the erased pointee type for this [`OwningPtr`].
297+
/// - If the type parameter `A` is `Unaligned` then this pointer must be sufficiently aligned
298+
/// for the pointee type `T`.
227299
#[inline]
228300
pub unsafe fn drop_as<T>(self) {
229301
self.as_ptr().cast::<T>().drop_in_place();
@@ -241,14 +313,14 @@ impl<'a> OwningPtr<'a> {
241313

242314
/// Gets an immutable pointer from this owned pointer.
243315
#[inline]
244-
pub fn as_ref(&self) -> Ptr<'_> {
316+
pub fn as_ref(&self) -> Ptr<'_, A> {
245317
// SAFE: The `Owning` type's guarantees about the validity of this pointer are a superset of `Ptr` s guarantees
246318
unsafe { Ptr::new(self.0) }
247319
}
248320

249321
/// Gets a mutable pointer from this owned pointer.
250322
#[inline]
251-
pub fn as_mut(&mut self) -> PtrMut<'_> {
323+
pub fn as_mut(&mut self) -> PtrMut<'_, A> {
252324
// SAFE: The `Owning` type's guarantees about the validity of this pointer are a superset of `Ptr` s guarantees
253325
unsafe { PtrMut::new(self.0) }
254326
}

0 commit comments

Comments
 (0)