Skip to content

Commit 8d2faa2

Browse files
authored
Rollup merge of #102281 - RalfJung:invalid-enums, r=cjgillot
make invalid_value lint a bit smarter around enums Fixes #102043
2 parents 4cef648 + 67fd09d commit 8d2faa2

9 files changed

+343
-163
lines changed

compiler/rustc_lint/src/builtin.rs

+117-51
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ use rustc_middle::lint::in_external_macro;
4646
use rustc_middle::ty::layout::{LayoutError, LayoutOf};
4747
use rustc_middle::ty::print::with_no_trimmed_paths;
4848
use rustc_middle::ty::subst::GenericArgKind;
49-
use rustc_middle::ty::Instance;
50-
use rustc_middle::ty::{self, Ty, TyCtxt};
49+
use rustc_middle::ty::{self, Instance, Ty, TyCtxt, VariantDef};
5150
use rustc_session::lint::{BuiltinLintDiagnostics, FutureIncompatibilityReason};
5251
use rustc_span::edition::Edition;
5352
use rustc_span::source_map::Spanned;
@@ -2425,12 +2424,63 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
24252424
None
24262425
}
24272426

2428-
/// Test if this enum has several actually "existing" variants.
2429-
/// Zero-sized uninhabited variants do not always have a tag assigned and thus do not "exist".
2430-
fn is_multi_variant<'tcx>(adt: ty::AdtDef<'tcx>) -> bool {
2431-
// As an approximation, we only count dataless variants. Those are definitely inhabited.
2432-
let existing_variants = adt.variants().iter().filter(|v| v.fields.is_empty()).count();
2433-
existing_variants > 1
2427+
/// Determines whether the given type is inhabited. `None` means that we don't know.
2428+
fn ty_inhabited<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<bool> {
2429+
use rustc_type_ir::sty::TyKind::*;
2430+
if !cx.tcx.type_uninhabited_from(cx.param_env.and(ty)).is_empty() {
2431+
// This is definitely uninhabited from some module.
2432+
return Some(false);
2433+
}
2434+
match ty.kind() {
2435+
Never => Some(false),
2436+
Int(_) | Uint(_) | Float(_) | Bool | Char | RawPtr(_) => Some(true),
2437+
// Fallback for more complicated types. (Note that `&!` might be considered
2438+
// uninhabited so references are "complicated", too.)
2439+
_ => None,
2440+
}
2441+
}
2442+
/// Determines whether a product type formed from a list of types is inhabited.
2443+
fn tys_inhabited<'tcx>(
2444+
cx: &LateContext<'tcx>,
2445+
tys: impl Iterator<Item = Ty<'tcx>>,
2446+
) -> Option<bool> {
2447+
let mut definitely_inhabited = true; // with no fields, we are definitely inhabited.
2448+
for ty in tys {
2449+
match ty_inhabited(cx, ty) {
2450+
// If any type is uninhabited, the product is uninhabited.
2451+
Some(false) => return Some(false),
2452+
// Otherwise go searching for a `None`.
2453+
None => {
2454+
// We don't know.
2455+
definitely_inhabited = false;
2456+
}
2457+
Some(true) => {}
2458+
}
2459+
}
2460+
if definitely_inhabited { Some(true) } else { None }
2461+
}
2462+
2463+
fn variant_find_init_error<'tcx>(
2464+
cx: &LateContext<'tcx>,
2465+
variant: &VariantDef,
2466+
substs: ty::SubstsRef<'tcx>,
2467+
descr: &str,
2468+
init: InitKind,
2469+
) -> Option<InitError> {
2470+
variant.fields.iter().find_map(|field| {
2471+
ty_find_init_error(cx, field.ty(cx.tcx, substs), init).map(|(mut msg, span)| {
2472+
if span.is_none() {
2473+
// Point to this field, should be helpful for figuring
2474+
// out where the source of the error is.
2475+
let span = cx.tcx.def_span(field.did);
2476+
write!(&mut msg, " (in this {descr})").unwrap();
2477+
(msg, Some(span))
2478+
} else {
2479+
// Just forward.
2480+
(msg, span)
2481+
}
2482+
})
2483+
})
24342484
}
24352485

