diff --git a/src/macros.rs b/src/macros.rs index d1d32c81b4..16ae5689d7 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -182,22 +182,23 @@ macro_rules! transmute_ref { // this macro expression is `&U` where `U: 'u + Sized + FromBytes + // Immutable`, and that `'t` outlives `'u`. - struct AssertSrcIsSized<'a, T: ::core::marker::Sized>(&'a T); + // struct AssertSrcIsSized<'a, T: ::core::marker::Sized>(&'a T); struct AssertSrcIsIntoBytes<'a, T: ?::core::marker::Sized + $crate::IntoBytes>(&'a T); struct AssertSrcIsImmutable<'a, T: ?::core::marker::Sized + $crate::Immutable>(&'a T); - struct AssertDstIsSized<'a, T: ::core::marker::Sized>(&'a T); + // struct AssertDstIsSized<'a, T: ::core::marker::Sized>(&'a T); struct AssertDstIsFromBytes<'a, U: ?::core::marker::Sized + $crate::FromBytes>(&'a U); struct AssertDstIsImmutable<'a, T: ?::core::marker::Sized + $crate::Immutable>(&'a T); - let _ = AssertSrcIsSized(e); + // let _ = AssertSrcIsSized(e); let _ = AssertSrcIsIntoBytes(e); let _ = AssertSrcIsImmutable(e); + // if true { + // #[allow(unused, unreachable_code)] + // let u = AssertDstIsSized(loop {}); + // u.0 + // } else if true { - #[allow(unused, unreachable_code)] - let u = AssertDstIsSized(loop {}); - u.0 - } else if true { #[allow(unused, unreachable_code)] let u = AssertDstIsFromBytes(loop {}); u.0 @@ -206,36 +207,20 @@ macro_rules! transmute_ref { let u = AssertDstIsImmutable(loop {}); u.0 } - } else if false { - // This branch, though never taken, ensures that `size_of::() == - // size_of::()` and that that `align_of::() >= - // align_of::()`. - - // `t` is inferred to have type `T` because it's assigned to `e` (of - // type `&T`) as `&t`. - let mut t = loop {}; - e = &t; - - // `u` is inferred to have type `U` because it's used as `&u` as the - // value returned from this branch. - let u; - - $crate::assert_size_eq!(t, u); - $crate::assert_align_gt_eq!(t, u); - - &u } else { - // SAFETY: For source type `Src` and destination type `Dst`: - // - We know that `Src: IntoBytes + Immutable` and `Dst: FromBytes + - // Immutable` thanks to the uses of `AssertSrcIsIntoBytes`, - // `AssertSrcIsImmutable`, `AssertDstIsFromBytes`, and - // `AssertDstIsImmutable` above. - // - We know that `size_of::() == size_of::()` thanks to - // the use of `assert_size_eq!` above. - // - We know that `align_of::() >= align_of::()` thanks to - // the use of `assert_align_gt_eq!` above. - let u = unsafe { $crate::util::macro_util::transmute_ref(e) }; - $crate::util::macro_util::must_use(u) + let opt = $crate::util::macro_util::static_assert_transmute_ref(&*e); + if let Some(u) = opt { + loop {} + &*u + } else { + let e: *const _ = e; + #[allow(clippy::as_conversions)] + let u = e as *const _; + // SAFETY: TODO (needs to be valid for both older and newer toolchains). + #[allow(clippy::as_conversions)] + let u = unsafe { &*u }; + $crate::util::macro_util::must_use(u) + } } }} } @@ -334,26 +319,27 @@ macro_rules! transmute_mut { // writing, mutable references are banned), the error message // appears to originate in the user's code rather than in the // internals of this macro. - struct AssertSrcIsSized<'a, T: ::core::marker::Sized>(&'a T); + // struct AssertSrcIsSized<'a, T: ::core::marker::Sized>(&'a T); struct AssertSrcIsFromBytes<'a, T: ?::core::marker::Sized + $crate::FromBytes>(&'a T); struct AssertSrcIsIntoBytes<'a, T: ?::core::marker::Sized + $crate::IntoBytes>(&'a T); - struct AssertDstIsSized<'a, T: ::core::marker::Sized>(&'a T); + // struct AssertDstIsSized<'a, T: ::core::marker::Sized>(&'a T); struct AssertDstIsFromBytes<'a, T: ?::core::marker::Sized + $crate::FromBytes>(&'a T); struct AssertDstIsIntoBytes<'a, T: ?::core::marker::Sized + $crate::IntoBytes>(&'a T); if true { - let _ = AssertSrcIsSized(&*e); + // let _ = AssertSrcIsSized(&*e); } else if true { let _ = AssertSrcIsFromBytes(&*e); } else { let _ = AssertSrcIsIntoBytes(&*e); } + // if true { + // #[allow(unused, unreachable_code)] + // let u = AssertDstIsSized(loop {}); + // &mut *u.0 + // } else if true { - #[allow(unused, unreachable_code)] - let u = AssertDstIsSized(loop {}); - &mut *u.0 - } else if true { #[allow(unused, unreachable_code)] let u = AssertDstIsFromBytes(loop {}); &mut *u.0 @@ -362,32 +348,20 @@ macro_rules! transmute_mut { let u = AssertDstIsIntoBytes(loop {}); &mut *u.0 } - } else if false { - // This branch, though never taken, ensures that `size_of::() == - // size_of::()` and that that `align_of::() >= - // align_of::()`. - - // `t` is inferred to have type `T` because it's assigned to `e` (of - // type `&mut T`) as `&mut t`. - let mut t = loop {}; - e = &mut t; - - // `u` is inferred to have type `U` because it's used as `&mut u` as - // the value returned from this branch. - let u; - - $crate::assert_size_eq!(t, u); - $crate::assert_align_gt_eq!(t, u); - - &mut u } else { - // SAFETY: For source type `Src` and destination type `Dst`: - // - We know that `size_of::() == size_of::()` thanks to - // the use of `assert_size_eq!` above. - // - We know that `align_of::() >= align_of::()` thanks to - // the use of `assert_align_gt_eq!` above. - let u = unsafe { $crate::util::macro_util::transmute_mut(e) }; - $crate::util::macro_util::must_use(u) + let opt = $crate::util::macro_util::static_assert_transmute_ref(&*e); + if let Some(u) = opt { + loop {} + &mut *u + } else { + let e: *mut _ = e; + #[allow(clippy::as_conversions)] + let u = e as *mut _; + // SAFETY: TODO (needs to be valid for both older and newer toolchains). + #[allow(clippy::as_conversions)] + let u = unsafe { &mut *u }; + $crate::util::macro_util::must_use(u) + } } }} } @@ -793,6 +767,37 @@ mod tests { const X: &'static [[u8; 2]; 4] = transmute_ref!(&ARRAY_OF_U8S); assert_eq!(*X, ARRAY_OF_ARRAYS); + // Test that `transmute_ref!` works on slice DSTs in and that memory is + // transmuted as expected. + #[derive(KnownLayout, Immutable, FromBytes, IntoBytes, PartialEq, Debug)] + #[repr(C)] + struct SliceDst { + a: u8, + b: [T], + } + + use crate::byteorder::native_endian::U16; + let slice_dst_of_u8s = + SliceDst::<[u8; 2]>::ref_from_bytes(&[0, 1, 2, 3, 4, 5, 6][..]).unwrap(); + let slice_dst_of_u16s = + SliceDst::::ref_from_bytes(&[0, 1, 2, 3, 4, 5, 6][..]).unwrap(); + let x: &SliceDst = transmute_ref!(slice_dst_of_u8s); + assert_eq!(x, slice_dst_of_u16s); + + // Test that `transmute_ref!` works on slices in a const context and + // that memory is transmuted as expected. + const ARRAY_OF_U16S: &[u16] = &[0u16, 1, 2]; + const ARRAY_OF_I16S: &[i16] = &[0i16, 1, 2]; + const Y: &[i16] = transmute_ref!(ARRAY_OF_U16S); + assert_eq!(Y, ARRAY_OF_I16S); + + // Test that `transmute_ref!` works on slice DSTs in a const context. We + // have no way of synthesizing slice DST values in a const context, so + // this just tests that it compiles. + const fn _transmute_slice_dst_ref(from: &SliceDst<[u8; 2]>) -> &SliceDst { + transmute_ref!(from) + } + // Test that it's legal to transmute a reference while shrinking the // lifetime (note that `X` has the lifetime `'static`). let x: &[u8; 8] = transmute_ref!(X); @@ -947,6 +952,30 @@ mod tests { #[allow(clippy::useless_transmute)] let y: &u8 = transmute_mut!(&mut x); assert_eq!(*y, 0); + + // Test that `transmute_mut!` works on slice DSTs in and that memory is + // transmuted as expected. + #[derive(KnownLayout, Immutable, FromBytes, IntoBytes, PartialEq, Debug)] + #[repr(C)] + struct SliceDst { + a: u8, + b: [T], + } + + use crate::byteorder::native_endian::U16; + let mut bytes = [0, 1, 2, 3, 4, 5, 6]; + let slice_dst_of_u8s = SliceDst::<[u8; 2]>::mut_from_bytes(&mut bytes[..]).unwrap(); + let mut bytes = [0, 1, 2, 3, 4, 5, 6]; + let slice_dst_of_u16s = SliceDst::::mut_from_bytes(&mut bytes[..]).unwrap(); + let x: &mut SliceDst = transmute_mut!(slice_dst_of_u8s); + assert_eq!(x, slice_dst_of_u16s); + + // Test that `transmute_mut!` works on slices that memory is transmuted + // as expected. + let array_of_u16s: &mut [u16] = &mut [0u16, 1, 2]; + let array_of_i16s: &mut [i16] = &mut [0i16, 1, 2]; + let x: &mut [i16] = transmute_mut!(array_of_u16s); + assert_eq!(x, array_of_i16s); } #[test] diff --git a/src/util/macro_util.rs b/src/util/macro_util.rs index f7e66056d2..1d0ec8eb0d 100644 --- a/src/util/macro_util.rs +++ b/src/util/macro_util.rs @@ -443,6 +443,38 @@ macro_rules! assert_size_eq { }}; } +// TODO: Document that this MUST be called from a live codepath in order for its +// guarantees to hold. +#[doc(hidden)] +#[inline(always)] +#[cfg(zerocopy_generic_bounds_in_const_fn)] +pub const fn static_assert_transmute_ref(_t: &T) -> Option<&U> +where + T: ?Sized + crate::KnownLayout, + U: ?Sized + crate::KnownLayout, +{ + use crate::{layout::*, KnownLayout}; + static_assert!(T: ?Sized + KnownLayout, U: ?Sized + KnownLayout => { + let t = T::LAYOUT; + let u = U::LAYOUT; + t.align.get() >= u.align.get() && match (t.size_info, u.size_info) { + (SizeInfo::Sized { size: t }, SizeInfo::Sized { size: u }) => t == u, + ( + SizeInfo::SliceDst(TrailingSliceLayout { offset: t_offset, elem_size: t_elem_size }), + SizeInfo::SliceDst(TrailingSliceLayout { offset: u_offset, elem_size: u_elem_size }) + ) => t_offset == u_offset && t_elem_size == u_elem_size, + _ => false, + } + }); + + None +} + +// #[cfg(not(zerocopy_generic_bounds_in_const_fn))] +// macro_rules! assert_transmute_ref_layouts { +// ($e:ident) => {}; +// } + /// Transmutes a reference of one type to a reference of another type. /// /// # Safety @@ -487,7 +519,6 @@ pub const unsafe fn transmute_ref<'dst, 'src: 'dst, Src: 'src, Dst: 'dst>( /// - `Dst: FromBytes + IntoBytes` /// - `size_of::() == size_of::()` /// - `align_of::() >= align_of::()` -// TODO(#686): Consider removing the `Immutable` requirement. #[inline(always)] pub unsafe fn transmute_mut<'dst, 'src: 'dst, Src: 'src, Dst: 'dst>( src: &'src mut Src, diff --git a/src/util/macros.rs b/src/util/macros.rs index af751e7523..b4b71f6945 100644 --- a/src/util/macros.rs +++ b/src/util/macros.rs @@ -739,14 +739,15 @@ macro_rules! static_assert { const ASSERT: bool; } - impl<$($tyvar $(: $(? $optbound +)* $($bound +)*)?,)*> StaticAssert for ($($tyvar,)*) { + // NOTE: We use `PhantomData` so we can support unsized types. + impl<$($tyvar $(: $(? $optbound +)* $($bound +)*)?,)*> StaticAssert for ($(core::marker::PhantomData<$tyvar>,)*) { const ASSERT: bool = { const_assert!($condition $(, $args)*); $condition }; } - const_assert!(<($($tyvar,)*) as StaticAssert>::ASSERT); + const_assert!(<($(core::marker::PhantomData<$tyvar>,)*) as StaticAssert>::ASSERT); }}; }