Interpreter calls discriminant_for_variant on zero variant enum
Closed this issue · 8 comments
Interpreter calls discriminant_for_variant
on zero variant enum, when asked unsafely to do so :-).
Code
#![feature(const_discriminant)]
#![feature(const_raw_ptr_deref)]
pub enum Void { }
pub const C: () = {
unsafe { std::mem::discriminant(&*(&() as *const () as *const Void)); };
};
Error
error: internal compiler error: /rustc/41dfaaa3c66759395835b3af59b22f4f22175dc8/compiler/rustc_middle/src/ty/sty.rs:2009:17: discriminant_for_variant called on zero variant enum
thread 'rustc' panicked at 'Box<dyn Any>', compiler/rustc_errors/src/lib.rs:1146:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
note: the compiler unexpectedly panicked. this is a bug.
note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md
note: rustc 1.57.0-nightly (41dfaaa3c 2021-10-10) running on x86_64-unknown-linux-gnu
note: compiler flags: --crate-type lib
query stack during panic:
#0 [eval_to_allocation_raw] const-evaluating + checking `C`
#1 [eval_to_const_value_raw] simplifying constant for the type system `C`
end of query stack
error: aborting due to previous error
@rustbot label: +requires-nightly +A-const-eval
Ah, nasty unsafe code is nasty. ;) This reminds me of discussions I had with @eddyb a while ago about the Layout
for 0-variant enums.
Basically the bad code is like
match op.layout.variants {
Variants::Single { index } => {
let discr = match op.layout.ty.discriminant_for_variant(*self.tcx, index) {
but is this really the only part of the compiler that assumes that for Variants::Single
, we can use discriminant_for_variant
?
FTR this is the layout of the empty enum:
Layout {
fields: Primitive,
variants: Single {
index: 0,
},
abi: Uninhabited,
largest_niche: None,
align: AbiAndPrefAlign {
abi: Align {
pow2: 0,
},
pref: Align {
pow2: 0,
},
},
size: Size {
raw: 0,
},
}
The fix could be
- to make
discriminant_for_variant
returnNone
for 0-variant enums (treating them basically like any other type that does not actually have a discriminant) - to add some logic in
InterpCx::read_discriminant
that avoids callingdiscriminant_for_variant
for 0-variant enums
I haven't made up my mind yet about which one seems better... but I am leaning towards the first option.
As pointed out at #89764 (comment), our codegen backend actually says that read_discriminant
on any uninhabited type is UB. If we want to stick to that, this limits our options, at least for Miri (CTFE does not have to do all the same checks): pretty much the only clean solution I can think of is to require the place to contain data that satisfies the validity invariant.
CTFE does not check validity invariants, so this does not clearly say what we should do there; we could still treat all types without discriminants the same (by returning 0).
In release mode https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=b4e16b630ff26b1c56b45647a8330f9a returns an undef (look at llvm IR) even though from all docs it should be sound. At least as long as &Void
is sound, which I don't know if it has been decided yet.
I have not been able to weaponize it, but we should probably just make the codegen backend return 0
In my book &Void
is not sound, but indeed it has not been decided yet.