24362486
/// Return `Some` only if we are sure this type does *not*
@@ -2468,14 +2518,15 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
24682518
RawPtr(_) if init == InitKind::Uninit => {
24692519
Some(("raw pointers must not be uninitialized".to_string(), None))
24702520
}
2471-
// Recurse and checks for some compound types.
2521+
// Recurse and checks for some compound types. (but not unions)
24722522
Adt(adt_def, substs) if !adt_def.is_union() => {
24732523
// First check if this ADT has a layout attribute (like `NonNull` and friends).
24742524
use std::ops::Bound;
24752525
match cx.tcx.layout_scalar_valid_range(adt_def.did()) {
24762526
// We exploit here that `layout_scalar_valid_range` will never
24772527
// return `Bound::Excluded`. (And we have tests checking that we
24782528
// handle the attribute correctly.)
2529+
// We don't add a span since users cannot declare such types anyway.
24792530
(Bound::Included(lo), _) if lo > 0 => {
24802531
return Some((format!("`{}` must be non-null", ty), None));
24812532
}
@@ -2492,50 +2543,65 @@ impl<'tcx> LateLintPass<'tcx> for InvalidValue {
24922543
}
24932544
_ => {}
24942545
}
2495-
// Now, recurse.
2496-
match adt_def.variants().len() {
2497-
0 => Some(("enums with no variants have no valid value".to_string(), None)),
2498-
1 => {
2499-
// Struct, or enum with exactly one variant.
2500-
// Proceed recursively, check all fields.
2501-
let variant = &adt_def.variant(VariantIdx::from_u32(0));
2502-
variant.fields.iter().find_map(|field| {
2503-
ty_find_init_error(cx, field.ty(cx.tcx, substs), init).map(
2504-
|(mut msg, span)| {
2505-
if span.is_none() {
2506-
// Point to this field, should be helpful for figuring
2507-
// out where the source of the error is.
2508-
let span = cx.tcx.def_span(field.did);
2509-
write!(
2510-
&mut msg,
2511-
" (in this {} field)",
2512-
adt_def.descr()
2513-
)
2514-
.unwrap();
2515-
(msg, Some(span))
2516-
} else {
2517-
// Just forward.
2518-
(msg, span)
2519-
}
2520-
},
2521-
)
2522-
})
2523-
}
2524-
// Multi-variant enum.
2525-
_ => {
2526-
if init == InitKind::Uninit && is_multi_variant(*adt_def) {
2527-
let span = cx.tcx.def_span(adt_def.did());
2528-
Some((
2529-
"enums have to be initialized to a variant".to_string(),
2530-
Some(span),
2531-
))
2532-
} else {
2533-
// In principle, for zero-initialization we could figure out which variant corresponds
2534-
// to tag 0, and check that... but for now we just accept all zero-initializations.
2535-
None
2536-
}
2546+
// Handle structs.
2547+
if adt_def.is_struct() {
2548+
return variant_find_init_error(
2549+
cx,
2550+
adt_def.non_enum_variant(),
2551+
substs,
2552+
"struct field",
2553+
init,
2554+
);
2555+
}
2556+
// And now, enums.
2557+
let span = cx.tcx.def_span(adt_def.did());
2558+
let mut potential_variants = adt_def.variants().iter().filter_map(|variant| {
2559+
let inhabited = tys_inhabited(
2560+
cx,
2561+
variant.fields.iter().map(|field| field.ty(cx.tcx, substs)),
2562+
);
2563+
let definitely_inhabited = match inhabited {
2564+
// Entirely skip uninhbaited variants.
2565+
Some(false) => return None,
2566+
// Forward the others, but remember which ones are definitely inhabited.
2567+
Some(true) => true,
2568+
None => false,
2569+
};
2570+
Some((variant, definitely_inhabited))
2571+
});
2572+
let Some(first_variant) = potential_variants.next() else {
2573+
return Some(("enums with no inhabited variants have no valid value".to_string(), Some(span)));
2574+
};
2575+
// So we have at least one potentially inhabited variant. Might we have two?
2576+
let Some(second_variant) = potential_variants.next() else {
2577+
// There is only one potentially inhabited variant. So we can recursively check that variant!
2578+
return variant_find_init_error(
2579+
cx,
2580+
&first_variant.0,
2581+
substs,
2582+
"field of the only potentially inhabited enum variant",
2583+
init,
2584+
);
2585+
};
2586+
// So we have at least two potentially inhabited variants.
2587+
// If we can prove that we have at least two *definitely* inhabited variants,
2588+
// then we have a tag and hence leaving this uninit is definitely disallowed.
2589+
// (Leaving it zeroed could be okay, depending on which variant is encoded as zero tag.)
2590+
if init == InitKind::Uninit {
2591+
let definitely_inhabited = (first_variant.1 as usize)
2592+
+ (second_variant.1 as usize)
2593+
+ potential_variants
2594+
.filter(|(_variant, definitely_inhabited)| *definitely_inhabited)
2595+
.count();
2596+
if definitely_inhabited > 1 {
2597+
return Some((
2598+
"enums with multiple inhabited variants have to be initialized to a variant".to_string(),
2599+
Some(span),
2600+
));
25372601
}
25382602
}
2603+
// We couldn't find anything wrong here.
2604+
None
25392605
}
25402606
Tuple(..) => {
25412607
// Proceed recursively, check all fields.

src/test/ui/consts/const-eval/ub-enum.32bit.stderr

+18-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0080]: it is undefined behavior to use this value
2-
--> $DIR/ub-enum.rs:23:1
2+
--> $DIR/ub-enum.rs:24:1
33
|
44
LL | const BAD_ENUM: Enum = unsafe { mem::transmute(1usize) };
55
| ^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .<enum-tag>: encountered 0x00000001, but expected a valid enum tag
@@ -10,7 +10,7 @@ LL | const BAD_ENUM: Enum = unsafe { mem::transmute(1usize) };
1010
}
1111

1212
error: any use of this value will cause an error
13-
--> $DIR/ub-enum.rs:26:1
13+
--> $DIR/ub-enum.rs:27:1
1414
|
1515
LL | const BAD_ENUM_PTR: Enum = unsafe { mem::transmute(&1) };
1616
| ^^^^^^^^^^^^^^^^^^^^^^^^ unable to turn pointer into raw bytes
@@ -22,7 +22,7 @@ LL | const BAD_ENUM_PTR: Enum = unsafe { mem::transmute(&1) };
2222
= help: the absolute address of a pointer is not known at compile-time, so such operations are not supported
2323

2424
error: any use of this value will cause an error
25-
--> $DIR/ub-enum.rs:30:1
25+
--> $DIR/ub-enum.rs:31:1
2626
|
2727
LL | const BAD_ENUM_WRAPPED: Wrap<Enum> = unsafe { mem::transmute(&1) };
2828
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unable to turn pointer into raw bytes
@@ -33,7 +33,7 @@ LL | const BAD_ENUM_WRAPPED: Wrap<Enum> = unsafe { mem::transmute(&1) };
3333
= help: the absolute address of a pointer is not known at compile-time, so such operations are not supported
3434

3535
error[E0080]: it is undefined behavior to use this value
36-
--> $DIR/ub-enum.rs:43:1
36+
--> $DIR/ub-enum.rs:44:1
3737
|
3838
LL | const BAD_ENUM2: Enum2 = unsafe { mem::transmute(0usize) };
3939
| ^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .<enum-tag>: encountered 0x00000000, but expected a valid enum tag
@@ -44,7 +44,7 @@ LL | const BAD_ENUM2: Enum2 = unsafe { mem::transmute(0usize) };
4444
}
4545

