Skip to content

Commit df1d616

Browse files
committed
safe transmute: support non-ZST, variantful, uninhabited enums
Previously, `Tree::from_enum`'s implementation branched into three disjoint cases: 1. enums that uninhabited 2. enums for which all but one variant is uninhabited 3. enums with multiple inhabited variants This branching (incorrectly) did not differentiate between variantful and variantless uninhabited enums. In both cases, we assumed (and asserted) that uninhabited enums are zero-sized types. This assumption is false for enums like: enum Uninhabited { A(!, u128) } ...which, currently, has the same size as `u128`. This faulty assumption manifested as the ICE reported in #126460. In this PR, we revise the first case of `Tree::from_enum` to consider only the narrow category of "enums that are uninhabited ZSTs". These enums, whose layouts are described with `Variants::Single { index }`, are special in their layouts otherwise resemble the `!` type and cannot be descended into like typical enums. This first case captures uninhabited enums like: enum Uninhabited { A(!, !), B(!) } The second case is revised to consider the broader category of "enums that defer their layout to one of their variants"; i.e., enums whose layouts are described with `Variants::Single { index }` and that do have a variant at `index`. This second case captures uninhabited enums that are not ZSTs, like: enum Uninhabited { A(!, u128) } ...which represent their variants with `Variants::Single`. Finally, the third case is revised to cover the broader category of "enums with multiple variants", which captures uninhabited, non-ZST enums like: enum Uninhabited { A(u8, !), B(!, u32) } ...which represent their variants with `Variants::Multiple`. This PR also adds a comment requested by RalfJung in his review of #126358 to `compiler/rustc_const_eval/src/interpret/discriminant.rs`. Fixes #126460
1 parent f9515fd commit df1d616

File tree

5 files changed

+89
-30
lines changed

5 files changed

+89
-30
lines changed

