Skip to content

Commit de5f50b

Browse files
committed
Lint against empty match on not-known-valid place
1 parent 7cc0ad8 commit de5f50b

10 files changed

+475
-28
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_mir_build/messages.ftl

+7
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,13 @@ mir_build_deref_raw_pointer_requires_unsafe_unsafe_op_in_unsafe_fn_allowed =
9797
.note = raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior
9898
.label = dereference of raw pointer
9999
100+
mir_build_empty_match_on_unsafe_place =
101+
empty match on potentially-invalid data
102+
.note = this place can hold invalid data, which would make the match reachable
103+
104+
mir_build_empty_match_on_unsafe_place_wrap_suggestion =
105+
consider forcing a read of the value
106+
100107
mir_build_extern_static_requires_unsafe =
101108
use of extern static is unsafe and requires unsafe block
102109
.note = extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior

compiler/rustc_mir_build/src/errors.rs

+21
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,27 @@ impl<'tcx> AddToDiagnostic for Overlap<'tcx> {
841841
}
842842
}
843843

844+
#[derive(LintDiagnostic)]
845+
#[diag(mir_build_empty_match_on_unsafe_place)]
846+
pub struct EmptyMatchOnUnsafePlace {
847+
#[note]
848+
pub scrut_span: Span,
849+
#[subdiagnostic]
850+
pub suggestion: EmptyMatchOnUnsafePlaceWrapSuggestion,
851+
}
852+
853+
#[derive(Subdiagnostic)]
854+
#[multipart_suggestion(
855+
mir_build_empty_match_on_unsafe_place_wrap_suggestion,
856+
applicability = "maybe-incorrect"
857+
)]
858+
pub struct EmptyMatchOnUnsafePlaceWrapSuggestion {
859+
#[suggestion_part(code = "{{ ")]
860+
pub scrut_start: Span,
861+
#[suggestion_part(code = " }}")]
862+
pub scrut_end: Span,
863+
}
864+
844865
#[derive(LintDiagnostic)]
845866
#[diag(mir_build_non_exhaustive_omitted_pattern)]
846867
#[help]

compiler/rustc_mir_build/src/thir/pattern/usefulness.rs

+39-10
Original file line numberDiff line numberDiff line change
@@ -557,8 +557,8 @@ use super::deconstruct_pat::{
557557
WitnessPat,
558558
};
559559
use crate::errors::{
560-
NonExhaustiveOmittedPattern, NonExhaustiveOmittedPatternLintOnArm, Overlap,
561-
OverlappingRangeEndpoints, Uncovered,
560+
EmptyMatchOnUnsafePlace, EmptyMatchOnUnsafePlaceWrapSuggestion, NonExhaustiveOmittedPattern,
561+
NonExhaustiveOmittedPatternLintOnArm, Overlap, OverlappingRangeEndpoints, Uncovered,
562562
};
563563

564564
use rustc_data_structures::captures::Captures;
@@ -1238,22 +1238,17 @@ fn compute_exhaustiveness_and_usefulness<'p, 'tcx>(
12381238

12391239
debug!("ty: {ty:?}");
12401240
let pcx = &PatCtxt { cx, ty, is_top_level };
1241-
let ctors_for_ty = ConstructorSet::for_ty(cx, ty);
12421241

12431242
// Whether the place/column we are inspecting is known to contain valid data.
12441243
let mut place_validity = matrix.place_validity[0];
1245-
if !pcx.cx.tcx.features().min_exhaustive_patterns
1246-
|| (is_top_level && matches!(ctors_for_ty, ConstructorSet::NoConstructors))
1247-
{
1248-
// For backwards compability we allow omitting some empty arms that we ideally shouldn't.
1244+
if cx.tcx.features().exhaustive_patterns {
1245+
// Under `exhaustive_patterns` we allow omitting empty arms even when they aren't redundant.
12491246
place_validity = place_validity.allow_omitting_side_effecting_arms();
12501247
}
12511248

12521249
// Analyze the constructors present in this column.
12531250
let ctors = matrix.heads().map(|p| p.ctor());
1254-
let split_set = ctors_for_ty.split(pcx, ctors);
1255-
1256-
// Decide what constructors to report.
1251+
let split_set = ConstructorSet::for_ty(cx, ty).split(pcx, ctors);
12571252
let all_missing = split_set.present.is_empty();
12581253

12591254
// Build the set of constructors we will specialize with. It must cover the whole type.
@@ -1578,6 +1573,40 @@ pub(crate) fn compute_match_usefulness<'p, 'tcx>(
15781573
arms: &[MatchArm<'p, 'tcx>],
15791574
scrut_ty: Ty<'tcx>,
15801575
) -> UsefulnessReport<'p, 'tcx> {
1576+
if !cx.known_valid_scrutinee && arms.iter().all(|arm| arm.has_guard) {
1577+
let is_directly_empty = match scrut_ty.kind() {
1578+
ty::Adt(def, ..) => {
1579+
def.is_enum()
1580+
&& def.variants().is_empty()
1581+
&& !cx.is_foreign_non_exhaustive_enum(scrut_ty)
1582+
}
1583+
ty::Never => true,
1584+
_ => false,
1585+
};
1586+
if is_directly_empty {
1587+
if cx.tcx.features().min_exhaustive_patterns {
1588+
cx.tcx.emit_spanned_lint(
1589+
lint::builtin::EMPTY_MATCH_ON_UNSAFE_PLACE,
1590+
cx.match_lint_level,
1591+
cx.whole_match_span.unwrap_or(cx.scrut_span),
1592+
EmptyMatchOnUnsafePlace {
1593+
scrut_span: cx.scrut_span,
1594+
suggestion: EmptyMatchOnUnsafePlaceWrapSuggestion {
1595+
scrut_start: cx.scrut_span.shrink_to_lo(),
1596+
scrut_end: cx.scrut_span.shrink_to_hi(),
1597+
},
1598+
},
1599+
);
1600+
}
1601+
1602+
// For backwards compability we allow an empty match in this case.
1603+
return UsefulnessReport {
1604+
arm_usefulness: Vec::new(),
1605+
non_exhaustiveness_witnesses: Vec::new(),
1606+
};
1607+
}
1608+
}
1609+
15811610
let scrut_validity = ValidityConstraint::from_bool(cx.known_valid_scrutinee);
15821611
let mut matrix = Matrix::new(cx, arms, scrut_ty, scrut_validity);
15831612
let non_exhaustiveness_witnesses = compute_exhaustiveness_and_usefulness(cx, &mut matrix, true);

0 commit comments

Comments
 (0)