Rollup merge of #119577 - tmiasko:lint, r=oli-obk
Migrate memory overlap check from validator to lint The check attempts to identify potential undefined behaviour, rather than whether MIR is well-formed. It belongs in the lint not validator. Follow up to changes from #119077.
This commit is contained in:
commit
3a983ad3b0
13 changed files with 115 additions and 264 deletions
|
@ -133,7 +133,6 @@
|
|||
|
||||
use std::collections::hash_map::{Entry, OccupiedEntry};
|
||||
|
||||
use crate::simplify::remove_dead_blocks;
|
||||
use crate::MirPass;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_index::bit_set::BitSet;
|
||||
|
@ -241,12 +240,6 @@ impl<'tcx> MirPass<'tcx> for DestinationPropagation {
|
|||
apply_merges(body, tcx, &merges, &merged_locals);
|
||||
}
|
||||
|
||||
if round_count != 0 {
|
||||
// Merging can introduce overlap between moved arguments and/or call destination in an
|
||||
// unreachable code, which validator considers to be ill-formed.
|
||||
remove_dead_blocks(body);
|
||||
}
|
||||
|
||||
trace!(round_count);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! This pass statically detects code which has undefined behaviour or is likely to be erroneous.
|
||||
//! It can be used to locate problems in MIR building or optimizations. It assumes that all code
|
||||
//! can be executed, so it has false positives.
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_middle::mir::visit::{PlaceContext, Visitor};
|
||||
use rustc_middle::mir::*;
|
||||
|
@ -11,7 +12,6 @@ use rustc_mir_dataflow::{Analysis, ResultsCursor};
|
|||
use std::borrow::Cow;
|
||||
|
||||
pub fn lint_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, when: String) {
|
||||
let reachable_blocks = traversal::reachable_as_bitset(body);
|
||||
let always_live_locals = &always_storage_live_locals(body);
|
||||
|
||||
let maybe_storage_live = MaybeStorageLive::new(Cow::Borrowed(always_live_locals))
|
||||
|
@ -24,17 +24,19 @@ pub fn lint_body<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, when: String) {
|
|||
.iterate_to_fixpoint()
|
||||
.into_results_cursor(body);
|
||||
|
||||
Lint {
|
||||
let mut lint = Lint {
|
||||
tcx,
|
||||
when,
|
||||
body,
|
||||
is_fn_like: tcx.def_kind(body.source.def_id()).is_fn_like(),
|
||||
always_live_locals,
|
||||
reachable_blocks,
|
||||
maybe_storage_live,
|
||||
maybe_storage_dead,
|
||||
places: Default::default(),
|
||||
};
|
||||
for (bb, data) in traversal::reachable(body) {
|
||||
lint.visit_basic_block_data(bb, data);
|
||||
}
|
||||
.visit_body(body);
|
||||
}
|
||||
|
||||
struct Lint<'a, 'tcx> {
|
||||
|
@ -43,9 +45,9 @@ struct Lint<'a, 'tcx> {
|
|||
body: &'a Body<'tcx>,
|
||||
is_fn_like: bool,
|
||||
always_live_locals: &'a BitSet<Local>,
|
||||
reachable_blocks: BitSet<BasicBlock>,
|
||||
maybe_storage_live: ResultsCursor<'a, 'tcx, MaybeStorageLive<'a>>,
|
||||
maybe_storage_dead: ResultsCursor<'a, 'tcx, MaybeStorageDead<'a>>,
|
||||
places: FxHashSet<PlaceRef<'tcx>>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Lint<'a, 'tcx> {
|
||||
|
@ -67,7 +69,7 @@ impl<'a, 'tcx> Lint<'a, 'tcx> {
|
|||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for Lint<'a, 'tcx> {
|
||||
fn visit_local(&mut self, local: Local, context: PlaceContext, location: Location) {
|
||||
if self.reachable_blocks.contains(location.block) && context.is_use() {
|
||||
if context.is_use() {
|
||||
self.maybe_storage_dead.seek_after_primary_effect(location);
|
||||
if self.maybe_storage_dead.get().contains(local) {
|
||||
self.fail(location, format!("use of local {local:?}, which has no storage here"));
|
||||
|
@ -76,18 +78,28 @@ impl<'a, 'tcx> Visitor<'tcx> for Lint<'a, 'tcx> {
|
|||
}
|
||||
|
||||
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
|
||||
match statement.kind {
|
||||
StatementKind::StorageLive(local) => {
|
||||
if self.reachable_blocks.contains(location.block) {
|
||||
self.maybe_storage_live.seek_before_primary_effect(location);
|
||||
if self.maybe_storage_live.get().contains(local) {
|
||||
match &statement.kind {
|
||||
StatementKind::Assign(box (dest, rvalue)) => {
|
||||
if let Rvalue::Use(Operand::Copy(src) | Operand::Move(src)) = rvalue {
|
||||
// The sides of an assignment must not alias. Currently this just checks whether
|
||||
// the places are identical.
|
||||
if dest == src {
|
||||
self.fail(
|
||||
location,
|
||||
format!("StorageLive({local:?}) which already has storage here"),
|
||||
"encountered `Assign` statement with overlapping memory",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
StatementKind::StorageLive(local) => {
|
||||
self.maybe_storage_live.seek_before_primary_effect(location);
|
||||
if self.maybe_storage_live.get().contains(*local) {
|
||||
self.fail(
|
||||
location,
|
||||
format!("StorageLive({local:?}) which already has storage here"),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
@ -95,9 +107,9 @@ impl<'a, 'tcx> Visitor<'tcx> for Lint<'a, 'tcx> {
|
|||
}
|
||||
|
||||
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
|
||||
match terminator.kind {
|
||||
match &terminator.kind {
|
||||
TerminatorKind::Return => {
|
||||
if self.is_fn_like && self.reachable_blocks.contains(location.block) {
|
||||
if self.is_fn_like {
|
||||
self.maybe_storage_live.seek_after_primary_effect(location);
|
||||
for local in self.maybe_storage_live.get().iter() {
|
||||
if !self.always_live_locals.contains(local) {
|
||||
|
@ -111,6 +123,28 @@ impl<'a, 'tcx> Visitor<'tcx> for Lint<'a, 'tcx> {
|
|||
}
|
||||
}
|
||||
}
|
||||
TerminatorKind::Call { args, destination, .. } => {
|
||||
// The call destination place and Operand::Move place used as an argument might be
|
||||
// passed by a reference to the callee. Consequently they must be non-overlapping.
|
||||
// Currently this simply checks for duplicate places.
|
||||
self.places.clear();
|
||||
self.places.insert(destination.as_ref());
|
||||
let mut has_duplicates = false;
|
||||
for arg in args {
|
||||
if let Operand::Move(place) = arg {
|
||||
has_duplicates |= !self.places.insert(place.as_ref());
|
||||
}
|
||||
}
|
||||
if has_duplicates {
|
||||
self.fail(
|
||||
location,
|
||||
format!(
|
||||
"encountered overlapping memory in `Move` arguments to `Call` terminator: {:?}",
|
||||
terminator.kind,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
|
|
@ -109,14 +109,15 @@ fn run_passes_inner<'tcx>(
|
|||
phase_change: Option<MirPhase>,
|
||||
validate_each: bool,
|
||||
) {
|
||||
let lint = tcx.sess.opts.unstable_opts.lint_mir & !body.should_skip();
|
||||
let validate = validate_each & tcx.sess.opts.unstable_opts.validate_mir & !body.should_skip();
|
||||
let overridden_passes = &tcx.sess.opts.unstable_opts.mir_enable_passes;
|
||||
trace!(?overridden_passes);
|
||||
|
||||
let prof_arg = tcx.sess.prof.enabled().then(|| format!("{:?}", body.source.def_id()));
|
||||
|
||||
if !body.should_skip() {
|
||||
let validate = validate_each & tcx.sess.opts.unstable_opts.validate_mir;
|
||||
let lint = tcx.sess.opts.unstable_opts.lint_mir;
|
||||
|
||||
for pass in passes {
|
||||
let name = pass.name();
|
||||
|
||||
|
@ -162,7 +163,12 @@ fn run_passes_inner<'tcx>(
|
|||
body.pass_count = 0;
|
||||
|
||||
dump_mir_for_phase_change(tcx, body);
|
||||
if validate || new_phase == MirPhase::Runtime(RuntimePhase::Optimized) {
|
||||
|
||||
let validate =
|
||||
(validate_each & tcx.sess.opts.unstable_opts.validate_mir & !body.should_skip())
|
||||
|| new_phase == MirPhase::Runtime(RuntimePhase::Optimized);
|
||||
let lint = tcx.sess.opts.unstable_opts.lint_mir & !body.should_skip();
|
||||
if validate {
|
||||
validate_body(tcx, body, format!("after phase change to {}", new_phase.name()));
|
||||
}
|
||||
if lint {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue