Skip to content

Commit 8587f11

Browse files
committed
Lint against empty match on not-known-valid place
1 parent 725702e commit 8587f11

11 files changed

+486
-41
lines changed

compiler/rustc_lint_defs/src/builtin.rs

+71
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ declare_lint_pass! {
3939
DUPLICATE_MACRO_ATTRIBUTES,
4040
ELIDED_LIFETIMES_IN_ASSOCIATED_CONSTANT,
4141
ELIDED_LIFETIMES_IN_PATHS,
42+
EMPTY_MATCH_ON_UNSAFE_PLACE,
4243
EXPORTED_PRIVATE_DEPENDENCIES,
4344
FFI_UNWIND_CALLS,
4445
FORBIDDEN_LINT_GROUPS,
@@ -4018,6 +4019,76 @@ declare_lint! {
40184019
@feature_gate = sym::non_exhaustive_omitted_patterns_lint;
40194020
}
40204021

4022+
declare_lint! {
4023+
/// The `empty_match_on_unsafe_place` lint detects uses of `match ... {}` on an empty type where
4024+
/// the matched place could contain invalid data in a well-defined program. These matches are
4025+
/// considered exhaustive for backwards-compatibility, but they shouldn't be since a `_` arm
4026+
/// would be reachable.
4027+
///
4028+
/// This will become an error in the future.
4029+
///
4030+
/// ### Example
4031+
///
4032+
/// ```compile_fail
4033+
/// #![feature(min_exhaustive_patterns)]
4034+
/// enum Void {}
4035+
/// let ptr: *const Void = ...;
4036+
/// unsafe {
4037+
/// match *ptr {}
4038+
/// }
4039+
/// ```
4040+
///
4041+
/// This will produce:
4042+
///
4043+
/// ```text
4044+
/// warning: empty match on potentially-invalid data
4045+
/// --> $DIR/empty-types.rs:157:9
4046+
/// |
4047+
/// LL | match *ptr {}
4048+
/// | ^^^^^^^^^^^^^
4049+
/// |
4050+
/// note: this place can hold invalid data, which would make the match reachable
4051+
/// --> $DIR/empty-types.rs:157:15
4052+
/// |
4053+
/// LL | match *ptr {}
4054+
/// | ^^^^
4055+
/// = note: `#[warn(empty_match_on_unsafe_place)]` on by default
4056+
/// help: consider forcing a read of the value
4057+
/// |
4058+
/// LL | match { *ptr } {}
4059+
/// | + +
4060+
/// ```
4061+
///
4062+
/// ### Explanation
4063+
///
4064+
/// Some place expressions (namely pointer dereferences, union field accesses, and
4065+
/// (conservatively) reference dereferences) can hold invalid data without causing UB. For
4066+
/// example, the following is a well-defined program that prints "reachable!".
4067+
///
4068+
/// ```rust
4069+
/// #[derive(Copy, Clone)]
4070+
/// enum Void {}
4071+
/// union Uninit<T: Copy> {
4072+
/// value: T,
4073+
/// uninit: (),
4074+
/// }
4075+
/// unsafe {
4076+
/// let x: Uninit<Void> = Uninit { uninit: () };
4077+
/// match x.value {
4078+
/// _ => println!("reachable!"),
4079+
/// }
4080+
/// }
4081+
/// ```
4082+
///
4083+
/// Therefore when the matched place can hold invalid data, a match with no arm should not be
4084+
/// considered exhaustive. For backwards-compatibility we consider them exhaustive but warn with
4085+
/// this lint. It will become an error in the future.
4086+
pub EMPTY_MATCH_ON_UNSAFE_PLACE,
4087+
Warn,
4088+
"warn about empty matches on a place with potentially-invalid data",
4089+
@feature_gate = sym::min_exhaustive_patterns;
4090+
}
4091+
40214092
declare_lint! {
40224093
/// The `text_direction_codepoint_in_comment` lint detects Unicode codepoints in comments that
40234094
/// change the visual representation of text on screen in a way that does not correspond to

compiler/rustc_pattern_analysis/messages.ftl

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
pattern_analysis_empty_match_on_unsafe_place =
2+
empty match on potentially-invalid data
3+
.note = this place can hold invalid data, which would make the match reachable
4+
5+
pattern_analysis_empty_match_on_unsafe_place_wrap_suggestion =
6+
consider forcing a read of the value
7+
18
pattern_analysis_non_exhaustive_omitted_pattern = some variants are not matched explicitly
29
.help = ensure that all variants are matched explicitly by adding the suggested match arms
310
.note = the matched value is of type `{$scrut_ty}` and the `non_exhaustive_omitted_patterns` attribute was found

compiler/rustc_pattern_analysis/src/errors.rs

+21
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,27 @@ impl<'tcx> Uncovered<'tcx> {
4343
}
4444
}
4545

46+
#[derive(LintDiagnostic)]
47+
#[diag(pattern_analysis_empty_match_on_unsafe_place)]
48+
pub struct EmptyMatchOnUnsafePlace {
49+
#[note]
50+
pub scrut_span: Span,
51+
#[subdiagnostic]
52+
pub suggestion: EmptyMatchOnUnsafePlaceWrapSuggestion,
53+
}
54+
55+
#[derive(Subdiagnostic)]
56+
#[multipart_suggestion(
57+
pattern_analysis_empty_match_on_unsafe_place_wrap_suggestion,
58+
applicability = "maybe-incorrect"
59+
)]
60+
pub struct EmptyMatchOnUnsafePlaceWrapSuggestion {
61+
#[suggestion_part(code = "{{ ")]
62+
pub scrut_start: Span,
63+
#[suggestion_part(code = " }}")]
64+
pub scrut_end: Span,
65+
}
66+
4667
#[derive(LintDiagnostic)]
4768
#[diag(pattern_analysis_overlapping_range_endpoints)]
4869
#[note]

