Skip to content

Commit

Permalink
fix safe-transmute handling of enums
Browse files Browse the repository at this point in the history
  • Loading branch information
RalfJung committed Dec 1, 2024
1 parent a36652c commit 3dc9b1b
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 73 deletions.
6 changes: 5 additions & 1 deletion compiler/rustc_abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1505,7 +1505,11 @@ impl BackendRepr {
#[cfg_attr(feature = "nightly", derive(HashStable_Generic))]
pub enum Variants<FieldIdx: Idx, VariantIdx: Idx> {
/// Single enum variants, structs/tuples, unions, and all non-ADTs.
Single { index: VariantIdx },
Single {
/// Always 0 for non-enums/generators.
/// For enums without a variant, this is an invalid index!
index: VariantIdx,
},

/// Enum-likes with more than one variant: each variant comes with
/// a *discriminant* (usually the same as the variant index but the user can
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/interpret/discriminant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
if ty.is_enum() {
// Hilariously, `Single` is used even for 0-variant enums.
// (See https://github.com/rust-lang/rust/issues/89765).
if matches!(ty.kind(), ty::Adt(def, ..) if def.variants().is_empty()) {
if ty.ty_adt_def().unwrap().variants().is_empty() {
throw_ub!(UninhabitedEnumVariantRead(index))
}
// For consistency with `write_discriminant`, and to make sure that
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_middle/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,8 @@ rustc_queries! {
}

/// Computes the tag (if any) for a given type and variant.
/// `None` means that the variant doesn't need a tag (because it is niched).
/// Will panic for uninhabited variants.
query tag_for_variant(
key: (Ty<'tcx>, abi::VariantIdx)
) -> Option<ty::ScalarInt> {
Expand Down
57 changes: 27 additions & 30 deletions compiler/rustc_transmute/src/layout/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,38 +319,35 @@ pub(crate) mod rustc {
) -> Result<Self, Err> {
assert!(def.is_enum());

// Computes the variant of a given index.
let layout_of_variant = |index, encoding: Option<TagEncoding<VariantIdx>>| {
let tag = cx.tcx().tag_for_variant((cx.tcx().erase_regions(ty), index));
let variant_def = Def::Variant(def.variant(index));
let variant_layout = ty_variant(cx, (ty, layout), index);
Self::from_variant(
variant_def,
tag.map(|tag| (tag, index, encoding.unwrap())),
(ty, variant_layout),
layout.size,
cx,
)
};
// Computes the layout of a variant.
let layout_of_variant =
|index, encoding: Option<TagEncoding<VariantIdx>>| -> Result<Self, Err> {
let variant_layout = ty_variant(cx, (ty, layout), index);
if variant_layout.is_uninhabited() {
return Ok(Self::uninhabited());
}
let tag = cx.tcx().tag_for_variant((cx.tcx().erase_regions(ty), index));
let variant_def = Def::Variant(def.variant(index));
Self::from_variant(
variant_def,
tag.map(|tag| (tag, index, encoding.unwrap())),
(ty, variant_layout),
layout.size,
cx,
)
};

// We consider three kinds of enums, each demanding a different
// treatment of their layout computation:
// 1. enums that are uninhabited ZSTs
// 2. enums that delegate their layout to a variant
// 3. enums with multiple variants
match layout.variants() {
Variants::Single { .. } if layout.is_uninhabited() && layout.size == Size::ZERO => {
// The layout representation of uninhabited, ZST enums is
// defined to be like that of the `!` type, as opposed of a
// typical enum. Consequently, they cannot be descended into
// as if they typical enums. We therefore special-case this
// scenario and simply return an uninhabited `Tree`.
Ok(Self::uninhabited())
}
Variants::Single { index } => {
// `Variants::Single` on enums with variants denotes that
// the enum delegates its layout to the variant at `index`.
layout_of_variant(*index, None)
// Hilariously, `Single` is used even for 0-variant enums;
// `index` is just junk in that case.
if ty.ty_adt_def().unwrap().variants().is_empty() {
Ok(Self::uninhabited())
} else {
// `Variants::Single` on enums with variants denotes that
// the enum delegates its layout to the variant at `index`.
layout_of_variant(*index, None)
}
}
Variants::Multiple { tag, tag_encoding, tag_field, .. } => {
// `Variants::Multiple` denotes an enum with multiple
Expand All @@ -369,7 +366,7 @@ pub(crate) mod rustc {
},
)?;

return Ok(Self::def(Def::Adt(def)).then(variants));
Ok(Self::def(Def::Adt(def)).then(variants))
}
}
}
Expand Down
30 changes: 0 additions & 30 deletions tests/crashes/126267.rs

This file was deleted.

21 changes: 21 additions & 0 deletions tests/ui/transmutability/uninhabited.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ mod assert {
}
}>
{}

pub fn is_transmutable<Src, Dst>()
where
Dst: TransmuteFrom<Src>, // safety is NOT assumed, but proven
{}
}

fn void() {
Expand Down Expand Up @@ -91,3 +96,19 @@ fn distant_void() {
assert::is_maybe_transmutable::<DistantVoid, &'static Void>();
assert::is_maybe_transmutable::<u128, DistantVoid>(); //~ ERROR: cannot be safely transmuted
}

fn issue_126267() {
pub enum ApiError {}
pub struct TokioError {
b: bool,
}
pub enum Error {
Api { source: ApiError }, // this variant is uninhabited
Ethereum,
Tokio { source: TokioError },
}

struct Src;
type Dst = Error;
assert::is_transmutable::<Src, Dst>(); //~ERROR: cannot be safely transmuted
}
37 changes: 26 additions & 11 deletions tests/ui/transmutability/uninhabited.stderr
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
error[E0080]: evaluation of constant value failed
--> $DIR/uninhabited.rs:41:9
--> $DIR/uninhabited.rs:46:9
|
LL | assert!(false);
| ^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: false', $DIR/uninhabited.rs:41:9
| ^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: false', $DIR/uninhabited.rs:46:9
|
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0080]: evaluation of constant value failed
--> $DIR/uninhabited.rs:63:9
--> $DIR/uninhabited.rs:68:9
|
LL | assert!(false);
| ^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: false', $DIR/uninhabited.rs:63:9
| ^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: false', $DIR/uninhabited.rs:68:9
|
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0080]: evaluation of constant value failed
--> $DIR/uninhabited.rs:87:9
--> $DIR/uninhabited.rs:92:9
|
LL | assert!(false);
| ^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: false', $DIR/uninhabited.rs:87:9
| ^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: false', $DIR/uninhabited.rs:92:9
|
= note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: `()` cannot be safely transmuted into `void::Void`
--> $DIR/uninhabited.rs:29:41
--> $DIR/uninhabited.rs:34:41
|
LL | assert::is_maybe_transmutable::<(), Void>();
| ^^^^ `void::Void` is uninhabited
Expand All @@ -45,7 +45,7 @@ LL | | }>
| |__________^ required by this bound in `is_maybe_transmutable`

error[E0277]: `()` cannot be safely transmuted into `yawning_void_struct::Void`
--> $DIR/uninhabited.rs:49:41
--> $DIR/uninhabited.rs:54:41
|
LL | assert::is_maybe_transmutable::<(), Void>();
| ^^^^ `yawning_void_struct::Void` is uninhabited
Expand All @@ -67,7 +67,7 @@ LL | | }>
| |__________^ required by this bound in `is_maybe_transmutable`

