Rollup merge of #119077 - tmiasko:lint, r=cjgillot
Separate MIR lints from validation Add a MIR lint pass, enabled with -Zlint-mir, which identifies undefined or likely erroneous behaviour. The initial implementation mostly migrates existing checks of this nature from MIR validator, where they did not belong (those checks have false positives and there is nothing inherently invalid about MIR with undefined behaviour). Fixes #104736 Fixes #104843 Fixes #116079 Fixes #116736 Fixes #118990
This commit is contained in:
commit
7dd095598b
13 changed files with 196 additions and 56 deletions
|
@ -8,9 +8,6 @@ use rustc_middle::mir::interpret::Scalar;
|
|||
use rustc_middle::mir::visit::{NonUseContext, PlaceContext, Visitor};
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::ty::{self, InstanceDef, ParamEnv, Ty, TyCtxt, TypeVisitableExt, Variance};
|
||||
use rustc_mir_dataflow::impls::MaybeStorageLive;
|
||||
use rustc_mir_dataflow::storage::always_storage_live_locals;
|
||||
use rustc_mir_dataflow::{Analysis, ResultsCursor};
|
||||
use rustc_target::abi::{Size, FIRST_VARIANT};
|
||||
use rustc_target::spec::abi::Abi;
|
||||
|
||||
|
@ -51,12 +48,6 @@ impl<'tcx> MirPass<'tcx> for Validator {
|
|||
Reveal::All => tcx.param_env_reveal_all_normalized(def_id),
|
||||
};
|
||||
|
||||
let always_live_locals = always_storage_live_locals(body);
|
||||
let storage_liveness = MaybeStorageLive::new(std::borrow::Cow::Owned(always_live_locals))
|
||||
.into_engine(tcx, body)
|
||||
.iterate_to_fixpoint()
|
||||
.into_results_cursor(body);
|
||||
|
||||
let can_unwind = if mir_phase <= MirPhase::Runtime(RuntimePhase::Initial) {
|
||||
// In this case `AbortUnwindingCalls` haven't yet been executed.
|
||||
true
|
||||
|
@ -83,7 +74,6 @@ impl<'tcx> MirPass<'tcx> for Validator {
|
|||
mir_phase,
|
||||
unwind_edge_count: 0,
|
||||
reachable_blocks: traversal::reachable_as_bitset(body),
|
||||
storage_liveness,
|
||||
place_cache: FxHashSet::default(),
|
||||
value_cache: FxHashSet::default(),
|
||||
can_unwind,
|
||||
|
@ -116,7 +106,6 @@ struct CfgChecker<'a, 'tcx> {
|
|||
mir_phase: MirPhase,
|
||||
unwind_edge_count: usize,
|
||||
reachable_blocks: BitSet<BasicBlock>,
|
||||
storage_liveness: ResultsCursor<'a, 'tcx, MaybeStorageLive<'static>>,
|
||||
place_cache: FxHashSet<PlaceRef<'tcx>>,
|
||||
value_cache: FxHashSet<u128>,
|
||||
// If `false`, then the MIR must not contain `UnwindAction::Continue` or
|
||||
|
@ -294,28 +283,13 @@ impl<'a, 'tcx> CfgChecker<'a, 'tcx> {
|
|||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
|
||||
fn visit_local(&mut self, local: Local, context: PlaceContext, location: Location) {
|
||||
fn visit_local(&mut self, local: Local, _context: PlaceContext, location: Location) {
|
||||
if self.body.local_decls.get(local).is_none() {
|
||||
self.fail(
|
||||
location,
|
||||
format!("local {local:?} has no corresponding declaration in `body.local_decls`"),
|
||||
);
|
||||
}
|
||||
|
||||
if self.reachable_blocks.contains(location.block) && context.is_use() {
|
||||
// We check that the local is live whenever it is used. Technically, violating this
|
||||
// restriction is only UB and not actually indicative of not well-formed MIR. This means
|
||||
// that an optimization which turns MIR that already has UB into MIR that fails this
|
||||
// check is not necessarily wrong. However, we have no such optimizations at the moment,
|
||||
// and so we include this check anyway to help us catch bugs. If you happen to write an
|
||||
// optimization that might cause this to incorrectly fire, feel free to remove this
|
||||
// check.
|
||||
self.storage_liveness.seek_after_primary_effect(location);
|
||||
let locals_with_storage = self.storage_liveness.get();
|
||||
if !locals_with_storage.contains(local) {
|
||||
self.fail(location, format!("use of local {local:?}, which has no storage here"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
|
||||
|
@ -367,26 +341,8 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> {
|
|||
self.fail(location, format!("explicit `{kind:?}` is forbidden"));
|
||||
}
|
||||
}
|
||||
StatementKind::StorageLive(local) => {
|
||||
// We check that the local is not live when entering a `StorageLive` for it.
|
||||
// Technically, violating this restriction is only UB and not actually indicative
|
||||
// of not well-formed MIR. This means that an optimization which turns MIR that
|
||||
// already has UB into MIR that fails this check is not necessarily wrong. However,
|
||||
// we have no such optimizations at the moment, and so we include this check anyway
|
||||
// to help us catch bugs. If you happen to write an optimization that might cause
|
||||
// this to incorrectly fire, feel free to remove this check.
|
||||
if self.reachable_blocks.contains(location.block) {
|
||||
self.storage_liveness.seek_before_primary_effect(location);
|
||||
let locals_with_storage = self.storage_liveness.get();
|
||||
if locals_with_storage.contains(*local) {
|
||||
self.fail(
|
||||
location,
|
||||
format!("StorageLive({local:?}) which already has storage here"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
StatementKind::StorageDead(_)
|
||||
StatementKind::StorageLive(_)
|
||||
| StatementKind::StorageDead(_)
|
||||
| StatementKind::Intrinsic(_)
|
||||
| StatementKind::Coverage(_)
|
||||
| StatementKind::ConstEvalCounter
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue