Skip to content

Commit

Permalink
Add transmute_ref! macro
Browse files Browse the repository at this point in the history
This macro is like the existing `transmute!`, but it transmutes
immutable references rather than values.

Issue #159
  • Loading branch information
joshlf committed May 26, 2023
1 parent 9cf8087 commit 2eac1be
Show file tree
Hide file tree
Showing 14 changed files with 1,678 additions and 37 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,5 @@ optional = true
rand = "0.6"
rustversion = "1.0"
static_assertions = "1.1"
# Required for "and $N others" normalization
trybuild = ">=1.0.70"
# Version >=1.0.70 is required for "and $N others" normalization.
trybuild = { version = ">=1.0.70", features = ["diff"] }
166 changes: 159 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1216,7 +1216,7 @@ impl<T> Unalign<T> {
/// Attempts to return a reference to the wrapped `T`, failing if `self` is
/// not properly aligned.
///
/// If `self` does not satisfy `mem::align_of::<T>()`, then it is unsound to
/// If `self` does not satisfy `align_of::<T>()`, then it is unsound to
/// return a reference to the wrapped `T`, and `try_deref` returns `None`.
///
/// If `T: Unaligned`, then `Unalign<T>` implements [`Deref`], and callers
Expand All @@ -1234,7 +1234,7 @@ impl<T> Unalign<T> {
/// Attempts to return a mutable reference to the wrapped `T`, failing if
/// `self` is not properly aligned.
///
/// If `self` does not satisfy `mem::align_of::<T>()`, then it is unsound to
/// If `self` does not satisfy `align_of::<T>()`, then it is unsound to
/// return a reference to the wrapped `T`, and `try_deref_mut` returns
/// `None`.
///
Expand All @@ -1257,7 +1257,7 @@ impl<T> Unalign<T> {
///
/// # Safety
///
/// If `self` does not satisfy `mem::align_of::<T>()`, then
/// If `self` does not satisfy `align_of::<T>()`, then
/// `self.deref_unchecked()` may cause undefined behavior.
pub const unsafe fn deref_unchecked(&self) -> &T {
// SAFETY: `self.get_ptr()` returns a raw pointer to a valid `T` at the
Expand All @@ -1276,7 +1276,7 @@ impl<T> Unalign<T> {
///
/// # Safety
///
/// If `self` does not satisfy `mem::align_of::<T>()`, then
/// If `self` does not satisfy `align_of::<T>()`, then
/// `self.deref_mut_unchecked()` may cause undefined behavior.
pub unsafe fn deref_mut_unchecked(&mut self) -> &mut T {
// SAFETY: `self.get_mut_ptr()` returns a raw pointer to a valid `T` at
Expand Down Expand Up @@ -1463,6 +1463,115 @@ macro_rules! transmute {
}}
}

/// A type whose size is equal to `align_of::<T>()`.
#[doc(hidden)]
#[allow(missing_debug_implementations)]
#[repr(C)]
pub struct AlignOf<T> {
// 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<T> AlignOf<T> {
#[doc(hidden)]
pub fn into_t(self) -> T {
unreachable!()
}
}

/// A type whose size is equal to `max(align_of::<T>(), align_of::<U>())`.
#[doc(hidden)]
#[allow(missing_debug_implementations)]
#[repr(C)]
pub union MaxAlignsOf<T, U> {
_t: ManuallyDrop<AlignOf<T>>,
_u: ManuallyDrop<AlignOf<U>>,
}

impl<T, U> MaxAlignsOf<T, U> {
#[doc(hidden)]
pub fn new(_t: T, _u: U) -> MaxAlignsOf<T, U> {
unreachable!()
}
}

/// Safely transmutes an 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`, 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::<T>() >= align_of::<U>()`.
///
/// The lifetime of the input type, `&T`, must be the same as or outlive the
/// lifetime of the output type, `&U`.
#[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. `core::mem::transmute` uses
// compiler magic to enforce size equality so long as the types are
// concrete. We use `Align` to create a type whose size is equal
// to the alignment of another type so that we can use `transmute` to
// check alignment as well.

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 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!();

let max_align_t_align_u = $crate::MaxAlignsOf::new(t, u);
// The type wildcard in this bound is inferred to be `T` because
// `size_is_align.into_t()` is assigned to `t` (which has type `T`).
//
// SAFETY: This code is never run.
let size_is_align: $crate::AlignOf<_> = unsafe { $crate::__real_transmute(max_align_t_align_u) };
t = size_is_align.into_t();
&u
} else {
// SAFETY: `core::mem::transmute` ensures that the type of `e` and
// the type of this macro invocation expression have the same size.
// We know this transmute is safe thanks to the `AsBytes` and
// `FromBytes` bounds enforced by the `false` branch.
//
// We use `$crate::__real_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::__real_transmute(e) }
}
}}
}

/// A length- and alignment-checked reference to a byte slice which can safely
/// be reinterpreted as another type.
///
Expand Down Expand Up @@ -2320,11 +2429,11 @@ impl<T: ?Sized> AsAddress for *mut T {
}
}

/// Is `t` aligned to `mem::align_of::<U>()`?
/// Is `t` aligned to `align_of::<U>()`?
#[inline(always)]
fn aligned_to<T: AsAddress, U>(t: T) -> bool {
// `mem::align_of::<U>()` is guaranteed to return a non-zero value, which in
// turn guarantees that this mod operation will not panic.
// `align_of::<U>()` is guaranteed to return a non-zero value, which in turn
// guarantees that this mod operation will not panic.
#[allow(clippy::arithmetic_side_effects)]
let remainder = t.addr() % mem::align_of::<U>();
remainder == 0
Expand Down Expand Up @@ -3195,6 +3304,49 @@ mod tests {
assert_eq!(X, ARRAY_OF_ARRAYS);
}

#[test]
fn test_size_is_align() {
macro_rules! test {
($ty:ty) => {
assert_eq!(mem::size_of::<AlignOf<$ty>>(), mem::align_of::<$ty>());
};
}

test!(());
test!(u8);
test!(AU64);
test!([AU64; 2]);
}

#[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]
fn test_address() {
// Test that the `Deref` and `DerefMut` implementations return a
Expand Down
1 change: 1 addition & 0 deletions tests/ui-msrv/transmute-illegal-lifetime.rs
9 changes: 9 additions & 0 deletions tests/ui-msrv/transmute-illegal-lifetime.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
error[E0597]: `x` does not live long enough
--> tests/ui-msrv/transmute-illegal-lifetime.rs:12:52
|
12 | let _: &'static u64 = zerocopy::transmute_ref!(&x);
| ------------ ^^ borrowed value does not live long enough
| |
| type annotation requires that `x` is borrowed for `'static`
13 | }
| - `x` dropped here while still borrowed
Loading

0 comments on commit 2eac1be

Please sign in to comment.