4646
error: any use of this value will cause an error
47-
--> $DIR/ub-enum.rs:45:1
47+
--> $DIR/ub-enum.rs:46:1
4848
|
4949
LL | const BAD_ENUM2_PTR: Enum2 = unsafe { mem::transmute(&0) };
5050
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ unable to turn pointer into raw bytes
@@ -55,7 +55,7 @@ LL | const BAD_ENUM2_PTR: Enum2 = unsafe { mem::transmute(&0) };
5555
= help: the absolute address of a pointer is not known at compile-time, so such operations are not supported
5656

5757
error: any use of this value will cause an error
58-
--> $DIR/ub-enum.rs:49:1
58+
--> $DIR/ub-enum.rs:50:1
5959
|
6060
LL | const BAD_ENUM2_WRAPPED: Wrap<Enum2> = unsafe { mem::transmute(&0) };
6161
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unable to turn pointer into raw bytes
@@ -66,13 +66,13 @@ LL | const BAD_ENUM2_WRAPPED: Wrap<Enum2> = unsafe { mem::transmute(&0) };
6666
= help: the absolute address of a pointer is not known at compile-time, so such operations are not supported
6767

6868
error[E0080]: evaluation of constant value failed
69-
--> $DIR/ub-enum.rs:59:42
69+
--> $DIR/ub-enum.rs:60:42
7070
|
7171
LL | const BAD_ENUM2_UNDEF : Enum2 = unsafe { MaybeUninit { uninit: () }.init };
7272
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
7373