compiler/rustc_const_eval/src/interpret/discriminant.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,16 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
241241
variant_index: VariantIdx,
242242
) -> InterpResult<'tcx, Option<(ScalarInt, usize)>> {
243243
match self.layout_of(ty)?.variants {
244-
abi::Variants::Single { .. } => Ok(None),
244+
abi::Variants::Single { .. } => {
245+
// The tag of a `Single` enum is like the tag of the niched
246+
// variant: there's no tag as the discriminant is encoded
247+
// entirely implicitly. If `write_discriminant` ever hits this
248+
// case, we do a "validation read" to ensure the the right
249+
// discriminant is encoded implicitly, so any attempt to write
250+
// the wrong discriminant for a `Single` enum will reliably
251+
// result in UB.
252+
Ok(None)
253+
}
245254

246255
abi::Variants::Multiple {
247256
tag_encoding: TagEncoding::Direct,

compiler/rustc_transmute/src/layout/tree.rs

+14-22
Original file line numberDiff line numberDiff line change
@@ -341,37 +341,29 @@ pub(crate) mod rustc {
341341

342342
// We consider three kinds of enums, each demanding a different
343343
// treatment of their layout computation:
344-
// 1. enums that are uninhabited
345-
// 2. enums for which all but one variant is uninhabited
346-
// 3. enums with multiple inhabited variants
344+
// 1. enums that are uninhabited ZSTs
345+
// 2. enums that delegate their layout to a variant
346+
// 3. enums with multiple variants
347347
match layout.variants() {
348-
_ if layout.abi.is_uninhabited() => {
349-
// Uninhabited enums are usually (always?) zero-sized. In
350-
// the (unlikely?) event that an uninhabited enum is
351-
// non-zero-sized, this assert will trigger an ICE, and this
352-
// code should be modified such that a `layout.size` amount
353-
// of uninhabited bytes is returned instead.
354-
//
355-
// Uninhabited enums are currently implemented such that
356-
// their layout is described with `Variants::Single`, even
357-
// though they don't necessarily have a 'single' variant to
358-
// defer to. That said, we don't bother specifically
359-
// matching on `Variants::Single` in this arm because the
360-
// behavioral principles here remain true even if, for
361-
// whatever reason, the compiler describes an uninhabited
362-
// enum with `Variants::Multiple`.
363-
assert_eq!(layout.size, Size::ZERO);
348+
Variants::Single { .. }
349+
if layout.abi.is_uninhabited() && layout.size == Size::ZERO =>
350+
{
351+
// The layout representation of uninhabited, ZST enums is
352+
// defined to be like that of the `!` type, as opposed of a
353+
// typical enum. Consequently, they cannot be descended into
354+
// as if they typical enums. We therefore special-case this
355+
// scenario and simply return an uninhabited `Tree`.
364356
Ok(Self::uninhabited())
365357
}
366358
Variants::Single { index } => {
367-
// `Variants::Single` on non-uninhabited enums denotes that
359+
// `Variants::Single` on enums with variants denotes that
368360
// the enum delegates its layout to the variant at `index`.
369361
layout_of_variant(*index)
370362
}
371363
Variants::Multiple { tag_field, .. } => {
372364
// `Variants::Multiple` denotes an enum with multiple
373-
// inhabited variants. The layout of such an enum is the
374-
// disjunction of the layouts of its tagged variants.
365+
// variants. The layout of such an enum is the disjunction
366+
// of the layouts of its tagged variants.
375367

376368
// For enums (but not coroutines), the tag field is
377369
// currently always the first field of the layout.

tests/ui/transmutability/enums/uninhabited_optimization.rs

+6
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,14 @@ enum SingleUninhabited {
1919
Y(Uninhabited),
2020
}
2121

22+
enum MultipleUninhabited {
23+
X(u8, Uninhabited),
24+
Y(Uninhabited, u16),
25+
}
26+
2227
fn main() {
2328
assert_transmutable::<Uninhabited>();
2429
assert_transmutable::<SingleInhabited>();
2530
assert_transmutable::<SingleUninhabited>();
31+
assert_transmutable::<MultipleUninhabited>();
2632
}

tests/ui/transmutability/uninhabited.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ fn void() {
3030
}
3131

3232
// Non-ZST uninhabited types are, nonetheless, uninhabited.
33-
fn yawning_void() {
33+
fn yawning_void_struct() {
3434
enum Void {}
3535

3636
struct YawningVoid(Void, u128);
@@ -49,6 +49,28 @@ fn yawning_void() {
4949
assert::is_maybe_transmutable::<(), Void>(); //~ ERROR: cannot be safely transmuted
5050
}
5151

52+
// Non-ZST uninhabited types are, nonetheless, uninhabited.
53+
fn yawning_void_enum() {
54+
enum Void {}
55+
56+
enum YawningVoid {
57+
A(Void, u128),
58+
}
59+
60+
const _: () = {
61+
assert!(std::mem::size_of::<YawningVoid>() == std::mem::size_of::<u128>());
62+
// Just to be sure the above constant actually evaluated:
63+
assert!(false); //~ ERROR: evaluation of constant value failed
64+
};
65+
66+
// This transmutation is vacuously acceptable; since one cannot construct a
67+
// `Void`, unsoundness cannot directly arise from transmuting a void into
68+
// anything else.
69+
assert::is_maybe_transmutable::<YawningVoid, u128>();
70+
71+
assert::is_maybe_transmutable::<(), Void>(); //~ ERROR: cannot be safely transmuted
72+
}
73+
5274
// References to uninhabited types are, logically, uninhabited, but for layout
5375
// purposes are not ZSTs, and aren't treated as uninhabited when they appear in
5476
// enum variants.

tests/ui/transmutability/uninhabited.stderr

+36-6
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,18 @@ LL | assert!(false);
77
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
88

99
error[E0080]: evaluation of constant value failed
10-
--> $DIR/uninhabited.rs:65:9
10+
--> $DIR/uninhabited.rs:63:9
1111
|
1212
LL | assert!(false);
13-
| ^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: false', $DIR/uninhabited.rs:65:9
13+
| ^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: false', $DIR/uninhabited.rs:63:9
14+
|
15+
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
16+
17+
error[E0080]: evaluation of constant value failed
18+
--> $DIR/uninhabited.rs:87:9
19+
|
20+
LL | assert!(false);
21+
| ^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: false', $DIR/uninhabited.rs:87:9
1422
|
1523
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)
1624

@@ -36,11 +44,33 @@ LL | | }
3644
LL | | }>
3745
| |__________^ required by this bound in `is_maybe_transmutable`
3846

39-
error[E0277]: `()` cannot be safely transmuted into `yawning_void::Void`
47+
error[E0277]: `()` cannot be safely transmuted into `yawning_void_struct::Void`
4048
--> $DIR/uninhabited.rs:49:41
4149
|
4250
LL | assert::is_maybe_transmutable::<(), Void>();
43-
| ^^^^ `yawning_void::Void` is uninhabited
51+
| ^^^^ `yawning_void_struct::Void` is uninhabited
52+
|
53+
note: required by a bound in `is_maybe_transmutable`
54+
--> $DIR/uninhabited.rs:10:14
55+
|
56+
LL | pub fn is_maybe_transmutable<Src, Dst>()
57+
| --------------------- required by a bound in this function
58+
LL | where
59+
LL | Dst: BikeshedIntrinsicFrom<Src, {
60+
| ______________^
61+
LL | | Assume {
62+
LL | | alignment: true,
63+
LL | | lifetimes: true,
64+
... |
65+
LL | | }
66+
LL | | }>
67+
| |__________^ required by this bound in `is_maybe_transmutable`
68+
69+
error[E0277]: `()` cannot be safely transmuted into `yawning_void_enum::Void`
70+
--> $DIR/uninhabited.rs:71:41
71+
|
72+
LL | assert::is_maybe_transmutable::<(), Void>();
73+
| ^^^^ `yawning_void_enum::Void` is uninhabited
4474
|
4575
note: required by a bound in `is_maybe_transmutable`
4676
--> $DIR/uninhabited.rs:10:14
@@ -59,7 +89,7 @@ LL | | }>
5989
| |__________^ required by this bound in `is_maybe_transmutable`
6090

6191
error[E0277]: `u128` cannot be safely transmuted into `DistantVoid`
62-
--> $DIR/uninhabited.rs:70:43
92+
--> $DIR/uninhabited.rs:92:43
6393
|
6494
LL | assert::is_maybe_transmutable::<u128, DistantVoid>();
6595
| ^^^^^^^^^^^ at least one value of `u128` isn't a bit-valid value of `DistantVoid`
@@ -80,7 +110,7 @@ LL | | }
80110
LL | | }>
81111
| |__________^ required by this bound in `is_maybe_transmutable`
82112

83-
error: aborting due to 5 previous errors
113+
error: aborting due to 7 previous errors
84114

85115
Some errors have detailed explanations: E0080, E0277.
86116
For more information about an error, try `rustc --explain E0080`.

0 commit comments

Comments
 (0)