error[E0277]: `()` cannot be safely transmuted into `yawning_void_enum::Void`
--> $DIR/uninhabited.rs:71:41
--> $DIR/uninhabited.rs:76:41
|
LL | assert::is_maybe_transmutable::<(), Void>();
| ^^^^ `yawning_void_enum::Void` is uninhabited
Expand All @@ -89,7 +89,7 @@ LL | | }>
| |__________^ required by this bound in `is_maybe_transmutable`

error[E0277]: `u128` cannot be safely transmuted into `DistantVoid`
--> $DIR/uninhabited.rs:92:43
--> $DIR/uninhabited.rs:97:43
|
LL | assert::is_maybe_transmutable::<u128, DistantVoid>();
| ^^^^^^^^^^^ at least one value of `u128` isn't a bit-valid value of `DistantVoid`
Expand All @@ -110,7 +110,22 @@ LL | | }
LL | | }>
| |__________^ required by this bound in `is_maybe_transmutable`

error: aborting due to 7 previous errors
error[E0277]: `Src` cannot be safely transmuted into `issue_126267::Error`
--> $DIR/uninhabited.rs:113:36
|
LL | assert::is_transmutable::<Src, Dst>();
| ^^^ `issue_126267::Error` may carry safety invariants
|
note: required by a bound in `is_transmutable`
--> $DIR/uninhabited.rs:22:18
|
LL | pub fn is_transmutable<Src, Dst>()
| --------------- required by a bound in this function
LL | where
LL | Dst: TransmuteFrom<Src>, // safety is NOT assumed, but proven
| ^^^^^^^^^^^^^^^^^^ required by this bound in `is_transmutable`

error: aborting due to 8 previous errors

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

0 comments on commit 3dc9b1b

Please sign in to comment.