Skip to content

Commit 0018bee

Browse files
james7132ItsDoot
authored andcommitted
Ensure Ptr/PtrMut/OwningPtr are aligned when casting in debug builds (bevyengine#7117)
# Objective Improve safety testing when using `bevy_ptr` types. This is a follow-up to bevyengine#7113. ## Solution Add a debug-only assertion that pointers are aligned when casting to a concrete type. This should very quickly catch any unsoundness from unaligned pointers, even without miri. However, this can have a large negative perf impact on debug builds. --- ## Changelog Added: `Ptr::deref` will now panic in debug builds if the pointer is not aligned. Added: `PtrMut::deref_mut` will now panic in debug builds if the pointer is not aligned. Added: `OwningPtr::read` will now panic in debug builds if the pointer is not aligned. Added: `OwningPtr::drop_as` will now panic in debug builds if the pointer is not aligned.
1 parent 2c846ac commit 0018bee

File tree

2 files changed

+48
-8
lines changed

2 files changed

+48
-8
lines changed

crates/bevy_ecs/src/storage/blob_vec.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,19 @@ impl BlobVec {
4646
drop: Option<unsafe fn(OwningPtr<'_>)>,
4747
capacity: usize,
4848
) -> BlobVec {
49+
let align = NonZeroUsize::new(item_layout.align()).expect("alignment must be > 0");
50+
let data = bevy_ptr::dangling_with_align(align);
4951
if item_layout.size() == 0 {
50-
let align = NonZeroUsize::new(item_layout.align()).expect("alignment must be > 0");
5152
BlobVec {
52-
data: bevy_ptr::dangling_with_align(align),
53+
data,
5354
capacity: usize::MAX,
5455
len: 0,
5556
item_layout,
5657
drop,
5758
}
5859
} else {
5960
let mut blob_vec = BlobVec {
60-
data: NonNull::dangling(),
61+
data,
6162
capacity: 0,
6263
len: 0,
6364
item_layout,

crates/bevy_ptr/src/lib.rs

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ impl<'a, A: IsAligned> Ptr<'a, A> {
164164
/// for the pointee type `T`.
165165
#[inline]
166166
pub unsafe fn deref<T>(self) -> &'a T {
167-
&*self.as_ptr().cast()
167+
&*self.as_ptr().cast::<T>().debug_ensure_aligned()
168168
}
169169

170170
/// Gets the underlying pointer, erasing the associated lifetime.
@@ -218,7 +218,7 @@ impl<'a, A: IsAligned> PtrMut<'a, A> {
218218
/// for the pointee type `T`.
219219
#[inline]
220220
pub unsafe fn deref_mut<T>(self) -> &'a mut T {
221-
&mut *self.as_ptr().cast()
221+
&mut *self.as_ptr().cast::<T>().debug_ensure_aligned()
222222
}
223223

224224
/// Gets the underlying pointer, erasing the associated lifetime.
@@ -287,7 +287,7 @@ impl<'a, A: IsAligned> OwningPtr<'a, A> {
287287
/// for the pointee type `T`.
288288
#[inline]
289289
pub unsafe fn read<T>(self) -> T {
290-
self.as_ptr().cast::<T>().read()
290+
self.as_ptr().cast::<T>().debug_ensure_aligned().read()
291291
}
292292

293293
/// Consumes the [`OwningPtr`] to drop the underlying data of type `T`.
@@ -298,7 +298,10 @@ impl<'a, A: IsAligned> OwningPtr<'a, A> {
298298
/// for the pointee type `T`.
299299
#[inline]
300300
pub unsafe fn drop_as<T>(self) {
301-
self.as_ptr().cast::<T>().drop_in_place();
301+
self.as_ptr()
302+
.cast::<T>()
303+
.debug_ensure_aligned()
304+
.drop_in_place();
302305
}
303306

304307
/// Gets the underlying pointer, erasing the associated lifetime.
@@ -364,9 +367,10 @@ impl<'a, T> Copy for ThinSlicePtr<'a, T> {}
364367
impl<'a, T> From<&'a [T]> for ThinSlicePtr<'a, T> {
365368
#[inline]
366369
fn from(slice: &'a [T]) -> Self {
370+
let ptr = slice.as_ptr() as *mut T;
367371
Self {
368372
// SAFETY: a reference can never be null
369-
ptr: unsafe { NonNull::new_unchecked(slice.as_ptr() as *mut T) },
373+
ptr: unsafe { NonNull::new_unchecked(ptr.debug_ensure_aligned()) },
370374
#[cfg(debug_assertions)]
371375
len: slice.len(),
372376
_marker: PhantomData,
@@ -377,6 +381,7 @@ impl<'a, T> From<&'a [T]> for ThinSlicePtr<'a, T> {
377381
/// Creates a dangling pointer with specified alignment.
378382
/// See [`NonNull::dangling`].
379383
pub fn dangling_with_align(align: NonZeroUsize) -> NonNull<u8> {
384+
debug_assert!(align.is_power_of_two(), "Alignment must be power of two.");
380385
// SAFETY: The pointer will not be null, since it was created
381386
// from the address of a `NonZeroUsize`.
382387
unsafe { NonNull::new_unchecked(align.get() as *mut u8) }
@@ -429,3 +434,37 @@ impl<'a, T> UnsafeCellDeref<'a, T> for &'a UnsafeCell<T> {
429434
self.get().read()
430435
}
431436
}
437+
438+
trait DebugEnsureAligned {
439+
fn debug_ensure_aligned(self) -> Self;
440+
}
441+
442+
// Disable this for miri runs as it already checks if pointer to reference
443+
// casts are properly aligned.
444+
#[cfg(all(debug_assertions, not(miri)))]
445+
impl<T: Sized> DebugEnsureAligned for *mut T {
446+
#[track_caller]
447+
fn debug_ensure_aligned(self) -> Self {
448+
let align = core::mem::align_of::<T>();
449+
// Implemenation shamelessly borrowed from the currently unstable
450+
// ptr.is_aligned_to.
451+
//
452+
// Replace once https://github.com/rust-lang/rust/issues/96284 is stable.
453+
assert!(
454+
self as usize & (align - 1) == 0,
455+
"pointer is not aligned. Address {:p} does not have alignemnt {} for type {}",
456+
self,
457+
align,
458+
core::any::type_name::<T>(),
459+
);
460+
self
461+
}
462+
}
463+
464+
#[cfg(any(not(debug_assertions), miri))]
465+
impl<T: Sized> DebugEnsureAligned for *mut T {
466+
#[inline(always)]
467+
fn debug_ensure_aligned(self) -> Self {
468+
self
469+
}
470+
}

0 commit comments

Comments
 (0)