Skip to content

Commit cdd4ff8

Browse files
committed
Auto merge of rust-lang#120367 - RalfJung:project_downcast_uninhabited, r=oli-obk
interpret: project_downcast: do not ICE for uninhabited variants Fixes rust-lang#120337 This assertion was already under discussion for a bit; I think the [example](rust-lang#120337 (comment)) `@tmiasko` found is the final nail in the coffin. One could argue maybe MIR building should read the discriminant before projecting, but even then MIR optimizations should be allowed to remove that read, so the downcast should still not ICE. Maybe the downcast should be UB, but in this example UB already arises earlier when a value of type `E` is constructed. r? `@oli-obk`
2 parents 1fc46f3 + 64cd13f commit cdd4ff8

File tree

9 files changed

+128
-26
lines changed

9 files changed

+128
-26
lines changed

Diff for: compiler/rustc_const_eval/src/interpret/operand.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,12 @@ impl<'tcx, Prov: Provenance> ImmTy<'tcx, Prov> {
260260
// This makes several assumptions about what layouts we will encounter; we match what
261261
// codegen does as good as we can (see `extract_field` in `rustc_codegen_ssa/src/mir/operand.rs`).
262262
let inner_val: Immediate<_> = match (**self, self.layout.abi) {
263-
// if the entire value is uninit, then so is the field (can happen in ConstProp)
263+
// If the entire value is uninit, then so is the field (can happen in ConstProp).
264264
(Immediate::Uninit, _) => Immediate::Uninit,
265+
// If the field is uninhabited, we can forget the data (can happen in ConstProp).
266+
// `enum S { A(!), B, C }` is an example of an enum with Scalar layout that
267+
// has an `Uninhabited` variant, which means this case is possible.
268+
_ if layout.abi.is_uninhabited() => Immediate::Uninit,
265269
// the field contains no information, can be left uninit
266270
// (Scalar/ScalarPair can contain even aligned ZST, not just 1-ZST)
267271
_ if layout.is_zst() => Immediate::Uninit,

Diff for: compiler/rustc_const_eval/src/interpret/projection.rs

+2-19
Original file line numberDiff line numberDiff line change
@@ -201,25 +201,8 @@ where
201201
// see https://github.com/rust-lang/rust/issues/93688#issuecomment-1032929496.)
202202
// So we just "offset" by 0.
203203
let layout = base.layout().for_variant(self, variant);
204-
// In the future we might want to allow this to permit code like this:
205-
// (this is a Rust/MIR pseudocode mix)
206-
// ```
207-
// enum Option2 {
208-
// Some(i32, !),
209-
// None,
210-
// }
211-
//
212-
// fn panic() -> ! { panic!() }
213-
//
214-
// let x: Option2;
215-
// x.Some.0 = 42;
216-
// x.Some.1 = panic();
217-
// SetDiscriminant(x, Some);
218-
// ```
219-
// However, for now we don't generate such MIR, and this check here *has* found real
220-
// bugs (see https://github.com/rust-lang/rust/issues/115145), so we will keep rejecting
221-
// it.
222-
assert!(!layout.abi.is_uninhabited());
204+
// This variant may in fact be uninhabited.
205+
// See <https://github.com/rust-lang/rust/issues/120337>.
223206

224207
// This cannot be `transmute` as variants *can* have a smaller size than the entire enum.
225208
base.offset(Size::ZERO, layout, self)

Diff for: compiler/rustc_middle/src/mir/syntax.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1074,6 +1074,8 @@ pub enum ProjectionElem<V, T> {
10741074
/// "Downcast" to a variant of an enum or a coroutine.
10751075
///
10761076
/// The included Symbol is the name of the variant, used for printing MIR.
1077+
///
1078+
/// This operation itself is never UB, all it does is change the type of the place.
10771079
Downcast(Option<Symbol>, VariantIdx),
10781080

10791081
/// Like an explicit cast from an opaque type to a concrete type, but without

Diff for: compiler/rustc_mir_transform/src/dataflow_const_prop.rs

+1-6
Original file line numberDiff line numberDiff line change
@@ -403,12 +403,7 @@ impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> {
403403
operand,
404404
&mut |elem, op| match elem {
405405
TrackElem::Field(idx) => self.ecx.project_field(op, idx.as_usize()).ok(),
406-
TrackElem::Variant(idx) => {
407-
if op.layout.for_variant(&self.ecx, idx).abi.is_uninhabited() {
408-
return None;
409-
}
410-
self.ecx.project_downcast(op, idx).ok()
411-
}
406+
TrackElem::Variant(idx) => self.ecx.project_downcast(op, idx).ok(),
412407
TrackElem::Discriminant => {
413408
let variant = self.ecx.read_discriminant(op).ok()?;
414409
let discr_value =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Validation stops the test before the ICE we used to hit
2+
//@compile-flags: -Zmiri-disable-validation
3+
4+
#![feature(never_type)]
5+
#[derive(Copy, Clone)]
6+
pub enum E {
7+
A(!),
8+
}
9+
pub union U {
10+
u: (),
11+
e: E,
12+
}
13+
14+
fn main() {
15+
let E::A(ref _a) = unsafe { &(&U { u: () }).e };
16+
}

Diff for: tests/mir-opt/gvn_uninhabited.f.GVN.panic-abort.diff

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
- // MIR for `f` before GVN
2+
+ // MIR for `f` after GVN
3+
4+
fn f() -> u32 {
5+
let mut _0: u32;
6+
let _1: u32;
7+
let mut _2: E;
8+
let mut _3: &U;
9+
let _4: U;
10+
scope 1 {
11+
debug i => _1;
12+
}
13+
scope 2 {
14+
let mut _5: &U;
15+
}
16+
17+
bb0: {
18+
StorageLive(_2);
19+
StorageLive(_3);
20+
_5 = const _;
21+
_3 = &(*_5);
22+
_2 = ((*_3).1: E);
23+
StorageLive(_1);
24+
- _1 = ((_2 as A).1: u32);
25+
+ _1 = const 0_u32;
26+
StorageDead(_3);
27+
StorageDead(_2);
28+
- _0 = _1;
29+
+ _0 = const 0_u32;
30+
StorageDead(_1);
31+
return;
32+
}
33+
}
34+
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
- // MIR for `f` before GVN
2+
+ // MIR for `f` after GVN
3+
4+
fn f() -> u32 {
5+
let mut _0: u32;
6+
let _1: u32;
7+
let mut _2: E;
8+
let mut _3: &U;
9+
let _4: U;
10+
scope 1 {
11+
debug i => _1;
12+
}
13+
scope 2 {
14+
let mut _5: &U;
15+
}
16+
17+
bb0: {
18+
StorageLive(_2);
19+
StorageLive(_3);
20+
_5 = const _;
21+
_3 = &(*_5);
22+
_2 = ((*_3).1: E);
23+
StorageLive(_1);
24+
- _1 = ((_2 as A).1: u32);
25+
+ _1 = const 0_u32;
26+
StorageDead(_3);
27+
StorageDead(_2);
28+
- _0 = _1;
29+
+ _0 = const 0_u32;
30+
StorageDead(_1);
31+
return;
32+
}
33+
}
34+

Diff for: tests/mir-opt/gvn_uninhabited.rs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// unit-test: GVN
2+
// compile-flags: -O
3+
// EMIT_MIR_FOR_EACH_PANIC_STRATEGY
4+
// skip-filecheck
5+
6+
#![feature(never_type)]
7+
8+
#[derive(Copy, Clone)]
9+
pub enum E {
10+
A(!, u32),
11+
}
12+
13+
pub union U {
14+
i: u32,
15+
e: E,
16+
}
17+
18+
// EMIT_MIR gvn_uninhabited.f.GVN.diff
19+
pub const fn f() -> u32 {
20+
let E::A(_, i) = unsafe { (&U { i: 0 }).e };
21+
i
22+
}
23+
24+
fn main() {}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// check-pass
2+
#![feature(never_type)]
3+
#[derive(Copy, Clone)]
4+
pub enum E { A(!), }
5+
pub union U { u: (), e: E, }
6+
pub const C: () = {
7+
let E::A(ref a) = unsafe { &(&U { u: () }).e};
8+
};
9+
10+
fn main() {}

0 commit comments

Comments
 (0)