Skip to content

Commit 7a246dd

Browse files
committed
Add pass to identify undefined or erroneous behaviour
1 parent 920e005 commit 7a246dd

File tree

7 files changed

+95
-49
lines changed

7 files changed

+95
-49
lines changed

Diff for: compiler/rustc_const_eval/src/transform/validate.rs

+3-47
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@ use rustc_middle::mir::interpret::Scalar;
88
use rustc_middle::mir::visit::{NonUseContext, PlaceContext, Visitor};
99
use rustc_middle::mir::*;
1010
use rustc_middle::ty::{self, InstanceDef, ParamEnv, Ty, TyCtxt, TypeVisitableExt, Variance};
11-
use rustc_mir_dataflow::impls::MaybeStorageLive;
12-
use rustc_mir_dataflow::storage::always_storage_live_locals;
13-
use rustc_mir_dataflow::{Analysis, ResultsCursor};
1411
use rustc_target::abi::{Size, FIRST_VARIANT};
1512
use rustc_target::spec::abi::Abi;
1613

@@ -51,12 +48,6 @@ impl<'tcx> MirPass<'tcx> for Validator {
5148
Reveal::All => tcx.param_env_reveal_all_normalized(def_id),
5249
};
5350

54-
let always_live_locals = always_storage_live_locals(body);
55-
let storage_liveness = MaybeStorageLive::new(std::borrow::Cow::Owned(always_live_locals))
56-
.into_engine(tcx, body)
57-
.iterate_to_fixpoint()
58-
.into_results_cursor(body);
59-
6051
let can_unwind = if mir_phase <= MirPhase::Runtime(RuntimePhase::Initial) {
6152
// In this case `AbortUnwindingCalls` haven't yet been executed.
6253
true
@@ -83,7 +74,6 @@ impl<'tcx> MirPass<'tcx> for Validator {
8374
mir_phase,
8475
unwind_edge_count: 0,
8576
reachable_blocks: traversal::reachable_as_bitset(body),
86-
storage_liveness,
8777
place_cache: FxHashSet::default(),
8878
value_cache: FxHashSet::default(),
8979
can_unwind,
@@ -116,7 +106,6 @@ struct CfgChecker<'a, 'tcx> {
116106
mir_phase: MirPhase,
117107
unwind_edge_count: usize,
118108
reachable_blocks: BitSet<BasicBlock>,
119-
storage_liveness: ResultsCursor<'a, 'tcx, MaybeStorageLive<'static>>,
120109
place_cache: FxHashSet<PlaceRef<'tcx>>,
121110
value_cache: FxHashSet<u128>,
122111
// If `false`, then the MIR must not contain `UnwindAction::Continue` or
@@ -294,28 +283,13 @@ impl<'a, 'tcx> CfgChecker<'a, 'tcx> {
294283
}
295284

296285
impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
297-
fn visit_local(&mut self, local: Local, context: PlaceContext, location: Location) {
286+
fn visit_local(&mut self, local: Local, _context: PlaceContext, location: Location) {
298287
if self.body.local_decls.get(local).is_none() {
299288
self.fail(
300289
location,
301290
format!("local {local:?} has no corresponding declaration in `body.local_decls`"),
302291
);
303292
}
304-
305-
if self.reachable_blocks.contains(location.block) && context.is_use() {
306-
// We check that the local is live whenever it is used. Technically, violating this
307-
// restriction is only UB and not actually indicative of not well-formed MIR. This means
308-
// that an optimization which turns MIR that already has UB into MIR that fails this
309-
// check is not necessarily wrong. However, we have no such optimizations at the moment,
310-
// and so we include this check anyway to help us catch bugs. If you happen to write an
311-
// optimization that might cause this to incorrectly fire, feel free to remove this
312-
// check.
313-
self.storage_liveness.seek_after_primary_effect(location);
314-
let locals_with_storage = self.storage_liveness.get();
315-
if !locals_with_storage.contains(local) {
316-
self.fail(location, format!("use of local {local:?}, which has no storage here"));
317-
}
318-
}
319293
}
320294

321295
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
@@ -367,26 +341,8 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
367341
self.fail(location, format!("explicit `{kind:?}` is forbidden"));
368342
}
369343
}
370-
StatementKind::StorageLive(local) => {
371-
// We check that the local is not live when entering a `StorageLive` for it.
372-
// Technically, violating this restriction is only UB and not actually indicative
373-
// of not well-formed MIR. This means that an optimization which turns MIR that
374-
// already has UB into MIR that fails this check is not necessarily wrong. However,
375-
// we have no such optimizations at the moment, and so we include this check anyway
376-
// to help us catch bugs. If you happen to write an optimization that might cause
377-
// this to incorrectly fire, feel free to remove this check.
378-
if self.reachable_blocks.contains(location.block) {
379-
self.storage_liveness.seek_before_primary_effect(location);
380-
let locals_with_storage = self.storage_liveness.get();
381-
if locals_with_storage.contains(*local) {
382-
self.fail(
383-
location,
384-
format!("StorageLive({local:?}) which already has storage here"),
385-
);
386-
}
387-
}
388-
}
389-
StatementKind::StorageDead(_)
344+
StatementKind::StorageLive(_)
345+
| StatementKind::StorageDead(_)
390346
| StatementKind::Intrinsic(_)
391347
| StatementKind::Coverage(_)
392348
| StatementKind::ConstEvalCounter

Diff for: compiler/rustc_mir_transform/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ pub mod inline;
8686
mod instsimplify;
8787
mod jump_threading;
8888
mod large_enums;
89+
mod lint;
8990
mod lower_intrinsics;
9091
mod lower_slice_len;
9192
mod match_branches;

Diff for: compiler/rustc_mir_transform/src/lint.rs

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//! This pass statically detects code which has undefined behaviour or is likely to be erroneous.
2+
//! It can be used to locate problems in MIR building or optimizations. It assumes that all code
3+
//! can be executed, so it has false positives.
4+
use rustc_index::bit_set::BitSet;
5+
use rustc_middle::mir::visit::{PlaceContext, Visitor};
6+
use rustc_middle::mir::*;
7+
use rustc_middle::ty::TyCtxt;
8+
use rustc_mir_dataflow::impls::MaybeStorageLive;
9+
use rustc_mir_dataflow::storage::always_storage_live_locals;
10+
use rustc_mir_dataflow::{Analysis, ResultsCursor};
11+
use std::borrow::Cow;
12+
13+
pub fn lint_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, when: String) {
14+
let reachable_blocks = traversal::reachable_as_bitset(body);
15+
let always_live_locals = &always_storage_live_locals(body);
16+
let storage_liveness = MaybeStorageLive::new(Cow::Borrowed(always_live_locals))
17+
.into_engine(tcx, body)
18+
.iterate_to_fixpoint()
19+
.into_results_cursor(body);
20+
21+
Lint { tcx, when, body, reachable_blocks, storage_liveness }.visit_body(body);
22+
}
23+
24+
struct Lint<'a, 'tcx> {
25+
tcx: TyCtxt<'tcx>,
26+
when: String,
27+
body: &'a Body<'tcx>,
28+
reachable_blocks: BitSet<BasicBlock>,
29+
storage_liveness: ResultsCursor<'a, 'tcx, MaybeStorageLive<'a>>,
30+
}
31+
32+
impl<'a, 'tcx> Lint<'a, 'tcx> {
33+
#[track_caller]
34+
fn fail(&self, location: Location, msg: impl AsRef<str>) {
35+
let span = self.body.source_info(location).span;
36+
self.tcx.sess.dcx().span_delayed_bug(
37+
span,
38+
format!(
39+
"broken MIR in {:?} ({}) at {:?}:\n{}",
40+
self.body.source.instance,
41+
self.when,
42+
location,
43+
msg.as_ref()
44+
),
45+
);
46+
}
47+
}
48+
49+
impl<'a, 'tcx> Visitor<'tcx> for Lint<'a, 'tcx> {
50+
fn visit_local(&mut self, local: Local, context: PlaceContext, location: Location) {
51+
if self.reachable_blocks.contains(location.block) && context.is_use() {
52+
self.storage_liveness.seek_after_primary_effect(location);
53+
if !self.storage_liveness.get().contains(local) {
54+
self.fail(location, format!("use of local {local:?}, which has no storage here"));
55+
}
56+
}
57+
}
58+
59+
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
60+
match statement.kind {
61+
StatementKind::StorageLive(local) => {
62+
if self.reachable_blocks.contains(location.block) {
63+
self.storage_liveness.seek_before_primary_effect(location);
64+
if self.storage_liveness.get().contains(local) {
65+
self.fail(
66+
location,
67+
format!("StorageLive({local:?}) which already has storage here"),
68+
);
69+
}
70+
}
71+
}
72+
_ => {}
73+
}
74+
75+
self.super_statement(statement, location);
76+
}
77+
}