compiler/rustc_pattern_analysis/src/lib.rs

+43-14
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,7 @@ use rustc_index::Idx;
2626
use rustc_middle::ty::Ty;
2727

2828
use crate::constructor::{Constructor, ConstructorSet};
29-
#[cfg(feature = "rustc")]
30-
use crate::lints::{
31-
lint_nonexhaustive_missing_variants, lint_overlapping_range_endpoints, PatternColumn,
32-
};
3329
use crate::pat::DeconstructedPat;
34-
#[cfg(feature = "rustc")]
35-
use crate::rustc::RustcMatchCheckCtxt;
36-
#[cfg(feature = "rustc")]
37-
use crate::usefulness::{compute_match_usefulness, ValidityConstraint};
3830

3931
// It's not possible to only enable the `typed_arena` dependency when the `rustc` feature is off, so
4032
// we use another feature instead. The crate won't compile if one of these isn't enabled.
@@ -110,26 +102,63 @@ impl<'p, Cx: TypeCx> Copy for MatchArm<'p, Cx> {}
110102
/// useful, and runs some lints.
111103
#[cfg(feature = "rustc")]
112104
pub fn analyze_match<'p, 'tcx>(
113-
tycx: &RustcMatchCheckCtxt<'p, 'tcx>,
105+
tycx: &rustc::RustcMatchCheckCtxt<'p, 'tcx>,
114106
arms: &[rustc::MatchArm<'p, 'tcx>],
115107
scrut_ty: Ty<'tcx>,
116108
) -> rustc::UsefulnessReport<'p, 'tcx> {
109+
use rustc_middle::ty;
110+
use rustc_session::lint;
111+
117112
// Arena to store the extra wildcards we construct during analysis.
118113
let wildcard_arena = tycx.pattern_arena;
119-
let scrut_validity = ValidityConstraint::from_bool(tycx.known_valid_scrutinee);
114+
let scrut_validity = usefulness::ValidityConstraint::from_bool(tycx.known_valid_scrutinee);
120115
let cx = MatchCtxt { tycx, wildcard_arena };
121116

122-
let report = compute_match_usefulness(cx, arms, scrut_ty, scrut_validity);
117+
if !tycx.known_valid_scrutinee && arms.iter().all(|arm| arm.has_guard) {
118+
let is_directly_empty = match scrut_ty.kind() {
119+
ty::Adt(def, ..) => {
120+
def.is_enum()
121+
&& def.variants().is_empty()
122+
&& !tycx.is_foreign_non_exhaustive_enum(scrut_ty)
123+
}
124+
ty::Never => true,
125+
_ => false,
126+
};
127+
if is_directly_empty {
128+
if tycx.tcx.features().min_exhaustive_patterns {
129+
tycx.tcx.emit_spanned_lint(
130+
lint::builtin::EMPTY_MATCH_ON_UNSAFE_PLACE,
131+
tycx.match_lint_level,
132+
tycx.whole_match_span.unwrap_or(tycx.scrut_span),
133+
errors::EmptyMatchOnUnsafePlace {
134+
scrut_span: tycx.scrut_span,
135+
suggestion: errors::EmptyMatchOnUnsafePlaceWrapSuggestion {
136+
scrut_start: tycx.scrut_span.shrink_to_lo(),
137+
scrut_end: tycx.scrut_span.shrink_to_hi(),
138+
},
139+
},
140+
);
141+
}
142+
143+
// For backwards compability we allow an empty match in this case.
144+
return rustc::UsefulnessReport {
145+
arm_usefulness: Vec::new(),
146+
non_exhaustiveness_witnesses: Vec::new(),
147+
};
148+
}
149+
}
150+
151+
let report = usefulness::compute_match_usefulness(cx, arms, scrut_ty, scrut_validity);
123152

124-
let pat_column = PatternColumn::new(arms);
153+
let pat_column = lints::PatternColumn::new(arms);
125154

126155
// Lint on ranges that overlap on their endpoints, which is likely a mistake.
127-
lint_overlapping_range_endpoints(cx, &pat_column);
156+
lints::lint_overlapping_range_endpoints(cx, &pat_column);
128157

129158
// Run the non_exhaustive_omitted_patterns lint. Only run on refutable patterns to avoid hitting
130159
// `if let`s. Only run if the match is exhaustive otherwise the error is redundant.
131160
if tycx.refutable && report.non_exhaustiveness_witnesses.is_empty() {
132-
lint_nonexhaustive_missing_variants(cx, arms, &pat_column, scrut_ty)
161+
lints::lint_nonexhaustive_missing_variants(cx, arms, &pat_column, scrut_ty)
133162
}
134163

135164
report

compiler/rustc_pattern_analysis/src/usefulness.rs

+4-8
Original file line numberDiff line numberDiff line change
@@ -1199,23 +1199,19 @@ fn compute_exhaustiveness_and_usefulness<'a, 'p, Cx: TypeCx>(
11991199

12001200
debug!("ty: {ty:?}");
12011201
let pcx = &PlaceCtxt { mcx, ty, is_scrutinee: is_top_level };
1202-
let ctors_for_ty = pcx.ctors_for_ty();
1203-
let is_integers = matches!(ctors_for_ty, ConstructorSet::Integers { .. }); // For diagnostics.
12041202

12051203
// Whether the place/column we are inspecting is known to contain valid data.
12061204
let mut place_validity = matrix.place_validity[0];
1207-
if !mcx.tycx.is_min_exhaustive_patterns_feature_on()
1208-
|| (is_top_level && matches!(ctors_for_ty, ConstructorSet::NoConstructors))
1209-
{
1210-
// For backwards compability we allow omitting some empty arms that we ideally shouldn't.
1205+
if mcx.tycx.is_exhaustive_patterns_feature_on() {
1206+
// Under `exhaustive_patterns` we allow omitting empty arms even when they aren't redundant.
12111207
place_validity = place_validity.allow_omitting_side_effecting_arms();
12121208
}
12131209

12141210
// Analyze the constructors present in this column.
1211+
let ctors_for_ty = pcx.ctors_for_ty();
1212+
let is_integers = matches!(ctors_for_ty, ConstructorSet::Integers { .. }); // For diagnostics.
12151213
let ctors = matrix.heads().map(|p| p.ctor());
12161214
let split_set = ctors_for_ty.split(pcx, ctors);
1217-
1218-
// Decide what constructors to report.
12191215
let all_missing = split_set.present.is_empty();
12201216

12211217
// Build the set of constructors we will specialize with. It must cover the whole type.

0 commit comments

Comments
 (0)