Skip to content

Commit 3e24b72

Browse files
committed
Pointerfication followup: Type safety and cleanup (#4621)
# Objective The `Ptr` types gives free access to the underlying `NonNull<u8>`, which adds more publicly visible pointer wrangling than there needs to be. There are also a few edge cases where Ptr types could be more readily utilized for properly validating the soundness of ECS operations. ## Solution - Replace `*Ptr(Mut)::inner` with `cast` which requires a concrete type to give the pointer. This function could also have a `debug_assert` with an alignment check to ensure that the pointer is aligned properly, but is currently not included. - Use `OwningPtr::read` in ECS macros over casting the inner pointer around.
1 parent 4a9932f commit 3e24b72

File tree

5 files changed

+91
-36
lines changed

5 files changed

+91
-36
lines changed

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
134134
#ecs_path::ptr::OwningPtr::make(self.#field, &mut func);
135135
});
136136
field_from_components.push(quote! {
137-
#field: func(ctx).inner().as_ptr().cast::<#field_type>().read(),
137+
#field: func(ctx).read::<#field_type>(),
138138
});
139139
}
140140
}

crates/bevy_ecs/src/bundle.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,7 @@ macro_rules! tuple_impl {
112112
F: FnMut(&mut T) -> OwningPtr<'_>
113113
{
114114
#[allow(non_snake_case)]
115-
let ($(mut $name,)*) = (
116-
$(func(ctx).inner().cast::<$name>(),)*
117-
);
118-
($($name.as_ptr().read(),)*)
115+
($(func(ctx).read::<$name>(),)*)
119116
}
120117

121118
#[allow(unused_variables, unused_mut)]

crates/bevy_ecs/src/component.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ impl std::fmt::Debug for ComponentDescriptor {
186186
impl ComponentDescriptor {
187187
// SAFETY: The pointer points to a valid value of type `T` and it is safe to drop this value.
188188
unsafe fn drop_ptr<T>(x: OwningPtr<'_>) {
189-
x.inner().cast::<T>().as_ptr().drop_in_place()
189+
x.drop_as::<T>()
190190
}
191191

192192
pub fn new<T: Component>() -> Self {

crates/bevy_ecs/src/ptr.rs

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,57 +43,86 @@ pub struct OwningPtr<'a>(NonNull<u8>, PhantomData<&'a mut u8>);
4343
macro_rules! impl_ptr {
4444
($ptr:ident) => {
4545
impl $ptr<'_> {
46+
/// Calculates the offset from a pointer.
47+
/// As the pointer is type-erased, there is no size information available. The provided
48+
/// `count` parameter is in raw bytes.
49+
///
50+
/// *See also: [`ptr::offset`][ptr_offset]*
51+
///
4652
/// # Safety
4753
/// the offset cannot make the existing ptr null, or take it out of bounds for its allocation.
54+
///
55+
/// [ptr_offset]: https://doc.rust-lang.org/std/primitive.pointer.html#method.offset
4856
#[inline]
4957
pub unsafe fn offset(self, count: isize) -> Self {
5058
Self(
51-
NonNull::new_unchecked(self.0.as_ptr().offset(count)),
59+
NonNull::new_unchecked(self.as_ptr().offset(count)),
5260
PhantomData,
5361
)
5462
}
5563

64+
/// Calculates the offset from a pointer (convenience for `.offset(count as isize)`).
65+
/// As the pointer is type-erased, there is no size information available. The provided
66+
/// `count` parameter is in raw bytes.
67+
///
68+
/// *See also: [`ptr::add`][ptr_add]*
69+
///
5670
/// # Safety
5771
/// the offset cannot make the existing ptr null, or take it out of bounds for its allocation.
72+
///
73+
/// [ptr_add]: https://doc.rust-lang.org/std/primitive.pointer.html#method.add
5874
#[inline]
5975
pub unsafe fn add(self, count: usize) -> Self {
6076
Self(
61-
NonNull::new_unchecked(self.0.as_ptr().add(count)),
77+
NonNull::new_unchecked(self.as_ptr().add(count)),
6278
PhantomData,
6379
)
6480
}
6581

66-
/// # Safety
82+
/// Creates a new instance from a raw pointer.
6783
///
84+
/// # Safety
6885
/// The lifetime for the returned item must not exceed the lifetime `inner` is valid for
6986
#[inline]
7087
pub unsafe fn new(inner: NonNull<u8>) -> Self {
7188
Self(inner, PhantomData)
7289
}
73-
74-
#[inline]
75-
pub fn inner(&self) -> NonNull<u8> {
76-
self.0
77-
}
7890
}
7991
};
8092
}
8193

8294
impl_ptr!(Ptr);
8395
impl<'a> Ptr<'a> {
84-
/// # Safety
96+
/// Transforms this [`Ptr`] into an [`PtrMut`]
8597
///
98+
/// # Safety
8699
/// Another [`PtrMut`] for the same [`Ptr`] must not be created until the first is dropped.
87100
#[inline]
88101
pub unsafe fn assert_unique(self) -> PtrMut<'a> {
89102
PtrMut(self.0, PhantomData)
90103
}
91104

105+
/// Transforms this [`Ptr<T>`] into a `&T` with the same lifetime
106+
///
92107
/// # Safety
93108
/// Must point to a valid `T`
94109
#[inline]
95110
pub unsafe fn deref<T>(self) -> &'a T {
96-
&*self.0.as_ptr().cast()
111+
&*self.as_ptr().cast()
112+
}
113+
114+
/// Gets the underlying pointer, erasing the associated lifetime.
115+
///
116+
/// If possible, it is strongly encouraged to use [`deref`](Self::deref) over this function,
117+
/// as it retains the lifetime.
118+
///
119+
/// # Safety
120+
/// All subsequent operations to the returned pointer must be valid inside the
121+
/// associated lifetime.
122+
#[inline]
123+
#[allow(clippy::wrong_self_convention)]
124+
pub unsafe fn as_ptr(self) -> *mut u8 {
125+
self.0.as_ptr()
97126
}
98127
}
99128
impl_ptr!(PtrMut);
@@ -113,7 +142,21 @@ impl<'a> PtrMut<'a> {
113142
/// Must point to a valid `T`
114143
#[inline]
115144
pub unsafe fn deref_mut<T>(self) -> &'a mut T {
116-
&mut *self.inner().as_ptr().cast()
145+
&mut *self.as_ptr().cast()
146+
}
147+
148+
/// Gets the underlying pointer, erasing the associated lifetime.
149+
///
150+
/// If possible, it is strongly encouraged to use [`deref_mut`](Self::deref_mut) over
151+
/// this function, as it retains the lifetime.
152+
///
153+
/// # Safety
154+
/// All subsequent operations to the returned pointer must be valid inside the
155+
/// associated lifetime.
156+
#[inline]
157+
#[allow(clippy::wrong_self_convention)]
158+
pub unsafe fn as_ptr(self) -> *mut u8 {
159+
self.0.as_ptr()
117160
}
118161
}
119162
impl_ptr!(OwningPtr);
@@ -132,7 +175,30 @@ impl<'a> OwningPtr<'a> {
132175
/// Must point to a valid `T`.
133176
#[inline]
134177
pub unsafe fn read<T>(self) -> T {
135-
self.inner().as_ptr().cast::<T>().read()
178+
self.as_ptr().cast::<T>().read()
179+
}
180+
181+
//// Consumes the [`OwningPtr`] to drop the underlying data of type `T`.
182+
///
183+
/// # Safety
184+
/// Must point to a valid `T`.
185+
#[inline]
186+
pub unsafe fn drop_as<T>(self) {
187+
self.as_ptr().cast::<T>().drop_in_place()
188+
}
189+
190+
/// Gets the underlying pointer, erasing the associated lifetime.
191+
///
192+
/// If possible, it is strongly encouraged to use the other more type-safe functions
193+
/// over this function.
194+
///
195+
/// # Safety
196+
/// All subsequent operations to the returned pointer must be valid inside the
197+
/// associated lifetime.
198+
#[inline]
199+
#[allow(clippy::wrong_self_convention)]
200+
pub unsafe fn as_ptr(self) -> *mut u8 {
201+
self.0.as_ptr()
136202
}
137203
}
138204