7474
error: any use of this value will cause an error
75-
--> $DIR/ub-enum.rs:64:1
75+
--> $DIR/ub-enum.rs:65:1
7676
|
7777
LL | const BAD_ENUM2_OPTION_PTR: Option<Enum2> = unsafe { mem::transmute(&0) };
7878
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unable to turn pointer into raw bytes
@@ -83,7 +83,7 @@ LL | const BAD_ENUM2_OPTION_PTR: Option<Enum2> = unsafe { mem::transmute(&0) };
8383
= help: the absolute address of a pointer is not known at compile-time, so such operations are not supported
8484

8585
error[E0080]: it is undefined behavior to use this value
86-
--> $DIR/ub-enum.rs:82:1
86+
--> $DIR/ub-enum.rs:83:1
8787
|
8888
LL | const BAD_UNINHABITED_VARIANT1: UninhDiscriminant = unsafe { mem::transmute(1u8) };
8989
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .<enum-variant(B)>.0: encountered a value of the never type `!`
@@ -94,7 +94,7 @@ LL | const BAD_UNINHABITED_VARIANT1: UninhDiscriminant = unsafe { mem::transmute
9494
}
9595

9696
error[E0080]: it is undefined behavior to use this value
97-
--> $DIR/ub-enum.rs:84:1
97+
--> $DIR/ub-enum.rs:85:1
9898
|
9999
LL | const BAD_UNINHABITED_VARIANT2: UninhDiscriminant = unsafe { mem::transmute(3u8) };
100100
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .<enum-variant(D)>.0: encountered a value of uninhabited type Never
@@ -105,7 +105,7 @@ LL | const BAD_UNINHABITED_VARIANT2: UninhDiscriminant = unsafe { mem::transmute
105105
}
106106

