Skip to content

Commit c49123d

Browse files
committedDec 11, 2023
Lint against empty match on not-known-valid place
1 parent a8cc4cf commit c49123d

11 files changed

+484
-31
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,
@@ -4019,6 +4020,76 @@ declare_lint! {
40194020
@feature_gate = sym::non_exhaustive_omitted_patterns_lint;
40204021
}
40214022

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

+41-4
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,17 @@ extern crate rustc_middle;
1414

1515
rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
1616

17-
use lints::PatternColumn;
1817
use rustc_hir::HirId;
19-
use rustc_middle::ty::Ty;
20-
use usefulness::{compute_match_usefulness, UsefulnessReport};
18+
use rustc_middle::ty::{self, Ty};
19+
use rustc_session::lint;
2120

2221
use crate::cx::MatchCheckCtxt;
23-
use crate::lints::{lint_nonexhaustive_missing_variants, lint_overlapping_range_endpoints};
22+
use crate::errors::{EmptyMatchOnUnsafePlace, EmptyMatchOnUnsafePlaceWrapSuggestion};
23+
use crate::lints::{
24+
lint_nonexhaustive_missing_variants, lint_overlapping_range_endpoints, PatternColumn,
25+
};
2426
use crate::pat::DeconstructedPat;
27+
use crate::usefulness::{compute_match_usefulness, UsefulnessReport};
2528

2629
/// The arm of a match expression.
2730
#[derive(Clone, Copy, Debug)]
@@ -41,6 +44,40 @@ pub fn analyze_match<'p, 'tcx>(
4144
) -> UsefulnessReport<'p, 'tcx> {
4245
let pat_column = PatternColumn::new(arms);
4346

47+
if !cx.known_valid_scrutinee && arms.iter().all(|arm| arm.has_guard) {
48+
let is_directly_empty = match scrut_ty.kind() {
49+
ty::Adt(def, ..) => {
50+
def.is_enum()
51+
&& def.variants().is_empty()
52+
&& !cx.is_foreign_non_exhaustive_enum(scrut_ty)
53+
}
54+
ty::Never => true,
55+
_ => false,
56+
};
57+
if is_directly_empty {
58+
if cx.tcx.features().min_exhaustive_patterns {
59+
cx.tcx.emit_spanned_lint(
60+
lint::builtin::EMPTY_MATCH_ON_UNSAFE_PLACE,
61+
cx.match_lint_level,
62+
cx.whole_match_span.unwrap_or(cx.scrut_span),
63+
EmptyMatchOnUnsafePlace {
64+
scrut_span: cx.scrut_span,
65+
suggestion: EmptyMatchOnUnsafePlaceWrapSuggestion {
66+
scrut_start: cx.scrut_span.shrink_to_lo(),
67+
scrut_end: cx.scrut_span.shrink_to_hi(),
68+
},
69+
},
70+
);
71+
}
72+
73+
// For backwards compability we allow an empty match in this case.
74+
return UsefulnessReport {
75+
arm_usefulness: Vec::new(),
76+
non_exhaustiveness_witnesses: Vec::new(),
77+
};
78+
}
79+
}
80+
4481
let report = compute_match_usefulness(cx, arms, scrut_ty);
4582

4683
// Lint on ranges that overlap on their endpoints, which is likely a mistake.

‎compiler/rustc_pattern_analysis/src/usefulness.rs

+4-8
Original file line numberDiff line numberDiff line change
@@ -1186,23 +1186,19 @@ fn compute_exhaustiveness_and_usefulness<'p, 'tcx>(
11861186

11871187
debug!("ty: {ty:?}");
11881188
let pcx = &PatCtxt { cx, ty, is_top_level };
1189-
let ctors_for_ty = &cx.ctors_for_ty(ty);
1190-
let is_integers = matches!(ctors_for_ty, ConstructorSet::Integers { .. }); // For diagnostics.
11911189

11921190
// Whether the place/column we are inspecting is known to contain valid data.
11931191
let mut place_validity = matrix.place_validity[0];
1194-
if !pcx.cx.tcx.features().min_exhaustive_patterns
1195-
|| (is_top_level && matches!(ctors_for_ty, ConstructorSet::NoConstructors))
1196-
{
1197-
// For backwards compability we allow omitting some empty arms that we ideally shouldn't.
1192+
if cx.tcx.features().exhaustive_patterns {
1193+
// Under `exhaustive_patterns` we allow omitting empty arms even when they aren't redundant.
11981194
place_validity = place_validity.allow_omitting_side_effecting_arms();
11991195
}
12001196

12011197
// Analyze the constructors present in this column.
1198+
let ctors_for_ty = &cx.ctors_for_ty(ty);
1199+
let is_integers = matches!(ctors_for_ty, ConstructorSet::Integers { .. }); // For diagnostics.
12021200
let ctors = matrix.heads().map(|p| p.ctor());
12031201
let split_set = ctors_for_ty.split(pcx, ctors);
1204-
1205-
// Decide what constructors to report.
12061202
let all_missing = split_set.present.is_empty();
12071203

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

0 commit comments

Comments
 (0)