Skip to content

Commit d25d4fc

Browse files
committed
[macros] Support shrinking value transmutes
In `transmute!`, support an `#![allow(shrink)]` attribute which is invoked as follows: transmute!(#![allow(shrink)] src); When this attribute is provided, `transmute!` will permit shrinking transmutes, in which the destination value may be smaller than the source value. gherrit-pr-id: I46b18b4b1d10507b7e1d2e01b09dc4960cfcdce1
1 parent 184dbb5 commit d25d4fc

7 files changed

+141
-26
lines changed

src/macros.rs

Lines changed: 75 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -51,39 +51,58 @@
5151
/// This macro can be invoked in `const` contexts.
5252
#[macro_export]
5353
macro_rules! transmute {
54-
($e:expr) => {{
55-
// NOTE: This must be a macro (rather than a function with trait bounds)
56-
// because there's no way, in a generic context, to enforce that two
57-
// types have the same size. `core::mem::transmute` uses compiler magic
58-
// to enforce this so long as the types are concrete.
59-
60-
let e = $e;
54+
// NOTE: This must be a macro (rather than a function with trait bounds)
55+
// because there's no way, in a generic context, to enforce that two types
56+
// have the same size. `core::mem::transmute` uses compiler magic to enforce
57+
// this so long as the types are concrete.
58+
(#![allow(shrink)] $e:expr) => {{
59+
let mut e = $e;
6160
if false {
62-
// This branch, though never taken, ensures that the type of `e` is
63-
// `IntoBytes` and that the type of this macro invocation expression
64-
// is `FromBytes`.
61+
$crate::__transmute_inner!(@assert_into_bytes_from_bytes e)
62+
} else {
63+
use $crate::util::macro_util::core_reexport::mem::ManuallyDrop;
64+
65+
#[repr(C)]
66+
union Transmute<Src, Dst> {
67+
src: ManuallyDrop<Src>,
68+
dst: ManuallyDrop<Dst>,
69+
}
6570

66-
struct AssertIsIntoBytes<T: $crate::IntoBytes>(T);
67-
let _ = AssertIsIntoBytes(e);
71+
// TODO: Update this safety comment.
72+
//
73+
// SAFETY: `core::mem::transmute` ensures that the type of `e` and
74+
// the type of this macro invocation expression have the same size.
75+
// We know this transmute is safe thanks to the `IntoBytes` and
76+
// `FromBytes` bounds enforced by the `false` branch.
77+
let u: Transmute<_, _> = unsafe {
78+
// Clippy: We can't annotate the types; this macro is designed
79+
// to infer the types from the calling context.
80+
#[allow(clippy::missing_transmute_annotations, unnecessary_transmutes)]
81+
$crate::util::macro_util::core_reexport::mem::transmute(e)
82+
};
6883

69-
struct AssertIsFromBytes<U: $crate::FromBytes>(U);
70-
#[allow(unused, unreachable_code)]
71-
let u = AssertIsFromBytes(loop {});
72-
u.0
84+
if false {
85+
// SAFETY: This code is never executed.
86+
e = ManuallyDrop::into_inner(unsafe { u.src });
87+
// Suppress the `unused_assignments` lint on the previous line.
88+
let _ = e;
89+
loop {}
90+
} else {
91+
// TODO: Safety comment
92+
let dst = unsafe { u.dst };
93+
$crate::util::macro_util::must_use(ManuallyDrop::into_inner(dst))
94+
}
95+
}
96+
}};
97+
($e:expr) => {{
98+
let e = $e;
99+
if false {
100+
$crate::__transmute_inner!(@assert_into_bytes_from_bytes e)
73101
} else {
74102
// SAFETY: `core::mem::transmute` ensures that the type of `e` and
75103
// the type of this macro invocation expression have the same size.
76104
// We know this transmute is safe thanks to the `IntoBytes` and
77105
// `FromBytes` bounds enforced by the `false` branch.
78-
//
79-
// We use this reexport of `core::mem::transmute` because we know it
80-
// will always be available for crates which are using the 2015
81-
// edition of Rust. By contrast, if we were to use
82-
// `std::mem::transmute`, this macro would not work for such crates
83-
// in `no_std` contexts, and if we were to use
84-
// `core::mem::transmute`, this macro would not work in `std`
85-
// contexts in which `core` was not manually imported. This is not a
86-
// problem for 2018 edition crates.
87106
let u = unsafe {
88107
// Clippy: We can't annotate the types; this macro is designed
89108
// to infer the types from the calling context.
@@ -92,7 +111,29 @@ macro_rules! transmute {
92111
};
93112
$crate::util::macro_util::must_use(u)
94113
}
95-
}}
114+
}};
115+
}
116+
117+
#[macro_export]
118+
#[doc(hidden)]
119+
macro_rules! __transmute_inner {
120+
(@assert_into_bytes_from_bytes $e:expr) => {{
121+
// This branch, though never taken, ensures that the type of `e` is
122+
// `IntoBytes` and that the type of the outer macro invocation
123+
// expression is `FromBytes`.
124+
125+
fn transmute<Src, Dst>(src: Src) -> Dst
126+
where
127+
Src: $crate::IntoBytes,
128+
Dst: $crate::FromBytes,
129+
{
130+
let _ = src;
131+
loop {}
132+
}
133+
loop {}
134+
#[allow(unreachable_code)]
135+
transmute($e)
136+
}};
96137
}
97138

98139
/// Safely transmutes a mutable or immutable reference of one type to an
@@ -1109,6 +1150,10 @@ mod tests {
11091150
let x: [u8; 8] = transmute!(array_of_arrays);
11101151
assert_eq!(x, array_of_u8s);
11111152

1153+
// Test that memory is transmuted as expected when shrinking.
1154+
let x: [[u8; 2]; 3] = transmute!(#![allow(shrink)] array_of_u8s);
1155+
assert_eq!(x, [[0u8, 1], [2, 3], [4, 5]]);
1156+
11121157
// Test that the source expression's value is forgotten rather than
11131158
// dropped.
11141159
#[derive(IntoBytes)]
@@ -1121,12 +1166,16 @@ mod tests {
11211166
}
11221167
#[allow(clippy::let_unit_value)]
11231168
let _: () = transmute!(PanicOnDrop(()));
1169+
#[allow(clippy::let_unit_value)]
1170+
let _: () = transmute!(#![allow(shrink)] PanicOnDrop(()));
11241171

11251172
// Test that `transmute!` is legal in a const context.
11261173
const ARRAY_OF_U8S: [u8; 8] = [0u8, 1, 2, 3, 4, 5, 6, 7];
11271174
const ARRAY_OF_ARRAYS: [[u8; 2]; 4] = [[0, 1], [2, 3], [4, 5], [6, 7]];
11281175
const X: [[u8; 2]; 4] = transmute!(ARRAY_OF_U8S);
11291176
assert_eq!(X, ARRAY_OF_ARRAYS);
1177+
const X_SHRINK: [[u8; 2]; 3] = transmute!(#![allow(shrink)] ARRAY_OF_U8S);
1178+
assert_eq!(X_SHRINK, [[0u8, 1], [2, 3], [4, 5]]);
11301179

11311180
// Test that `transmute!` works with `!Immutable` types.
11321181
let x: usize = transmute!(UnsafeCell::new(1usize));
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-size-increase-allow-shrink.rs
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error[E0308]: mismatched types
2+
--> tests/ui-msrv/../../zerocopy-derive/tests/include.rs
3+
|
4+
| let ptr = unsafe { ptr.cast_unsized_unchecked(|p| p.as_non_null().cast()) };
5+
| ^^^^^^^^^^^^^^^^^^^^^^ expected struct `PtrInner`, found struct `NonNull`
6+
|
7+
= note: expected struct `PtrInner<'_, _>`
8+
found struct `NonNull<_>`
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2023 The Fuchsia Authors
2+
//
3+
// Licensed under a BSD-style license <LICENSE-BSD>, Apache License, Version 2.0
4+
// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0>, or the MIT
5+
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your option.
6+
// This file may not be copied, modified, or distributed except according to
7+
// those terms.
8+
9+
include!("../../zerocopy-derive/tests/include.rs");
10+
11+
extern crate zerocopy;
12+
13+
use util::AU16;
14+
use zerocopy::transmute;
15+
16+
fn main() {}
17+
18+
// `transmute!` does not support transmuting from a smaller type to a larger
19+
// one.
20+
const INCREASE_SIZE: AU16 = transmute!(#![allow(shrink)] 0u8);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
error[E0308]: mismatched types
2+
--> tests/ui-nightly/../../zerocopy-derive/tests/include.rs
3+
|
4+
| let ptr = unsafe { ptr.cast_unsized_unchecked(|p| p.as_non_null().cast()) };
5+
| ^^^^^^^^^^^^^^^^^^^^^^ expected `PtrInner<'_, _>`, found `NonNull<_>`
6+
|
7+
= note: expected struct `PtrInner<'_, _>`
8+
found struct `NonNull<_>`
9+
10+
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
11+
--> tests/ui-nightly/transmute-size-increase-allow-shrink.rs:20:29
12+
|
13+
20 | const INCREASE_SIZE: AU16 = transmute!(#![allow(shrink)] 0u8);
14+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15+
|
16+
= note: source type: `u8` (8 bits)
17+
= note: target type: `Transmute<u8, AU16>` (16 bits)
18+
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../ui-nightly/transmute-size-increase-allow-shrink.rs
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
2+
--> tests/ui-stable/transmute-size-increase-allow-shrink.rs:20:29
3+
|
4+
20 | const INCREASE_SIZE: AU16 = transmute!(#![allow(shrink)] 0u8);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: source type: `u8` (8 bits)
8+
= note: target type: `Transmute<u8, AU16>` (16 bits)
9+
= note: this error originates in the macro `transmute` (in Nightly builds, run with -Z macro-backtrace for more info)
10+
11+
error[E0308]: mismatched types
12+
--> tests/ui-stable/../../zerocopy-derive/tests/include.rs
13+
|
14+
| let ptr = unsafe { ptr.cast_unsized_unchecked(|p| p.as_non_null().cast()) };
15+
| ^^^^^^^^^^^^^^^^^^^^^^ expected `PtrInner<'_, _>`, found `NonNull<_>`
16+
|
17+
= note: expected struct `PtrInner<'_, _>`
18+
found struct `NonNull<_>`

0 commit comments

Comments
 (0)