diff --git a/crates/ptr-union/src/lib.rs b/crates/ptr-union/src/lib.rs index b3f23aa..494bb70 100644 --- a/crates/ptr-union/src/lib.rs +++ b/crates/ptr-union/src/lib.rs @@ -558,9 +558,14 @@ macro_rules! impl_union { { paste::paste! { fn clone(&self) -> Self { - let builder = unsafe { <$Builder<$($A,)*>>::new_unchecked() }; + #[cold] + #[inline(never)] + fn clone_error() -> ! { + panic!("Tried to clone {} in a {}, but the cloned pointer wasn't sufficiently aligned", core::any::type_name::(), stringify!($Union)) + } + None - $(.or_else(|| self.[]().map(|this| builder.$a(this))))* + $(.or_else(|| self.[]().map(|this| Self::[](this).unwrap_or_else(|_| clone_error::<$A>()))))* .unwrap_or_else(|| unsafe { unreachable_unchecked() }) } } diff --git a/crates/ptr-union/tests/clone_unaligned.rs b/crates/ptr-union/tests/clone_unaligned.rs new file mode 100644 index 0000000..aee6667 --- /dev/null +++ b/crates/ptr-union/tests/clone_unaligned.rs @@ -0,0 +1,67 @@ +//! This is a regression test for https://github.com/CAD97/pointer-utils/issues/89 +//! +//! The idea here is to have a Box like pointer which we can control the alignment for to +//! ensure that the tests are stable (doesn't depend on allocator shenanigans). + +use std::{ptr::NonNull, sync::atomic::AtomicUsize}; + +use ptr_union::Union8; + +#[derive(Debug)] +struct MyBox { + ptr: NonNull, +} + +// SAFETY: +// * MyBox doesn't have any shared mutability +// * the address of the returned pointer doesn't depend on the address of MyBox +// * MyBox doesn't implement Deref +unsafe impl erasable::ErasablePtr for MyBox { + fn erase(this: Self) -> erasable::ErasedPtr { + this.ptr.cast() + } + + unsafe fn unerase(this: erasable::ErasedPtr) -> Self { + Self { ptr: this.cast() } + } +} + +static OFFSET: AtomicUsize = AtomicUsize::new(8); + +impl MyBox { + fn new() -> Self { + let offset = OFFSET.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + MyBox { + ptr: NonNull::new(offset as _).unwrap(), + } + } +} + +impl Clone for MyBox { + fn clone(&self) -> Self { + Self::new() + } +} + +type Union = Union8< + MyBox, + NonNull, + NonNull, + NonNull, + NonNull, + NonNull, + NonNull, + NonNull, +>; + +#[test] +#[allow(clippy::redundant_clone)] +#[should_panic = "but the cloned pointer wasn't sufficiently aligned"] +fn test_clone_unaligned() { + let bx = MyBox::new(); + // this can't fail since the first `MyBox` is created at address 8, which is aligned to 8 bytes + let x = Union::new_a(bx).unwrap(); + + // this clone should panic, since the next `MyBox` is created at address 9, which is not aligned to 8 bytes + let _y = x.clone(); +}