Skip to content

Commit

Permalink
Auto merge of rust-lang#10192 - Jarcho:revert_9701, r=flip1995
Browse files Browse the repository at this point in the history
Partially revert rust-lang#9701

This partially reverts rust-lang#9701 due to rust-lang#10134

r? `@flip1995`

changelog: None
  • Loading branch information
bors committed Jan 12, 2023
2 parents 0b8ee70 + 757e944 commit 7f27e2e
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 233 deletions.
8 changes: 4 additions & 4 deletions clippy_lints/src/dereference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1282,10 +1282,10 @@ fn referent_used_exactly_once<'tcx>(
possible_borrowers.push((body_owner_local_def_id, PossibleBorrowerMap::new(cx, mir)));
}
let possible_borrower = &mut possible_borrowers.last_mut().unwrap().1;
// If `place.local` were not included here, the `copyable_iterator::warn` test would fail. The
// reason is that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible
// borrower of itself. See the comment in that method for an explanation as to why.
possible_borrower.at_most_borrowers(cx, &[local, place.local], place.local, location)
// If `only_borrowers` were used here, the `copyable_iterator::warn` test would fail. The reason is
// that `PossibleBorrowerVisitor::visit_terminator` considers `place.local` a possible borrower of
// itself. See the comment in that method for an explanation as to why.
possible_borrower.bounded_borrowers(&[local], &[local, place.local], place.local, location)
&& used_exactly_once(mir, place.local).unwrap_or(false)
} else {
false
Expand Down
4 changes: 2 additions & 2 deletions clippy_lints/src/redundant_clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
// `res = clone(arg)` can be turned into `res = move arg;`
// if `arg` is the only borrow of `cloned` at this point.

if cannot_move_out || !possible_borrower.at_most_borrowers(cx, &[arg], cloned, loc) {
if cannot_move_out || !possible_borrower.only_borrowers(&[arg], cloned, loc) {
continue;
}

Expand Down Expand Up @@ -178,7 +178,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
// StorageDead(pred_arg);
// res = to_path_buf(cloned);
// ```
if cannot_move_out || !possible_borrower.at_most_borrowers(cx, &[arg, cloned], local, loc) {
if cannot_move_out || !possible_borrower.only_borrowers(&[arg, cloned], local, loc) {
continue;
}

Expand Down
270 changes: 99 additions & 171 deletions clippy_utils/src/mir/possible_borrower.rs
Original file line number Diff line number Diff line change
@@ -1,137 +1,90 @@
use super::possible_origin::PossibleOriginVisitor;
use super::{possible_origin::PossibleOriginVisitor, transitive_relation::TransitiveRelation};
use crate::ty::is_copy;
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
use rustc_data_structures::fx::FxHashMap;
use rustc_index::bit_set::{BitSet, HybridBitSet};
use rustc_lint::LateContext;
use rustc_middle::mir::{
self, visit::Visitor as _, BasicBlock, Local, Location, Mutability, Statement, StatementKind, Terminator,
};
use rustc_middle::ty::{self, visit::TypeVisitor, TyCtxt};
use rustc_mir_dataflow::{
fmt::DebugWithContext, impls::MaybeStorageLive, lattice::JoinSemiLattice, Analysis, AnalysisDomain,
CallReturnPlaces, ResultsCursor,
};
use rustc_middle::mir::{self, visit::Visitor as _, Mutability};
use rustc_middle::ty::{self, visit::TypeVisitor};
use rustc_mir_dataflow::{impls::MaybeStorageLive, Analysis, ResultsCursor};
use std::borrow::Cow;
use std::ops::ControlFlow;

/// Collects the possible borrowers of each local.
/// For example, `b = &a; c = &a;` will make `b` and (transitively) `c`
/// possible borrowers of `a`.
#[allow(clippy::module_name_repetitions)]
struct PossibleBorrowerAnalysis<'b, 'tcx> {
tcx: TyCtxt<'tcx>,
struct PossibleBorrowerVisitor<'a, 'b, 'tcx> {
possible_borrower: TransitiveRelation,
body: &'b mir::Body<'tcx>,
cx: &'a LateContext<'tcx>,
possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
struct PossibleBorrowerState {
map: FxIndexMap<Local, BitSet<Local>>,
domain_size: usize,
}

impl PossibleBorrowerState {
fn new(domain_size: usize) -> Self {
impl<'a, 'b, 'tcx> PossibleBorrowerVisitor<'a, 'b, 'tcx> {
fn new(
cx: &'a LateContext<'tcx>,
body: &'b mir::Body<'tcx>,
possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
) -> Self {
Self {
map: FxIndexMap::default(),
domain_size,
possible_borrower: TransitiveRelation::default(),
cx,
body,
possible_origin,
}
}

#[allow(clippy::similar_names)]
fn add(&mut self, borrowed: Local, borrower: Local) {
self.map
.entry(borrowed)
.or_insert(BitSet::new_empty(self.domain_size))
.insert(borrower);
}
}

impl<C> DebugWithContext<C> for PossibleBorrowerState {
fn fmt_with(&self, _ctxt: &C, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
<_ as std::fmt::Debug>::fmt(self, f)
}
fn fmt_diff_with(&self, _old: &Self, _ctxt: &C, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
unimplemented!()
}
}
fn into_map(
self,
cx: &'a LateContext<'tcx>,
maybe_live: ResultsCursor<'b, 'tcx, MaybeStorageLive<'tcx>>,
) -> PossibleBorrowerMap<'b, 'tcx> {
let mut map = FxHashMap::default();
for row in (1..self.body.local_decls.len()).map(mir::Local::from_usize) {
if is_copy(cx, self.body.local_decls[row].ty) {
continue;
}

impl JoinSemiLattice for PossibleBorrowerState {
fn join(&mut self, other: &Self) -> bool {
let mut changed = false;
for (&borrowed, borrowers) in other.map.iter() {
let mut borrowers = self.possible_borrower.reachable_from(row, self.body.local_decls.len());
borrowers.remove(mir::Local::from_usize(0));
if !borrowers.is_empty() {
changed |= self
.map
.entry(borrowed)
.or_insert(BitSet::new_empty(self.domain_size))
.union(borrowers);
map.insert(row, borrowers);
}
}
changed
}
}

impl<'b, 'tcx> AnalysisDomain<'tcx> for PossibleBorrowerAnalysis<'b, 'tcx> {
type Domain = PossibleBorrowerState;

const NAME: &'static str = "possible_borrower";

fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
PossibleBorrowerState::new(body.local_decls.len())
}

fn initialize_start_block(&self, _body: &mir::Body<'tcx>, _entry_set: &mut Self::Domain) {}
}

impl<'b, 'tcx> PossibleBorrowerAnalysis<'b, 'tcx> {
fn new(
tcx: TyCtxt<'tcx>,
body: &'b mir::Body<'tcx>,
possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
) -> Self {
Self {
tcx,
body,
possible_origin,
let bs = BitSet::new_empty(self.body.local_decls.len());
PossibleBorrowerMap {
map,
maybe_live,
bitset: (bs.clone(), bs),
}
}
}

impl<'b, 'tcx> Analysis<'tcx> for PossibleBorrowerAnalysis<'b, 'tcx> {
fn apply_call_return_effect(
&self,
_state: &mut Self::Domain,
_block: BasicBlock,
_return_places: CallReturnPlaces<'_, 'tcx>,
) {
}

fn apply_statement_effect(&self, state: &mut Self::Domain, statement: &Statement<'tcx>, _location: Location) {
if let StatementKind::Assign(box (place, rvalue)) = &statement.kind {
let lhs = place.local;
match rvalue {
mir::Rvalue::Ref(_, _, borrowed) => {
state.add(borrowed.local, lhs);
},
other => {
if ContainsRegion
.visit_ty(place.ty(&self.body.local_decls, self.tcx).ty)
.is_continue()
{
return;
impl<'a, 'b, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, 'b, 'tcx> {
fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _location: mir::Location) {
let lhs = place.local;
match rvalue {
mir::Rvalue::Ref(_, _, borrowed) => {
self.possible_borrower.add(borrowed.local, lhs);
},
other => {
if ContainsRegion
.visit_ty(place.ty(&self.body.local_decls, self.cx.tcx).ty)
.is_continue()
{
return;
}
rvalue_locals(other, |rhs| {
if lhs != rhs {
self.possible_borrower.add(rhs, lhs);
}
rvalue_locals(other, |rhs| {
if lhs != rhs {
state.add(rhs, lhs);
}
});
},
}
});
},
}
}

fn apply_terminator_effect(&self, state: &mut Self::Domain, terminator: &Terminator<'tcx>, _location: Location) {
fn visit_terminator(&mut self, terminator: &mir::Terminator<'_>, _loc: mir::Location) {
if let mir::TerminatorKind::Call {
args,
destination: mir::Place { local: dest, .. },
Expand Down Expand Up @@ -171,10 +124,10 @@ impl<'b, 'tcx> Analysis<'tcx> for PossibleBorrowerAnalysis<'b, 'tcx> {

for y in mutable_variables {
for x in &immutable_borrowers {
state.add(*x, y);
self.possible_borrower.add(*x, y);
}
for x in &mutable_borrowers {
state.add(*x, y);
self.possible_borrower.add(*x, y);
}
}
}
Expand Down Expand Up @@ -210,98 +163,73 @@ fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) {
}
}

/// Result of `PossibleBorrowerAnalysis`.
/// Result of `PossibleBorrowerVisitor`.
#[allow(clippy::module_name_repetitions)]
pub struct PossibleBorrowerMap<'b, 'tcx> {
body: &'b mir::Body<'tcx>,
possible_borrower: ResultsCursor<'b, 'tcx, PossibleBorrowerAnalysis<'b, 'tcx>>,
maybe_live: ResultsCursor<'b, 'tcx, MaybeStorageLive<'b>>,
pushed: BitSet<Local>,
stack: Vec<Local>,
/// Mapping `Local -> its possible borrowers`
pub map: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
maybe_live: ResultsCursor<'b, 'tcx, MaybeStorageLive<'tcx>>,
// Caches to avoid allocation of `BitSet` on every query
pub bitset: (BitSet<mir::Local>, BitSet<mir::Local>),
}

impl<'b, 'tcx> PossibleBorrowerMap<'b, 'tcx> {
pub fn new(cx: &LateContext<'tcx>, mir: &'b mir::Body<'tcx>) -> Self {
impl<'a, 'b, 'tcx> PossibleBorrowerMap<'b, 'tcx> {
pub fn new(cx: &'a LateContext<'tcx>, mir: &'b mir::Body<'tcx>) -> Self {
let possible_origin = {
let mut vis = PossibleOriginVisitor::new(mir);
vis.visit_body(mir);
vis.into_map(cx)
};
let possible_borrower = PossibleBorrowerAnalysis::new(cx.tcx, mir, possible_origin)
let maybe_storage_live_result = MaybeStorageLive::new(Cow::Owned(BitSet::new_empty(mir.local_decls.len())))
.into_engine(cx.tcx, mir)
.pass_name("possible_borrower")
.pass_name("redundant_clone")
.iterate_to_fixpoint()
.into_results_cursor(mir);
let maybe_live = MaybeStorageLive::new(Cow::Owned(BitSet::new_empty(mir.local_decls.len())))
.into_engine(cx.tcx, mir)
.pass_name("possible_borrower")
.iterate_to_fixpoint()
.into_results_cursor(mir);
PossibleBorrowerMap {
body: mir,
possible_borrower,
maybe_live,
pushed: BitSet::new_empty(mir.local_decls.len()),
stack: Vec::with_capacity(mir.local_decls.len()),
}
let mut vis = PossibleBorrowerVisitor::new(cx, mir, possible_origin);
vis.visit_body(mir);
vis.into_map(cx, maybe_storage_live_result)
}

/// Returns true if the set of borrowers of `borrowed` living at `at` includes no more than
/// `borrowers`.
/// Notes:
/// 1. It would be nice if `PossibleBorrowerMap` could store `cx` so that `at_most_borrowers`
/// would not require it to be passed in. But a `PossibleBorrowerMap` is stored in `LintPass`
/// `Dereferencing`, which outlives any `LateContext`.
/// 2. In all current uses of `at_most_borrowers`, `borrowers` is a slice of at most two
/// elements. Thus, `borrowers.contains(...)` is effectively a constant-time operation. If
/// `at_most_borrowers`'s uses were to expand beyond this, its implementation might have to be
/// adjusted.
pub fn at_most_borrowers(
/// Returns true if the set of borrowers of `borrowed` living at `at` matches with `borrowers`.
pub fn only_borrowers(&mut self, borrowers: &[mir::Local], borrowed: mir::Local, at: mir::Location) -> bool {
self.bounded_borrowers(borrowers, borrowers, borrowed, at)
}

/// Returns true if the set of borrowers of `borrowed` living at `at` includes at least `below`
/// but no more than `above`.
pub fn bounded_borrowers(
&mut self,
cx: &LateContext<'tcx>,
borrowers: &[mir::Local],
below: &[mir::Local],
above: &[mir::Local],
borrowed: mir::Local,
at: mir::Location,
) -> bool {
if is_copy(cx, self.body.local_decls[borrowed].ty) {
return true;
}

self.possible_borrower.seek_before_primary_effect(at);
self.maybe_live.seek_before_primary_effect(at);

let possible_borrower = &self.possible_borrower.get().map;
let maybe_live = &self.maybe_live;

self.pushed.clear();
self.stack.clear();
self.maybe_live.seek_after_primary_effect(at);

if let Some(borrowers) = possible_borrower.get(&borrowed) {
for b in borrowers.iter() {
if self.pushed.insert(b) {
self.stack.push(b);
}
self.bitset.0.clear();
let maybe_live = &mut self.maybe_live;
if let Some(bitset) = self.map.get(&borrowed) {
for b in bitset.iter().filter(move |b| maybe_live.contains(*b)) {
self.bitset.0.insert(b);
}
} else {
// Nothing borrows `borrowed` at `at`.
return true;
return false;
}

while let Some(borrower) = self.stack.pop() {
if maybe_live.contains(borrower) && !borrowers.contains(&borrower) {
return false;
}
self.bitset.1.clear();
for b in below {
self.bitset.1.insert(*b);
}

if let Some(borrowers) = possible_borrower.get(&borrower) {
for b in borrowers.iter() {
if self.pushed.insert(b) {
self.stack.push(b);
}
}
}
if !self.bitset.0.superset(&self.bitset.1) {
return false;
}

for b in above {
self.bitset.0.remove(*b);
}

true
self.bitset.0.is_empty()
}

pub fn local_is_alive_at(&mut self, local: mir::Local, at: mir::Location) -> bool {
Expand Down
Loading

0 comments on commit 7f27e2e

Please sign in to comment.