Diff for: compiler/rustc_mir_transform/src/pass_manager.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use rustc_middle::mir::{self, Body, MirPhase, RuntimePhase};
22
use rustc_middle::ty::TyCtxt;
33
use rustc_session::Session;
44

5-
use crate::{validate, MirPass};
5+
use crate::{lint::lint_body, validate, MirPass};
66

77
/// Just like `MirPass`, except it cannot mutate `Body`.
88
pub trait MirLint<'tcx> {
@@ -109,6 +109,7 @@ fn run_passes_inner<'tcx>(
109109
phase_change: Option<MirPhase>,
110110
validate_each: bool,
111111
) {
112+
let lint = tcx.sess.opts.unstable_opts.lint_mir & !body.should_skip();
112113
let validate = validate_each & tcx.sess.opts.unstable_opts.validate_mir & !body.should_skip();
113114
let overridden_passes = &tcx.sess.opts.unstable_opts.mir_enable_passes;
114115
trace!(?overridden_passes);
@@ -131,6 +132,9 @@ fn run_passes_inner<'tcx>(
131132
if validate {
132133
validate_body(tcx, body, format!("before pass {name}"));
133134
}
135+
if lint {
136+
lint_body(tcx, body, format!("before pass {name}"));
137+
}
134138

135139
if let Some(prof_arg) = &prof_arg {
136140
tcx.sess
@@ -147,6 +151,9 @@ fn run_passes_inner<'tcx>(
147151
if validate {
148152
validate_body(tcx, body, format!("after pass {name}"));
149153
}
154+
if lint {
155+
lint_body(tcx, body, format!("after pass {name}"));
156+
}
150157

151158
body.pass_count += 1;
152159
}
@@ -164,6 +171,9 @@ fn run_passes_inner<'tcx>(
164171
if validate || new_phase == MirPhase::Runtime(RuntimePhase::Optimized) {
165172
validate_body(tcx, body, format!("after phase change to {}", new_phase.name()));
166173
}
174+
if lint {
175+
lint_body(tcx, body, format!("after phase change to {}", new_phase.name()));
176+
}
167177

168178
body.pass_count = 1;
169179
}

Diff for: compiler/rustc_session/src/options.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1694,6 +1694,8 @@ options! {
16941694
"link native libraries in the linker invocation (default: yes)"),
16951695
link_only: bool = (false, parse_bool, [TRACKED],
16961696
"link the `.rlink` file generated by `-Z no-link` (default: no)"),
1697+
lint_mir: bool = (false, parse_bool, [UNTRACKED],
1698+
"lint MIR before and after each transformation"),
16971699
llvm_module_flag: Vec<(String, u32, String)> = (Vec::new(), parse_llvm_module_flag, [TRACKED],
16981700
"a list of module flags to pass to LLVM (space separated)"),
16991701
llvm_plugins: Vec<String> = (Vec::new(), parse_list, [TRACKED],

Diff for: tests/ui/mir/validate/storage-live.rs renamed to tests/ui/mir/lint/storage-live.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// compile-flags: -Zvalidate-mir -Ztreat-err-as-bug
1+
// compile-flags: -Zlint-mir -Ztreat-err-as-bug
22
// failure-status: 101
33
// error-pattern: broken MIR in
44
// error-pattern: StorageLive(_1) which already has storage here
File renamed without changes.

0 commit comments

Comments
 (0)