Skip to content

Commit 6e87eb5

Browse files
authored
Rollup merge of #133681 - RalfJung:niches, r=wesleywiser
improve TagEncoding::Niche docs, sanity check, and UB checks Turns out the `niche_variants` range can actually contain the `untagged_variant`. We should report this as UB in Miri, so this PR implements that. Also rename `partially_check_layout` to `layout_sanity_check` for better consistency with how similar functions are called in other parts of the compiler. Turns out my adjustments to the transmutation logic also fix #126267.
2 parents 58fac8f + 611a991 commit 6e87eb5

File tree

13 files changed

+177
-92
lines changed

13 files changed

+177
-92
lines changed

compiler/rustc_abi/src/lib.rs

+24-6
Original file line numberDiff line numberDiff line change
@@ -1215,6 +1215,15 @@ impl Scalar {
12151215
Scalar::Union { .. } => true,
12161216
}
12171217
}
1218+
1219+
/// Returns `true` if this is a signed integer scalar
1220+
#[inline]
1221+
pub fn is_signed(&self) -> bool {
1222+
match self.primitive() {
1223+
Primitive::Int(_, signed) => signed,
1224+
_ => false,
1225+
}
1226+
}
12181227
}
12191228

12201229
// NOTE: This struct is generic over the FieldIdx for rust-analyzer usage.
@@ -1401,10 +1410,7 @@ impl BackendRepr {
14011410
#[inline]
14021411
pub fn is_signed(&self) -> bool {
14031412
match self {
1404-
BackendRepr::Scalar(scal) => match scal.primitive() {
1405-
Primitive::Int(_, signed) => signed,
1406-
_ => false,
1407-
},
1413+
BackendRepr::Scalar(scal) => scal.is_signed(),
14081414
_ => panic!("`is_signed` on non-scalar ABI {self:?}"),
14091415
}
14101416
}
@@ -1499,7 +1505,11 @@ impl BackendRepr {
14991505
#[cfg_attr(feature = "nightly", derive(HashStable_Generic))]
15001506
pub enum Variants<FieldIdx: Idx, VariantIdx: Idx> {
15011507
/// Single enum variants, structs/tuples, unions, and all non-ADTs.
1502-
Single { index: VariantIdx },
1508+
Single {
1509+
/// Always 0 for non-enums/generators.
1510+
/// For enums without a variant, this is an invalid index!
1511+
index: VariantIdx,
1512+
},
15031513

15041514
/// Enum-likes with more than one variant: each variant comes with
15051515
/// a *discriminant* (usually the same as the variant index but the user can
@@ -1528,14 +1538,22 @@ pub enum TagEncoding<VariantIdx: Idx> {
15281538
/// The variant `untagged_variant` contains a niche at an arbitrary
15291539
/// offset (field `tag_field` of the enum), which for a variant with
15301540
/// discriminant `d` is set to
1531-
/// `(d - niche_variants.start).wrapping_add(niche_start)`.
1541+
/// `(d - niche_variants.start).wrapping_add(niche_start)`
1542+
/// (this is wrapping arithmetic using the type of the niche field).
15321543
///
15331544
/// For example, `Option<(usize, &T)>` is represented such that
15341545
/// `None` has a null pointer for the second tuple field, and
15351546
/// `Some` is the identity function (with a non-null reference).
1547+
///
1548+
/// Other variants that are not `untagged_variant` and that are outside the `niche_variants`
1549+
/// range cannot be represented; they must be uninhabited.
15361550
Niche {
15371551
untagged_variant: VariantIdx,
1552+
/// This range *may* contain `untagged_variant`; that is then just a "dead value" and
1553+
/// not used to encode anything.
15381554
niche_variants: RangeInclusive<VariantIdx>,
1555+
/// This is inbounds of the type of the niche field
1556+
/// (not sign-extended, i.e., all bits beyond the niche field size are 0).
15391557
niche_start: u128,
15401558
},
15411559
}

compiler/rustc_const_eval/src/const_eval/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use rustc_abi::VariantIdx;
44
use rustc_middle::query::{Key, TyCtxtAt};
5+
use rustc_middle::ty::layout::LayoutOf;
56
use rustc_middle::ty::{self, Ty, TyCtxt};
67
use rustc_middle::{bug, mir};
78
use tracing::instrument;
@@ -85,5 +86,6 @@ pub fn tag_for_variant_provider<'tcx>(
8586
crate::const_eval::DummyMachine,
8687
);
8788

88-
ecx.tag_for_variant(ty, variant_index).unwrap().map(|(tag, _tag_field)| tag)
89+
let layout = ecx.layout_of(ty).unwrap();
90+
ecx.tag_for_variant(layout, variant_index).unwrap().map(|(tag, _tag_field)| tag)
8991
}

compiler/rustc_const_eval/src/interpret/discriminant.rs

+25-20
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Functions for reading and writing discriminants of multi-variant layouts (enums and coroutines).
22
33
use rustc_abi::{self as abi, TagEncoding, VariantIdx, Variants};
4-
use rustc_middle::ty::layout::{LayoutOf, PrimitiveExt};
4+
use rustc_middle::ty::layout::{LayoutOf, PrimitiveExt, TyAndLayout};
55
use rustc_middle::ty::{self, CoroutineArgsExt, ScalarInt, Ty};
66
use rustc_middle::{mir, span_bug};
77
use tracing::{instrument, trace};
@@ -21,17 +21,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
2121
variant_index: VariantIdx,
2222
dest: &impl Writeable<'tcx, M::Provenance>,
2323
) -> InterpResult<'tcx> {
24-
// Layout computation excludes uninhabited variants from consideration
25-
// therefore there's no way to represent those variants in the given layout.
26-
// Essentially, uninhabited variants do not have a tag that corresponds to their
27-
// discriminant, so we cannot do anything here.
28-
// When evaluating we will always error before even getting here, but ConstProp 'executes'
29-
// dead code, so we cannot ICE here.
30-
if dest.layout().for_variant(self, variant_index).is_uninhabited() {
31-
throw_ub!(UninhabitedEnumVariantWritten(variant_index))
32-
}
33-
34-
match self.tag_for_variant(dest.layout().ty, variant_index)? {
24+
match self.tag_for_variant(dest.layout(), variant_index)? {
3525
Some((tag, tag_field)) => {
3626
// No need to validate that the discriminant here because the
3727
// `TyAndLayout::for_variant()` call earlier already checks the
@@ -80,7 +70,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
8070
if ty.is_enum() {
8171
// Hilariously, `Single` is used even for 0-variant enums.
8272
// (See https://github.com/rust-lang/rust/issues/89765).
83-
if matches!(ty.kind(), ty::Adt(def, ..) if def.variants().is_empty()) {
73+
if ty.ty_adt_def().unwrap().variants().is_empty() {
8474
throw_ub!(UninhabitedEnumVariantRead(index))
8575
}
8676
// For consistency with `write_discriminant`, and to make sure that
@@ -188,6 +178,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
188178
let variants =
189179
ty.ty_adt_def().expect("tagged layout for non adt").variants();
190180
assert!(variant_index < variants.next_index());
181+
if variant_index == untagged_variant {
182+
// The untagged variant can be in the niche range, but even then it
183+
// is not a valid encoding.
184+
throw_ub!(InvalidTag(Scalar::from_uint(tag_bits, tag_layout.size)))
185+
}
191186
variant_index
192187
} else {
193188
untagged_variant
@@ -236,10 +231,18 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
236231
/// given field index.
237232
pub(crate) fn tag_for_variant(
238233
&self,
239-
ty: Ty<'tcx>,
234+
layout: TyAndLayout<'tcx>,
240235
variant_index: VariantIdx,
241236
) -> InterpResult<'tcx, Option<(ScalarInt, usize)>> {
242-
match self.layout_of(ty)?.variants {
237+
// Layout computation excludes uninhabited variants from consideration.
238+
// Therefore, there's no way to represent those variants in the given layout.
239+
// Essentially, uninhabited variants do not have a tag that corresponds to their
240+
// discriminant, so we have to bail out here.
241+
if layout.for_variant(self, variant_index).is_uninhabited() {
242+
throw_ub!(UninhabitedEnumVariantWritten(variant_index))
243+
}
244+
245+
match layout.variants {
243246
abi::Variants::Single { .. } => {
244247
// The tag of a `Single` enum is like the tag of the niched
245248
// variant: there's no tag as the discriminant is encoded
@@ -260,7 +263,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
260263
// raw discriminants for enums are isize or bigger during
261264
// their computation, but the in-memory tag is the smallest possible
262265
// representation
263-
let discr = self.discriminant_for_variant(ty, variant_index)?;
266+
let discr = self.discriminant_for_variant(layout.ty, variant_index)?;
264267
let discr_size = discr.layout.size;
265268
let discr_val = discr.to_scalar().to_bits(discr_size)?;
266269
let tag_size = tag_layout.size(self);
@@ -286,11 +289,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
286289
..
287290
} => {
288291
assert!(variant_index != untagged_variant);
292+
// We checked that this variant is inhabited, so it must be in the niche range.
293+
assert!(
294+
niche_variants.contains(&variant_index),
295+
"invalid variant index for this enum"
296+
);
289297
let variants_start = niche_variants.start().as_u32();
290-
let variant_index_relative = variant_index
291-
.as_u32()
292-
.checked_sub(variants_start)
293-
.expect("overflow computing relative variant idx");
298+
let variant_index_relative = variant_index.as_u32().strict_sub(variants_start);
294299
// We need to use machine arithmetic when taking into account `niche_start`:
295300
// tag_val = variant_index_relative + niche_start_val
296301
let tag_layout = self.layout_of(tag_layout.primitive().to_int_ty(*self.tcx))?;

compiler/rustc_const_eval/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#![feature(never_type)]
1111
#![feature(rustdoc_internals)]
1212
#![feature(slice_ptr_get)]
13+
#![feature(strict_overflow_ops)]
1314
#![feature(trait_alias)]
1415
#![feature(try_blocks)]
1516
#![feature(unqualified_local_imports)]

compiler/rustc_middle/src/query/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,8 @@ rustc_queries! {
10811081
}
10821082

10831083
/// Computes the tag (if any) for a given type and variant.
1084+
/// `None` means that the variant doesn't need a tag (because it is niched).
1085+
/// Will panic for uninhabited variants.
10841086
query tag_for_variant(
10851087
key: (Ty<'tcx>, abi::VariantIdx)
10861088
) -> Option<ty::ScalarInt> {

compiler/rustc_transmute/src/layout/tree.rs

+27-30
Original file line numberDiff line numberDiff line change
@@ -319,38 +319,35 @@ pub(crate) mod rustc {
319319
) -> Result<Self, Err> {
320320
assert!(def.is_enum());
321321

322-
// Computes the variant of a given index.
323-
let layout_of_variant = |index, encoding: Option<TagEncoding<VariantIdx>>| {
324-
let tag = cx.tcx().tag_for_variant((cx.tcx().erase_regions(ty), index));
325-
let variant_def = Def::Variant(def.variant(index));
326-
let variant_layout = ty_variant(cx, (ty, layout), index);
327-
Self::from_variant(
328-
variant_def,
329-
tag.map(|tag| (tag, index, encoding.unwrap())),
330-
(ty, variant_layout),
331-
layout.size,
332-
cx,
333-
)
334-
};
322+
// Computes the layout of a variant.
323+
let layout_of_variant =
324+
|index, encoding: Option<TagEncoding<VariantIdx>>| -> Result<Self, Err> {
325+
let variant_layout = ty_variant(cx, (ty, layout), index);
326+
if variant_layout.is_uninhabited() {
327+
return Ok(Self::uninhabited());
328+
}
329+
let tag = cx.tcx().tag_for_variant((cx.tcx().erase_regions(ty), index));
330+
let variant_def = Def::Variant(def.variant(index));
331+
Self::from_variant(
332+
variant_def,
333+
tag.map(|tag| (tag, index, encoding.unwrap())),
334+
(ty, variant_layout),
335+
layout.size,
336+
cx,
337+
)
338+
};
335339

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

372-
return Ok(Self::def(Def::Adt(def)).then(variants));
369+
Ok(Self::def(Def::Adt(def)).then(variants))
373370
}
374371
}
375372
}

compiler/rustc_ty_utils/src/layout.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ fn layout_of<'tcx>(
8181
record_layout_for_printing(&cx, layout);
8282
}
8383

84-
invariant::partially_check_layout(&cx, &layout);
84+
invariant::layout_sanity_check(&cx, &layout);
8585

8686
Ok(layout)
8787
}

compiler/rustc_ty_utils/src/layout/invariant.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use std::assert_matches::assert_matches;
22

3-
use rustc_abi::{BackendRepr, FieldsShape, Scalar, Size, Variants};
3+
use rustc_abi::{BackendRepr, FieldsShape, Scalar, Size, TagEncoding, Variants};
44
use rustc_middle::bug;
55
use rustc_middle::ty::layout::{HasTyCtxt, LayoutCx, TyAndLayout};
66

77
/// Enforce some basic invariants on layouts.
8-
pub(super) fn partially_check_layout<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayout<'tcx>) {
8+
pub(super) fn layout_sanity_check<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLayout<'tcx>) {
99
let tcx = cx.tcx();
1010

1111
// Type-level uninhabitedness should always imply ABI uninhabitedness.
@@ -241,7 +241,17 @@ pub(super) fn partially_check_layout<'tcx>(cx: &LayoutCx<'tcx>, layout: &TyAndLa
241241

242242
check_layout_abi(cx, layout);
243243

244-
if let Variants::Multiple { variants, .. } = &layout.variants {
244+
if let Variants::Multiple { variants, tag, tag_encoding, .. } = &layout.variants {
245+
if let TagEncoding::Niche { niche_start, untagged_variant, niche_variants } = tag_encoding {
246+
let niche_size = tag.size(cx);
247+
assert!(*niche_start <= niche_size.unsigned_int_max());
248+
for (idx, variant) in variants.iter_enumerated() {
249+
// Ensure all inhabited variants are accounted for.
250+
if !variant.is_uninhabited() {
251+
assert!(idx == *untagged_variant || niche_variants.contains(&idx));
252+
}
253+
}
254+
}
245255
for variant in variants.iter() {
246256
// No nested "multiple".
247257
assert_matches!(variant.variants, Variants::Single { .. });
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Validity makes this fail at the wrong place.
2+
//@compile-flags: -Zmiri-disable-validation
3+
use std::mem;
4+
5+
// This enum has untagged variant idx 1, with niche_variants being 0..=2
6+
// and niche_start being 2.
7+
// That means the untagged variants is in the niche variant range!
8+
// However, using the corresponding value (2+1 = 3) is not a valid encoding of this variant.
9+
#[derive(Copy, Clone, PartialEq)]
10+
enum Foo {
11+
Var1,
12+
Var2(bool),
13+
Var3,
14+
}
15+
16+
fn main() {
17+
unsafe {
18+
assert!(Foo::Var2(false) == mem::transmute(0u8));
19+
assert!(Foo::Var2(true) == mem::transmute(1u8));
20+
assert!(Foo::Var1 == mem::transmute(2u8));
21+
assert!(Foo::Var3 == mem::transmute(4u8));
22+
23+
let invalid: Foo = mem::transmute(3u8);
24+
assert!(matches!(invalid, Foo::Var2(_)));
25+
//~^ ERROR: invalid tag
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: Undefined Behavior: enum value has invalid tag: 0x03
2+
--> tests/fail/enum-untagged-variant-invalid-encoding.rs:LL:CC
3+
|
4+
LL | assert!(matches!(invalid, Foo::Var2(_)));
5+
| ^^^^^^^ enum value has invalid tag: 0x03
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `main` at tests/fail/enum-untagged-variant-invalid-encoding.rs:LL:CC
11+
12+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
13+
14+
error: aborting due to 1 previous error
15+

tests/crashes/126267.rs

-30
This file was deleted.

0 commit comments

Comments
 (0)