From fa18674835148179c4fb9d616aff1394ee7fbfb7 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Thu, 12 Oct 2023 12:09:17 +0200 Subject: [PATCH 1/5] Add from_box_bytes and box_bytes_of with BoxBytes type --- src/allocation.rs | 104 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/src/allocation.rs b/src/allocation.rs index a2633b52..58c3ef01 100644 --- a/src/allocation.rs +++ b/src/allocation.rs @@ -19,6 +19,7 @@ use alloc::{ vec, vec::Vec, }; +use core::ops::{Deref, DerefMut}; /// As [`try_cast_box`](try_cast_box), but unwraps for you. #[inline] @@ -686,4 +687,105 @@ pub trait TransparentWrapperAlloc: } } -impl> TransparentWrapperAlloc for T {} +impl> TransparentWrapperAlloc + for T +{ +} + +/// As `Box<[u8]>`, but remembers the original alignment. +pub struct BoxBytes { + // SAFETY: `ptr` is owned and was allocated with `layout`. + ptr: NonNull, + layout: Layout, +} + +impl Deref for BoxBytes { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + // SAFETY: See type invariant. + unsafe { + core::slice::from_raw_parts(self.ptr.as_ptr(), self.layout.size()) + } + } +} + +impl DerefMut for BoxBytes { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: See type invariant. + unsafe { + core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.layout.size()) + } + } +} + +impl Drop for BoxBytes { + fn drop(&mut self) { + // SAFETY: See type invariant. + unsafe { alloc::alloc::dealloc(self.ptr.as_ptr(), self.layout) }; + } +} + +impl From> for BoxBytes { + fn from(value: Box) -> Self { + let layout = Layout::new::(); + let ptr = Box::into_raw(value) as *mut u8; + // SAFETY: Box::into_raw() returns a non-null pointer. + let ptr = unsafe { NonNull::new_unchecked(ptr) }; + BoxBytes { ptr, layout } + } +} + +/// Re-interprets `Box` as `BoxBytes`. +#[inline] +pub fn box_bytes_of(input: Box) -> BoxBytes { + input.into() +} + +/// Re-interprets `BoxBytes` as `Box`. +/// +/// ## Panics +/// +/// This is [`try_from_box_bytes`] but will panic on error and the input will be +/// dropped. +#[inline] +pub fn from_box_bytes(input: BoxBytes) -> Box { + try_from_box_bytes(input).map_err(|(error, _)| error).unwrap() +} + +/// Re-interprets `BoxBytes` as `Box`. +/// +/// ## Panics +/// +/// * If the input isn't aligned for the new type +/// * If the input's length isn’t exactly the size of the new type +#[inline] +pub fn try_from_box_bytes( + input: BoxBytes, +) -> Result, (PodCastError, BoxBytes)> { + let layout = Layout::new::(); + if input.layout.align() != layout.align() { + return Err((PodCastError::AlignmentMismatch, input)); + } else if input.layout.size() != layout.size() { + return Err((PodCastError::SizeMismatch, input)); + } else { + // SAFETY: See type invariant. + Ok(unsafe { Box::from_raw(input.ptr.as_ptr() as *mut T) }) + } +} + +impl BoxBytes { + /// Constructs a `BoxBytes` from its raw parts. + /// + /// # Safety + /// + /// The pointer is owned and has been allocated with the provided layout. + pub unsafe fn from_parts(ptr: NonNull, layout: Layout) -> Self { + BoxBytes { ptr, layout } + } + + /// Returns the original layout. + pub fn layout(&self) -> Layout { + self.layout + } +} From c5aba9c23c633356957592c1f077208daa5a0783 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Thu, 12 Oct 2023 19:54:10 +0200 Subject: [PATCH 2/5] Apply suggestions from code review Co-authored-by: zachs18 <8355914+zachs18@users.noreply.github.com> --- src/allocation.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/allocation.rs b/src/allocation.rs index 58c3ef01..a62b2126 100644 --- a/src/allocation.rs +++ b/src/allocation.rs @@ -694,7 +694,7 @@ impl> TransparentWrapperAlloc /// As `Box<[u8]>`, but remembers the original alignment. pub struct BoxBytes { - // SAFETY: `ptr` is owned and was allocated with `layout`. + // SAFETY: `ptr` is owned, was allocated with `layout`, and points to `layout.size()` initialized bytes. ptr: NonNull, layout: Layout, } @@ -726,7 +726,7 @@ impl Drop for BoxBytes { } } -impl From> for BoxBytes { +impl From> for BoxBytes { fn from(value: Box) -> Self { let layout = Layout::new::(); let ptr = Box::into_raw(value) as *mut u8; @@ -738,7 +738,7 @@ impl From> for BoxBytes { /// Re-interprets `Box` as `BoxBytes`. #[inline] -pub fn box_bytes_of(input: Box) -> BoxBytes { +pub fn box_bytes_of(input: Box) -> BoxBytes { input.into() } @@ -749,7 +749,7 @@ pub fn box_bytes_of(input: Box) -> BoxBytes { /// This is [`try_from_box_bytes`] but will panic on error and the input will be /// dropped. #[inline] -pub fn from_box_bytes(input: BoxBytes) -> Box { +pub fn from_box_bytes(input: BoxBytes) -> Box { try_from_box_bytes(input).map_err(|(error, _)| error).unwrap() } @@ -760,7 +760,7 @@ pub fn from_box_bytes(input: BoxBytes) -> Box { /// * If the input isn't aligned for the new type /// * If the input's length isn’t exactly the size of the new type #[inline] -pub fn try_from_box_bytes( +pub fn try_from_box_bytes( input: BoxBytes, ) -> Result, (PodCastError, BoxBytes)> { let layout = Layout::new::(); @@ -779,7 +779,7 @@ impl BoxBytes { /// /// # Safety /// - /// The pointer is owned and has been allocated with the provided layout. + /// The pointer is owned, has been allocated with the provided layout, and points to `layout.size()` initialized bytes. pub unsafe fn from_parts(ptr: NonNull, layout: Layout) -> Self { BoxBytes { ptr, layout } } From 456f217ef5057d13f0716b2e18cbfbcb726ebdb9 Mon Sep 17 00:00:00 2001 From: ia0 Date: Thu, 12 Oct 2023 20:06:14 +0200 Subject: [PATCH 3/5] Add into_raw_parts --- src/allocation.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/allocation.rs b/src/allocation.rs index a62b2126..9f87235d 100644 --- a/src/allocation.rs +++ b/src/allocation.rs @@ -694,7 +694,8 @@ impl> TransparentWrapperAlloc /// As `Box<[u8]>`, but remembers the original alignment. pub struct BoxBytes { - // SAFETY: `ptr` is owned, was allocated with `layout`, and points to `layout.size()` initialized bytes. + // SAFETY: `ptr` is owned, was allocated with `layout`, and points to + // `layout.size()` initialized bytes. ptr: NonNull, layout: Layout, } @@ -769,8 +770,9 @@ pub fn try_from_box_bytes( } else if input.layout.size() != layout.size() { return Err((PodCastError::SizeMismatch, input)); } else { + let (ptr, _) = input.into_raw_parts(); // SAFETY: See type invariant. - Ok(unsafe { Box::from_raw(input.ptr.as_ptr() as *mut T) }) + Ok(unsafe { Box::from_raw(ptr.as_ptr() as *mut T) }) } } @@ -779,11 +781,21 @@ impl BoxBytes { /// /// # Safety /// - /// The pointer is owned, has been allocated with the provided layout, and points to `layout.size()` initialized bytes. + /// The pointer is owned, has been allocated with the provided layout, and + /// points to `layout.size()` initialized bytes. pub unsafe fn from_parts(ptr: NonNull, layout: Layout) -> Self { BoxBytes { ptr, layout } } + /// Deconstructs a `BoxBytes` into its raw parts. + /// + /// The pointer is owned, has been allocated with the provided layout, and + /// points to `layout.size()` initialized bytes. + pub fn into_raw_parts(self) -> (NonNull, Layout) { + let me = ManuallyDrop::new(self); + (me.ptr, me.layout) + } + /// Returns the original layout. pub fn layout(&self) -> Layout { self.layout From ba621722f35e752e66c91dc2b1ba367d60b0f93b Mon Sep 17 00:00:00 2001 From: ia0 Date: Thu, 12 Oct 2023 20:38:55 +0200 Subject: [PATCH 4/5] Add tests --- tests/std_tests.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tests/std_tests.rs b/tests/std_tests.rs index e2390588..84fef6bb 100644 --- a/tests/std_tests.rs +++ b/tests/std_tests.rs @@ -3,6 +3,7 @@ //! depend on that can go here. use bytemuck::*; +use core::num::NonZeroU8; #[test] fn test_transparent_vtabled() { @@ -44,3 +45,62 @@ fn test_zero_sized_box_alloc() { unsafe impl Zeroable for Empty {} let _: Box = try_zeroed_box().unwrap(); } + +#[test] +#[cfg(feature = "extern_crate_alloc")] +fn test_try_from_box_bytes() { + // Different layout: target alignment is greater than source alignment. + assert_eq!( + try_from_box_bytes::(Box::new([0u8; 4]).into()).map_err(|(x, _)| x), + Err(PodCastError::AlignmentMismatch) + ); + + // Different layout: target alignment is less than source alignment. + assert_eq!( + try_from_box_bytes::(Box::new(0u64).into()).map_err(|(x, _)| x), + Err(PodCastError::AlignmentMismatch) + ); + + // Different layout: target size is greater than source size. + assert_eq!( + try_from_box_bytes::<[u32; 2]>(Box::new(0u32).into()).map_err(|(x, _)| x), + Err(PodCastError::SizeMismatch) + ); + + // Different layout: target size is less than source size. + assert_eq!( + try_from_box_bytes::(Box::new([0u32; 2]).into()).map_err(|(x, _)| x), + Err(PodCastError::SizeMismatch) + ); + + // Round trip: alignment is equal to size. + assert_eq!(*from_box_bytes::(Box::new(1000u32).into()), 1000u32); + + // Round trip: alignment is divider of size. + assert_eq!(&*from_box_bytes::<[u8; 5]>(Box::new(*b"hello").into()), b"hello"); + + // It's ok for T to have uninitialized bytes. + #[cfg(feature = "derive")] + { + #[derive(Debug, Copy, Clone, PartialEq, Eq, AnyBitPattern)] + struct Foo(u8, u16); + assert_eq!( + *from_box_bytes::(Box::new([0xc5c5u16; 2]).into()), + Foo(0xc5u8, 0xc5c5u16) + ); + } +} + +#[test] +#[cfg(feature = "extern_crate_alloc")] +fn test_box_bytes_of() { + assert_eq!(&*box_bytes_of(Box::new(*b"hello")), b"hello"); + + #[cfg(target_endian = "big")] + assert_eq!(&*box_bytes_of(Box::new(0x12345678)), b"\x12\x34\x56\x78"); + #[cfg(target_endian = "little")] + assert_eq!(&*box_bytes_of(Box::new(0x12345678)), b"\x78\x56\x34\x12"); + + // It's ok for T to have invalid bit patterns. + assert_eq!(&*box_bytes_of(Box::new(NonZeroU8::new(0xc5))), b"\xc5"); +} From 22cb7ddaca535f234a4d3276f45e669c6d6da4e2 Mon Sep 17 00:00:00 2001 From: Julien Cretin Date: Fri, 13 Oct 2023 10:07:50 +0200 Subject: [PATCH 5/5] Rename from_parts to from_raw_parts --- src/allocation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/allocation.rs b/src/allocation.rs index 9f87235d..20006749 100644 --- a/src/allocation.rs +++ b/src/allocation.rs @@ -783,7 +783,7 @@ impl BoxBytes { /// /// The pointer is owned, has been allocated with the provided layout, and /// points to `layout.size()` initialized bytes. - pub unsafe fn from_parts(ptr: NonNull, layout: Layout) -> Self { + pub unsafe fn from_raw_parts(ptr: NonNull, layout: Layout) -> Self { BoxBytes { ptr, layout } }