diff --git a/src/lib.rs b/src/lib.rs index b76f10e1e1..fed1c481f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1674,6 +1674,140 @@ macro_rules! transmute { }} } +/// Safely transmutes a mutable or immutable reference of one type to an +/// immutable reference of another type of the same size. +/// +/// The expression `$e` must have a concrete type, `&T` or `&mut T`, where `T: +/// Sized + AsBytes`. The `transmute_ref!` expression must also have a concrete +/// type, `&U` (`U` is inferred from the calling context), where `U: Sized + +/// FromBytes`. It must be the case that `align_of::() >= align_of::()`. +/// +/// The lifetime of the input type, `&T` or `&mut T`, must be the same as or +/// outlive the lifetime of the output type, `&U`. +/// +/// # Alignment increase error message +/// +/// Because of limitations on macros, the error message generated when +/// `transmute_ref!` is used to transmute from a type of lower alignment to a +/// type of higher alignment is somewhat confusing. For example, the following +/// code: +/// +/// ```rust,compile_fail +/// const INCREASE_ALIGNMENT: &u16 = zerocopy::transmute_ref!(&[0u8; 2]); +/// ``` +/// +/// ...generates the following error: +/// +/// ```text +/// error[E0512]: cannot transmute between types of different sizes, or dependently-sized types +/// --> src/lib.rs:1524:34 +/// | +/// 5 | const INCREASE_ALIGNMENT: &u16 = zerocopy::transmute_ref!(&[0u8; 2]); +/// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +/// | +/// = note: source type: `MaxAlignsOf<[u8; 2], u16>` (16 bits) +/// = note: target type: `AlignOf<[u8; 2]>` (8 bits) +/// = note: this error originates in the macro `zerocopy::transmute_ref` (in Nightly builds, run with -Z macro-backtrace for more info) +/// ``` +/// +/// This is saying that `max(align_of::(), align_of::()) != +/// align_of::()`, which is equivalent to `align_of::() < +/// align_of::()`. +#[macro_export] +macro_rules! transmute_ref { + ($e:expr) => {{ + // NOTE: This must be a macro (rather than a function with trait bounds) + // because there's no way, in a generic context, to enforce that two + // types have the same size or alignment. + + // Reborrow so that mutable references are supported too. + // + // In the rest of the comments, we refer only to `&T` since this + // reborrow ensures that `e` is an immutable reference. + let e = &*$e; + + #[allow(unused, clippy::diverging_sub_expression)] + if false { + // This branch, though never taken, ensures that the type of `e` is + // `&T` where `T: 't + Sized + AsBytes`, that the type of this macro + // expression is `&U` where `U: 'u + Sized + FromBytes`, and that + // `'t` outlives `'u`. + const fn transmute<'u, 't: 'u, T: 't + Sized + $crate::AsBytes, U: 'u + Sized + $crate::FromBytes>(_t: &'t T) -> &'u U { + unreachable!() + } + transmute(e) + } else if false { + // This branch, though never taken, ensures that `size_of::() == + // size_of::()`. + + // `t` is inferred to have type `T` because it's assigned to `e` (of + // type `&T`) as `&t`. + let mut t = unreachable!(); + e = &t; + + // `u` is inferred to have type `U` because it's used as `&u` as the + // value returned from this branch. + // + // SAFETY: This code is never run. + let u = unsafe { $crate::macro_util::core_reexport::mem::transmute(t) }; + &u + } else if false { + // This branch, though never taken, ensures that the alignment of + // `T` is greater than or equal to to the alignment of `U`. + + // `t` is inferred to have type `T` because it's assigned to `e` (of + // type `&T`) as `&t`. + let mut t = unreachable!(); + e = &t; + + // `u` is inferred to have type `U` because it's used as `&u` as the + // value returned from this branch. + let mut u = unreachable!(); + + // `max_aligns` is inferred to have type `MaxAlignsOf` because + // of the inferred types of `t` and `u`. + let max_aligns = $crate::macro_util::MaxAlignsOf::new(t, u); + // The type wildcard in this bound is inferred to be `T` because + // `align_of.into_t()` is assigned to `t` (which has type `T`). + // + // This transmute will only compile successfully if + // `max(align_of::(), align_of::()) == align_of::()` - in + // other words, if `align_of::() >= align_of::()`. + // + // SAFETY: This code is never run. + let align_of: $crate::macro_util::AlignOf<_> = unsafe { $crate::macro_util::core_reexport::mem::transmute(max_aligns) }; + t = align_of.into_t(); + + &u + } else { + // SAFETY: + // - We know that the input and output types are both `Sized` (ie, + // thin) references thanks to the trait bounds on `transmute` + // above, and thanks to the fact that transmute takes and returns + // references. + // - We know that it is sound to view the target type of the input + // reference (`T`) as the target type of the output reference + // (`U`) because `T: AsBytes` and `U: FromBytes` (guaranteed by + // trait bounds on `transmute`) and because `size_of::() == + // size_of::()` (guaranteed by the first `core::mem::transmute` + // above). + // - We know that alignment is not increased thanks to the second + // `core::mem::transmute` above (the one which transmutes + // `MaxAlignsOf` into `AlignOf`). + // + // We use this reexport of `core::mem::transmute` because we know it + // will always be available for crates which are using the 2015 + // edition of Rust. By contrast, if we were to use + // `std::mem::transmute`, this macro would not work for such crates + // in `no_std` contexts, and if we were to use + // `core::mem::transmute`, this macro would not work in `std` + // contexts in which `core` was not manually imported. This is not a + // problem for 2018 edition crates. + unsafe { $crate::macro_util::core_reexport::mem::transmute(e) } + } + }} +} + /// A typed reference derived from a byte slice. /// /// A `Ref` is a reference to a `T` which is stored in a byte slice, `B`. @@ -3810,6 +3944,92 @@ mod tests { assert_eq!(X, ARRAY_OF_ARRAYS); } + #[test] + fn test_align_of() { + macro_rules! test { + ($ty:ty) => { + assert_eq!(mem::size_of::>(), mem::align_of::<$ty>()); + }; + } + + test!(()); + test!(u8); + test!(AU64); + test!([AU64; 2]); + } + + #[test] + fn test_max_aligns_of() { + macro_rules! test { + ($t:ty, $u:ty) => { + assert_eq!( + mem::size_of::>(), + core::cmp::max(mem::align_of::<$t>(), mem::align_of::<$u>()) + ); + }; + } + + test!(u8, u8); + test!(u8, AU64); + test!(AU64, u8); + } + + #[test] + fn test_typed_align_check() { + // Test that the type-based alignment check used in `transmute_ref!` + // behaves as expected. + + macro_rules! assert_t_align_gteq_u_align { + ($t:ty, $u:ty, $gteq:expr) => { + assert_eq!( + mem::size_of::>() + == mem::size_of::>(), + $gteq + ); + }; + } + + assert_t_align_gteq_u_align!(u8, u8, true); + assert_t_align_gteq_u_align!(AU64, AU64, true); + assert_t_align_gteq_u_align!(AU64, u8, true); + assert_t_align_gteq_u_align!(u8, AU64, false); + } + + #[test] + fn test_transmute_ref() { + // Test that memory is transmuted as expected. + let array_of_u8s = [0u8, 1, 2, 3, 4, 5, 6, 7]; + let array_of_arrays = [[0, 1], [2, 3], [4, 5], [6, 7]]; + let x: &[[u8; 2]; 4] = transmute_ref!(&array_of_u8s); + assert_eq!(*x, array_of_arrays); + let x: &[u8; 8] = transmute_ref!(&array_of_arrays); + assert_eq!(*x, array_of_u8s); + + // Test that `transmute_ref!` is legal in a const context. + const ARRAY_OF_U8S: [u8; 8] = [0u8, 1, 2, 3, 4, 5, 6, 7]; + const ARRAY_OF_ARRAYS: [[u8; 2]; 4] = [[0, 1], [2, 3], [4, 5], [6, 7]]; + #[allow(clippy::redundant_static_lifetimes)] + const X: &'static [[u8; 2]; 4] = transmute_ref!(&ARRAY_OF_U8S); + assert_eq!(*X, ARRAY_OF_ARRAYS); + + // 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); + assert_eq!(*x, ARRAY_OF_U8S); + + // Test that `transmute_ref!` supports decreasing alignment. + let u = AU64(0); + let array = [0, 0, 0, 0, 0, 0, 0, 0]; + let x: &[u8; 8] = transmute_ref!(&u); + assert_eq!(*x, array); + + // Test that a mutable reference can be turned into an immutable one. + let mut x = 0u8; + #[allow(clippy::useless_transmute)] + let y: &u8 = transmute_ref!(&mut x); + assert_eq!(*y, 0); + } + #[test] fn test_address() { // Test that the `Deref` and `DerefMut` implementations return a diff --git a/src/macro_util.rs b/src/macro_util.rs index 9e5c39a650..85718aec6d 100644 --- a/src/macro_util.rs +++ b/src/macro_util.rs @@ -13,7 +13,7 @@ #![allow(missing_debug_implementations)] -use core::marker::PhantomData; +use core::{marker::PhantomData, mem::ManuallyDrop}; /// A compile-time check that should be one particular value. pub trait ShouldBe {} @@ -23,6 +23,40 @@ pub struct HasPadding(PhantomData); impl ShouldBe for HasPadding {} +/// A type whose size is equal to `align_of::()`. +#[repr(C)] +pub struct AlignOf { + // This field ensures that: + // - The size is always at least 1 (the minimum possible alignment). + // - If the alignment is greater than 1, Rust has to round up to the next + // multiple of it in order to make sure that `Align`'s size is a multiple + // of that alignment. Without this field, its size could be 0, which is a + // valid multiple of any alignment. + _u: u8, + _a: [T; 0], +} + +impl AlignOf { + #[inline(never)] // Make `missing_inline_in_public_items` happy. + pub fn into_t(self) -> T { + unreachable!() + } +} + +/// A type whose size is equal to `max(align_of::(), align_of::())`. +#[repr(C)] +pub union MaxAlignsOf { + _t: ManuallyDrop>, + _u: ManuallyDrop>, +} + +impl MaxAlignsOf { + #[inline(never)] // Make `missing_inline_in_public_items` happy. + pub fn new(_t: T, _u: U) -> MaxAlignsOf { + unreachable!() + } +} + /// Does the struct type `$t` have padding? /// /// `$ts` is the list of the type of every field in `$t`. `$t` must be a