crates/bevy_ecs/src/storage/blob_vec.rs

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ impl BlobVec {
101101
std::alloc::alloc(new_layout)
102102
} else {
103103
std::alloc::realloc(
104-
self.get_ptr_mut().inner().as_ptr(),
104+
self.get_ptr_mut().as_ptr(),
105105
array_layout(&self.item_layout, self.capacity)
106106
.expect("array layout should be valid"),
107107
new_layout.size(),
@@ -121,11 +121,7 @@ impl BlobVec {
121121
pub unsafe fn initialize_unchecked(&mut self, index: usize, value: OwningPtr<'_>) {
122122
debug_assert!(index < self.len());
123123
let ptr = self.get_unchecked_mut(index);
124-
std::ptr::copy_nonoverlapping::<u8>(
125-
value.inner().as_ptr(),
126-
ptr.inner().as_ptr(),
127-
self.item_layout.size(),
128-
);
124+
std::ptr::copy_nonoverlapping::<u8>(value.as_ptr(), ptr.as_ptr(), self.item_layout.size());
129125
}
130126

131127
/// # Safety
@@ -142,15 +138,11 @@ impl BlobVec {
142138
// in the collection), so we get a double drop. To prevent that, we set len to 0 until we're
143139
// done.
144140
let old_len = self.len;
145-
let ptr = self.get_unchecked_mut(index).promote().inner();
141+
let ptr = self.get_unchecked_mut(index).promote().as_ptr();
146142
self.len = 0;
147143
// Drop the old value, then write back, justifying the promotion
148-
(self.drop)(OwningPtr::new(ptr));
149-
std::ptr::copy_nonoverlapping::<u8>(
150-
value.inner().as_ptr(),
151-
ptr.as_ptr(),
152-
self.item_layout.size(),
153-
);
144+
(self.drop)(OwningPtr::new(NonNull::new_unchecked(ptr)));
145+
std::ptr::copy_nonoverlapping::<u8>(value.as_ptr(), ptr, self.item_layout.size());
154146
self.len = old_len;
155147
}
156148

@@ -192,13 +184,13 @@ impl BlobVec {
192184
let last = self.len - 1;
193185
let swap_scratch = self.swap_scratch.as_ptr();
194186
std::ptr::copy_nonoverlapping::<u8>(
195-
self.get_unchecked_mut(index).inner().as_ptr(),
187+
self.get_unchecked_mut(index).as_ptr(),
196188
swap_scratch,
197189
self.item_layout.size(),
198190
);
199191
std::ptr::copy::<u8>(
200-
self.get_unchecked_mut(last).inner().as_ptr(),
201-
self.get_unchecked_mut(index).inner().as_ptr(),
192+
self.get_unchecked_mut(last).as_ptr(),
193+
self.get_unchecked_mut(index).as_ptr(),
202194
self.item_layout.size(),
203195
);
204196
self.len -= 1;
@@ -280,7 +272,7 @@ impl Drop for BlobVec {
280272
array_layout(&self.item_layout, self.capacity).expect("array layout should be valid");
281273
if array_layout.size() > 0 {
282274
unsafe {
283-
std::alloc::dealloc(self.get_ptr_mut().inner().as_ptr(), array_layout);
275+
std::alloc::dealloc(self.get_ptr_mut().as_ptr(), array_layout);
284276
std::alloc::dealloc(self.swap_scratch.as_ptr(), self.item_layout);
285277
}
286278
}
@@ -350,7 +342,7 @@ mod tests {
350342

351343
// SAFETY: The pointer points to a valid value of type `T` and it is safe to drop this value.
352344
unsafe fn drop_ptr<T>(x: OwningPtr<'_>) {
353-
x.inner().cast::<T>().as_ptr().drop_in_place()
345+
x.drop_as::<T>()
354346
}
355347

356348
/// # Safety

0 commit comments

Comments
 (0)