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.
This commit is contained in:
		
							parent
							
								
									4a9932fa8e
								
							
						
					
					
						commit
						3e24b725af
					
				| @ -134,7 +134,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream { | |||||||
|                 #ecs_path::ptr::OwningPtr::make(self.#field, &mut func); |                 #ecs_path::ptr::OwningPtr::make(self.#field, &mut func); | ||||||
|             }); |             }); | ||||||
|             field_from_components.push(quote! { |             field_from_components.push(quote! { | ||||||
|                 #field: func(ctx).inner().as_ptr().cast::<#field_type>().read(), |                 #field: func(ctx).read::<#field_type>(), | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -112,10 +112,7 @@ macro_rules! tuple_impl { | |||||||
|                 F: FnMut(&mut T) -> OwningPtr<'_> |                 F: FnMut(&mut T) -> OwningPtr<'_> | ||||||
|             { |             { | ||||||
|                 #[allow(non_snake_case)] |                 #[allow(non_snake_case)] | ||||||
|                 let ($(mut $name,)*) = ( |                 ($(func(ctx).read::<$name>(),)*) | ||||||
|                     $(func(ctx).inner().cast::<$name>(),)* |  | ||||||
|                 ); |  | ||||||
|                 ($($name.as_ptr().read(),)*) |  | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             #[allow(unused_variables, unused_mut)] |             #[allow(unused_variables, unused_mut)] | ||||||
|  | |||||||
| @ -186,7 +186,7 @@ impl std::fmt::Debug for ComponentDescriptor { | |||||||
| impl ComponentDescriptor { | impl ComponentDescriptor { | ||||||
|     // SAFETY: The pointer points to a valid value of type `T` and it is safe to drop this value.
 |     // SAFETY: The pointer points to a valid value of type `T` and it is safe to drop this value.
 | ||||||
|     unsafe fn drop_ptr<T>(x: OwningPtr<'_>) { |     unsafe fn drop_ptr<T>(x: OwningPtr<'_>) { | ||||||
|         x.inner().cast::<T>().as_ptr().drop_in_place() |         x.drop_as::<T>() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn new<T: Component>() -> Self { |     pub fn new<T: Component>() -> Self { | ||||||
|  | |||||||
| @ -43,57 +43,86 @@ pub struct OwningPtr<'a>(NonNull<u8>, PhantomData<&'a mut u8>); | |||||||
| macro_rules! impl_ptr { | macro_rules! impl_ptr { | ||||||
|     ($ptr:ident) => { |     ($ptr:ident) => { | ||||||
|         impl $ptr<'_> { |         impl $ptr<'_> { | ||||||
|  |             /// Calculates the offset from a pointer.
 | ||||||
|  |             /// As the pointer is type-erased, there is no size information available. The provided
 | ||||||
|  |             /// `count` parameter is in raw bytes.
 | ||||||
|  |             ///
 | ||||||
|  |             /// *See also: [`ptr::offset`][ptr_offset]*
 | ||||||
|  |             ///
 | ||||||
|             /// # Safety
 |             /// # Safety
 | ||||||
|             /// the offset cannot make the existing ptr null, or take it out of bounds for its allocation.
 |             /// the offset cannot make the existing ptr null, or take it out of bounds for its allocation.
 | ||||||
|  |             ///
 | ||||||
|  |             /// [ptr_offset]: https://doc.rust-lang.org/std/primitive.pointer.html#method.offset
 | ||||||
|             #[inline] |             #[inline] | ||||||
|             pub unsafe fn offset(self, count: isize) -> Self { |             pub unsafe fn offset(self, count: isize) -> Self { | ||||||
|                 Self( |                 Self( | ||||||
|                     NonNull::new_unchecked(self.0.as_ptr().offset(count)), |                     NonNull::new_unchecked(self.as_ptr().offset(count)), | ||||||
|                     PhantomData, |                     PhantomData, | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             /// Calculates the offset from a pointer (convenience for `.offset(count as isize)`).
 | ||||||
|  |             /// As the pointer is type-erased, there is no size information available. The provided
 | ||||||
|  |             /// `count` parameter is in raw bytes.
 | ||||||
|  |             ///
 | ||||||
|  |             /// *See also: [`ptr::add`][ptr_add]*
 | ||||||
|  |             ///
 | ||||||
|             /// # Safety
 |             /// # Safety
 | ||||||
|             /// the offset cannot make the existing ptr null, or take it out of bounds for its allocation.
 |             /// the offset cannot make the existing ptr null, or take it out of bounds for its allocation.
 | ||||||
|  |             ///
 | ||||||
|  |             /// [ptr_add]: https://doc.rust-lang.org/std/primitive.pointer.html#method.add
 | ||||||
|             #[inline] |             #[inline] | ||||||
|             pub unsafe fn add(self, count: usize) -> Self { |             pub unsafe fn add(self, count: usize) -> Self { | ||||||
|                 Self( |                 Self( | ||||||
|                     NonNull::new_unchecked(self.0.as_ptr().add(count)), |                     NonNull::new_unchecked(self.as_ptr().add(count)), | ||||||
|                     PhantomData, |                     PhantomData, | ||||||
|                 ) |                 ) | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             /// # Safety
 |             /// Creates a new instance from a raw pointer.
 | ||||||
|             ///
 |             ///
 | ||||||
|  |             /// # Safety
 | ||||||
|             /// The lifetime for the returned item must not exceed the lifetime `inner` is valid for
 |             /// The lifetime for the returned item must not exceed the lifetime `inner` is valid for
 | ||||||
|             #[inline] |             #[inline] | ||||||
|             pub unsafe fn new(inner: NonNull<u8>) -> Self { |             pub unsafe fn new(inner: NonNull<u8>) -> Self { | ||||||
|                 Self(inner, PhantomData) |                 Self(inner, PhantomData) | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|             #[inline] |  | ||||||
|             pub fn inner(&self) -> NonNull<u8> { |  | ||||||
|                 self.0 |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl_ptr!(Ptr); | impl_ptr!(Ptr); | ||||||
| impl<'a> Ptr<'a> { | impl<'a> Ptr<'a> { | ||||||
|     /// # Safety
 |     /// Transforms this [`Ptr`] into an [`PtrMut`]
 | ||||||
|     ///
 |     ///
 | ||||||
|  |     /// # Safety
 | ||||||
|     /// Another [`PtrMut`] for the same [`Ptr`] must not be created until the first is dropped.
 |     /// Another [`PtrMut`] for the same [`Ptr`] must not be created until the first is dropped.
 | ||||||
|     #[inline] |     #[inline] | ||||||
|     pub unsafe fn assert_unique(self) -> PtrMut<'a> { |     pub unsafe fn assert_unique(self) -> PtrMut<'a> { | ||||||
|         PtrMut(self.0, PhantomData) |         PtrMut(self.0, PhantomData) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /// Transforms this [`Ptr<T>`] into a `&T` with the same lifetime
 | ||||||
|  |     ///
 | ||||||
|     /// # Safety
 |     /// # Safety
 | ||||||
|     /// Must point to a valid `T`
 |     /// Must point to a valid `T`
 | ||||||
|     #[inline] |     #[inline] | ||||||
|     pub unsafe fn deref<T>(self) -> &'a T { |     pub unsafe fn deref<T>(self) -> &'a T { | ||||||
|         &*self.0.as_ptr().cast() |         &*self.as_ptr().cast() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Gets the underlying pointer, erasing the associated lifetime.
 | ||||||
|  |     ///
 | ||||||
|  |     /// If possible, it is strongly encouraged to use [`deref`](Self::deref) over this function,
 | ||||||
|  |     /// as it retains the lifetime.
 | ||||||
|  |     ///
 | ||||||
|  |     /// # Safety
 | ||||||
|  |     /// All subsequent operations to the returned pointer must be valid inside the
 | ||||||
|  |     /// associated lifetime.
 | ||||||
|  |     #[inline] | ||||||
|  |     #[allow(clippy::wrong_self_convention)] | ||||||
|  |     pub unsafe fn as_ptr(self) -> *mut u8 { | ||||||
|  |         self.0.as_ptr() | ||||||
|     } |     } | ||||||
| } | } | ||||||
| impl_ptr!(PtrMut); | impl_ptr!(PtrMut); | ||||||
| @ -113,7 +142,21 @@ impl<'a> PtrMut<'a> { | |||||||
|     /// Must point to a valid `T`
 |     /// Must point to a valid `T`
 | ||||||
|     #[inline] |     #[inline] | ||||||
|     pub unsafe fn deref_mut<T>(self) -> &'a mut T { |     pub unsafe fn deref_mut<T>(self) -> &'a mut T { | ||||||
|         &mut *self.inner().as_ptr().cast() |         &mut *self.as_ptr().cast() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Gets the underlying pointer, erasing the associated lifetime.
 | ||||||
|  |     ///
 | ||||||
|  |     /// If possible, it is strongly encouraged to use [`deref_mut`](Self::deref_mut) over
 | ||||||
|  |     /// this function, as it retains the lifetime.
 | ||||||
|  |     ///
 | ||||||
|  |     /// # Safety
 | ||||||
|  |     /// All subsequent operations to the returned pointer must be valid inside the
 | ||||||
|  |     /// associated lifetime.
 | ||||||
|  |     #[inline] | ||||||
|  |     #[allow(clippy::wrong_self_convention)] | ||||||
|  |     pub unsafe fn as_ptr(self) -> *mut u8 { | ||||||
|  |         self.0.as_ptr() | ||||||
|     } |     } | ||||||
| } | } | ||||||
| impl_ptr!(OwningPtr); | impl_ptr!(OwningPtr); | ||||||
| @ -132,7 +175,30 @@ impl<'a> OwningPtr<'a> { | |||||||
|     /// Must point to a valid `T`.
 |     /// Must point to a valid `T`.
 | ||||||
|     #[inline] |     #[inline] | ||||||
|     pub unsafe fn read<T>(self) -> T { |     pub unsafe fn read<T>(self) -> T { | ||||||
|         self.inner().as_ptr().cast::<T>().read() |         self.as_ptr().cast::<T>().read() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     //// Consumes the [`OwningPtr`] to drop the underlying data of type `T`.
 | ||||||
|  |     ///
 | ||||||
|  |     /// # Safety
 | ||||||
|  |     /// Must point to a valid `T`.
 | ||||||
|  |     #[inline] | ||||||
|  |     pub unsafe fn drop_as<T>(self) { | ||||||
|  |         self.as_ptr().cast::<T>().drop_in_place() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Gets the underlying pointer, erasing the associated lifetime.
 | ||||||
|  |     ///
 | ||||||
|  |     /// If possible, it is strongly encouraged to use the other more type-safe functions
 | ||||||
|  |     /// over this function.
 | ||||||
|  |     ///
 | ||||||
|  |     /// # Safety
 | ||||||
|  |     /// All subsequent operations to the returned pointer must be valid inside the
 | ||||||
|  |     /// associated lifetime.
 | ||||||
|  |     #[inline] | ||||||
|  |     #[allow(clippy::wrong_self_convention)] | ||||||
|  |     pub unsafe fn as_ptr(self) -> *mut u8 { | ||||||
|  |         self.0.as_ptr() | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -101,7 +101,7 @@ impl BlobVec { | |||||||
|                 std::alloc::alloc(new_layout) |                 std::alloc::alloc(new_layout) | ||||||
|             } else { |             } else { | ||||||
|                 std::alloc::realloc( |                 std::alloc::realloc( | ||||||
|                     self.get_ptr_mut().inner().as_ptr(), |                     self.get_ptr_mut().as_ptr(), | ||||||
|                     array_layout(&self.item_layout, self.capacity) |                     array_layout(&self.item_layout, self.capacity) | ||||||
|                         .expect("array layout should be valid"), |                         .expect("array layout should be valid"), | ||||||
|                     new_layout.size(), |                     new_layout.size(), | ||||||
| @ -121,11 +121,7 @@ impl BlobVec { | |||||||
|     pub unsafe fn initialize_unchecked(&mut self, index: usize, value: OwningPtr<'_>) { |     pub unsafe fn initialize_unchecked(&mut self, index: usize, value: OwningPtr<'_>) { | ||||||
|         debug_assert!(index < self.len()); |         debug_assert!(index < self.len()); | ||||||
|         let ptr = self.get_unchecked_mut(index); |         let ptr = self.get_unchecked_mut(index); | ||||||
|         std::ptr::copy_nonoverlapping::<u8>( |         std::ptr::copy_nonoverlapping::<u8>(value.as_ptr(), ptr.as_ptr(), self.item_layout.size()); | ||||||
|             value.inner().as_ptr(), |  | ||||||
|             ptr.inner().as_ptr(), |  | ||||||
|             self.item_layout.size(), |  | ||||||
|         ); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// # Safety
 |     /// # Safety
 | ||||||
| @ -142,15 +138,11 @@ impl BlobVec { | |||||||
|         // in the collection), so we get a double drop. To prevent that, we set len to 0 until we're
 |         // in the collection), so we get a double drop. To prevent that, we set len to 0 until we're
 | ||||||
|         // done.
 |         // done.
 | ||||||
|         let old_len = self.len; |         let old_len = self.len; | ||||||
|         let ptr = self.get_unchecked_mut(index).promote().inner(); |         let ptr = self.get_unchecked_mut(index).promote().as_ptr(); | ||||||
|         self.len = 0; |         self.len = 0; | ||||||
|         // Drop the old value, then write back, justifying the promotion
 |         // Drop the old value, then write back, justifying the promotion
 | ||||||
|         (self.drop)(OwningPtr::new(ptr)); |         (self.drop)(OwningPtr::new(NonNull::new_unchecked(ptr))); | ||||||
|         std::ptr::copy_nonoverlapping::<u8>( |         std::ptr::copy_nonoverlapping::<u8>(value.as_ptr(), ptr, self.item_layout.size()); | ||||||
|             value.inner().as_ptr(), |  | ||||||
|             ptr.as_ptr(), |  | ||||||
|             self.item_layout.size(), |  | ||||||
|         ); |  | ||||||
|         self.len = old_len; |         self.len = old_len; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -192,13 +184,13 @@ impl BlobVec { | |||||||
|         let last = self.len - 1; |         let last = self.len - 1; | ||||||
|         let swap_scratch = self.swap_scratch.as_ptr(); |         let swap_scratch = self.swap_scratch.as_ptr(); | ||||||
|         std::ptr::copy_nonoverlapping::<u8>( |         std::ptr::copy_nonoverlapping::<u8>( | ||||||
|             self.get_unchecked_mut(index).inner().as_ptr(), |             self.get_unchecked_mut(index).as_ptr(), | ||||||
|             swap_scratch, |             swap_scratch, | ||||||
|             self.item_layout.size(), |             self.item_layout.size(), | ||||||
|         ); |         ); | ||||||
|         std::ptr::copy::<u8>( |         std::ptr::copy::<u8>( | ||||||
|             self.get_unchecked_mut(last).inner().as_ptr(), |             self.get_unchecked_mut(last).as_ptr(), | ||||||
|             self.get_unchecked_mut(index).inner().as_ptr(), |             self.get_unchecked_mut(index).as_ptr(), | ||||||
|             self.item_layout.size(), |             self.item_layout.size(), | ||||||
|         ); |         ); | ||||||
|         self.len -= 1; |         self.len -= 1; | ||||||
| @ -280,7 +272,7 @@ impl Drop for BlobVec { | |||||||
|             array_layout(&self.item_layout, self.capacity).expect("array layout should be valid"); |             array_layout(&self.item_layout, self.capacity).expect("array layout should be valid"); | ||||||
|         if array_layout.size() > 0 { |         if array_layout.size() > 0 { | ||||||
|             unsafe { |             unsafe { | ||||||
|                 std::alloc::dealloc(self.get_ptr_mut().inner().as_ptr(), array_layout); |                 std::alloc::dealloc(self.get_ptr_mut().as_ptr(), array_layout); | ||||||
|                 std::alloc::dealloc(self.swap_scratch.as_ptr(), self.item_layout); |                 std::alloc::dealloc(self.swap_scratch.as_ptr(), self.item_layout); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @ -350,7 +342,7 @@ mod tests { | |||||||
| 
 | 
 | ||||||
|     // SAFETY: The pointer points to a valid value of type `T` and it is safe to drop this value.
 |     // SAFETY: The pointer points to a valid value of type `T` and it is safe to drop this value.
 | ||||||
|     unsafe fn drop_ptr<T>(x: OwningPtr<'_>) { |     unsafe fn drop_ptr<T>(x: OwningPtr<'_>) { | ||||||
|         x.inner().cast::<T>().as_ptr().drop_in_place() |         x.drop_as::<T>() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// # Safety
 |     /// # Safety
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 James Liu
						James Liu