Skip to content

Commit 3f653f5

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 rust-lang#126460. In this PR, we revise the first case of `Tree::from_enum` to consider only the narrower category of "enums that are variantless". These enums, whose layouts are described with `Variants::Single { index }`, are special in that they do not have a variant at `index`. 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 }` but that do have a variant at `index`. This PR also adds a comment requested by @RalfJung in his review of rust-lang#126358 to `compiler/rustc_const_eval/src/interpret/discriminant.rs`. Fixes rust-lang#126460
1 parent f9515fd commit 3f653f5

File tree

3 files changed

+45
-15
lines changed

3 files changed

+45
-15
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

+12-13
Original file line numberDiff line numberDiff line change
@@ -341,30 +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
344+
// 1. enums that variantless
345345
// 2. enums for which all but one variant is uninhabited
346346
// 3. enums with multiple inhabited variants
347347
match layout.variants() {
348-
_ if layout.abi.is_uninhabited() => {
349-
// Uninhabited enums are usually (always?) zero-sized. In
348+
Variants::Single { .. } if def.variants().is_empty() => {
349+
// Uninhabited enums in the form `enum Void {}` are
350+
// currently implemented such that their layout is
351+
// described with `Variants::Single`, even though they do
352+
// not have a 'single' variant to defer to. Consequently,
353+
// we cannot use the regular code path for these enums,
354+
// because that path assumes that a variant exists at the
355+
// `Single`'s `index`.
356+
//
357+
// Variantless enums are usually (always?) zero-sized. In
350358
// the (unlikely?) event that an uninhabited enum is
351359
// non-zero-sized, this assert will trigger an ICE, and this
352360
// code should be modified such that a `layout.size` amount
353361
// 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`.
363362
assert_eq!(layout.size, Size::ZERO);
364363
Ok(Self::uninhabited())
365364
}
366365
Variants::Single { index } => {
367-
// `Variants::Single` on non-uninhabited enums denotes that
366+
// `Variants::Single` on enums with variants denotes that
368367
// the enum delegates its layout to the variant at `index`.
369368
layout_of_variant(*index)
370369
}

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.

0 commit comments

Comments
 (0)