107107
error[E0080]: it is undefined behavior to use this value
108-
--> $DIR/ub-enum.rs:92:1
108+
--> $DIR/ub-enum.rs:93:1
109109
|
110110
LL | const BAD_OPTION_CHAR: Option<(char, char)> = Some(('x', unsafe { mem::transmute(!0u32) }));
111111
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .<enum-variant(Some)>.0.1: encountered 0xffffffff, but expected a valid unicode scalar value (in `0..=0x10FFFF` but not in `0xD800..=0xDFFF`)
@@ -116,13 +116,13 @@ LL | const BAD_OPTION_CHAR: Option<(char, char)> = Some(('x', unsafe { mem::tran
116116
}
117117

118118
error[E0080]: evaluation of constant value failed
119-
--> $DIR/ub-enum.rs:97:77
119+
--> $DIR/ub-enum.rs:98:77
120120
|
121121
LL | const BAD_UNINHABITED_WITH_DATA1: Result<(i32, Never), (i32, !)> = unsafe { mem::transmute(0u64) };
122122
| ^^^^^^^^^^^^^^^^^^^^ transmuting to uninhabited type
123123

124124
error[E0080]: evaluation of constant value failed
125-
--> $DIR/ub-enum.rs:99:77
125+
--> $DIR/ub-enum.rs:100:77
126126
|
127127
LL | const BAD_UNINHABITED_WITH_DATA2: Result<(i32, !), (i32, Never)> = unsafe { mem::transmute(0u64) };
128128
| ^^^^^^^^^^^^^^^^^^^^ transmuting to uninhabited type
@@ -132,7 +132,7 @@ error: aborting due to 13 previous errors
132132
For more information about this error, try `rustc --explain E0080`.
133133
Future incompatibility report: Future breakage diagnostic:
134134
error: any use of this value will cause an error
135-
--> $DIR/ub-enum.rs:26:1
135+
--> $DIR/ub-enum.rs:27:1
136136
|
137137
LL | const BAD_ENUM_PTR: Enum = unsafe { mem::transmute(&1) };
138138
| ^^^^^^^^^^^^^^^^^^^^^^^^ unable to turn pointer into raw bytes
@@ -145,7 +145,7 @@ LL | const BAD_ENUM_PTR: Enum = unsafe { mem::transmute(&1) };
145145

146146
Future breakage diagnostic:
147147
error: any use of this value will cause an error
148-
--> $DIR/ub-enum.rs:30:1
148+
--> $DIR/ub-enum.rs:31:1
149149
|
150150
LL | const BAD_ENUM_WRAPPED: Wrap<Enum> = unsafe { mem::transmute(&1) };
151151
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unable to turn pointer into raw bytes
@@ -158,7 +158,7 @@ LL | const BAD_ENUM_WRAPPED: Wrap<Enum> = unsafe { mem::transmute(&1) };
158158

159159
Future breakage diagnostic:
160160
error: any use of this value will cause an error
161-
--> $DIR/ub-enum.rs:45:1
161+
--> $DIR/ub-enum.rs:46:1
162162
|
163163
LL | const BAD_ENUM2_PTR: Enum2 = unsafe { mem::transmute(&0) };
164164
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ unable to turn pointer into raw bytes
@@ -171,7 +171,7 @@ LL | const BAD_ENUM2_PTR: Enum2 = unsafe { mem::transmute(&0) };
171171

172172
Future breakage diagnostic:
173173
error: any use of this value will cause an error
174-
--> $DIR/ub-enum.rs:49:1
174+
--> $DIR/ub-enum.rs:50:1
175175
|
176176
LL | const BAD_ENUM2_WRAPPED: Wrap<Enum2> = unsafe { mem::transmute(&0) };
177177
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unable to turn pointer into raw bytes
@@ -184,7 +184,7 @@ LL | const BAD_ENUM2_WRAPPED: Wrap<Enum2> = unsafe { mem::transmute(&0) };
184184

185185
Future breakage diagnostic:
186186
error: any use of this value will cause an error
187-
--> $DIR/ub-enum.rs:64:1
187+
--> $DIR/ub-enum.rs:65:1
188188
|
189189
LL | const BAD_ENUM2_OPTION_PTR: Option<Enum2> = unsafe { mem::transmute(&0) };
190190
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unable to turn pointer into raw bytes

0 commit comments

Comments
 (0)