Auto merge of #9386 - smoelius:further-enhance-needless-borrow, r=Jarcho
Further enhance `needless_borrow`, mildly refactor `redundant_clone` This PR does the following: * Moves some code from `redundant_clone` into a new `clippy_utils` module called `mir`, and wraps that code in a function called `dropped_without_further_use`. * Relaxes the "is copyable" condition condition from #9136 by also suggesting to remove borrows from values dropped without further use. The changes involve the just mentioned function. * Separates `redundant_clone` into modules. Strictly speaking, the last bullet is independent of the others. `redundant_clone` is somewhat hairy, IMO. Separating it into modules makes it slightly less so, by helping to delineate what depends upon what. I've tried to break everything up into digestible commits. r? `@Jarcho` (`@Jarcho` I hope you don't mind.) changelog: continuation of #9136
This commit is contained in:
commit
272bbfb857
@ -49,7 +49,7 @@ fn mtime(path: impl AsRef<Path>) -> SystemTime {
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.flatten()
|
||||
.map(|entry| mtime(&entry.path()))
|
||||
.map(|entry| mtime(entry.path()))
|
||||
.max()
|
||||
.unwrap_or(SystemTime::UNIX_EPOCH)
|
||||
} else {
|
||||
|
@ -128,7 +128,7 @@ fn generate_lint_files(
|
||||
for (lint_group, lints) in Lint::by_lint_group(usable_lints.into_iter().chain(internal_lints)) {
|
||||
let content = gen_lint_group_list(&lint_group, lints.iter());
|
||||
process_file(
|
||||
&format!("clippy_lints/src/lib.register_{lint_group}.rs"),
|
||||
format!("clippy_lints/src/lib.register_{lint_group}.rs"),
|
||||
update_mode,
|
||||
&content,
|
||||
);
|
||||
|
@ -1,4 +1,5 @@
|
||||
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
|
||||
use clippy_utils::mir::{enclosing_mir, expr_local, local_assignments, used_exactly_once, PossibleBorrowerMap};
|
||||
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
|
||||
use clippy_utils::sugg::has_enclosing_paren;
|
||||
use clippy_utils::ty::{expr_sig, is_copy, peel_mid_ty_refs, ty_sig, variant_of_res};
|
||||
@ -11,13 +12,16 @@
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::{walk_ty, Visitor};
|
||||
use rustc_hir::{
|
||||
self as hir, def_id::DefId, BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy,
|
||||
GenericArg, HirId, ImplItem, ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind,
|
||||
Path, QPath, TraitItem, TraitItemKind, TyKind, UnOp,
|
||||
self as hir,
|
||||
def_id::{DefId, LocalDefId},
|
||||
BindingAnnotation, Body, BodyId, BorrowKind, Closure, Expr, ExprKind, FnRetTy, GenericArg, HirId, ImplItem,
|
||||
ImplItemKind, Item, ItemKind, Local, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, TraitItem,
|
||||
TraitItemKind, TyKind, UnOp,
|
||||
};
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_infer::infer::TyCtxtInferExt;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::mir::{Rvalue, StatementKind};
|
||||
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability};
|
||||
use rustc_middle::ty::{
|
||||
self, Binder, BoundVariableKind, EarlyBinder, FnSig, GenericArgKind, List, ParamTy, PredicateKind,
|
||||
@ -141,7 +145,7 @@
|
||||
"dereferencing when the compiler would automatically dereference"
|
||||
}
|
||||
|
||||
impl_lint_pass!(Dereferencing => [
|
||||
impl_lint_pass!(Dereferencing<'_> => [
|
||||
EXPLICIT_DEREF_METHODS,
|
||||
NEEDLESS_BORROW,
|
||||
REF_BINDING_TO_REFERENCE,
|
||||
@ -149,7 +153,7 @@
|
||||
]);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Dereferencing {
|
||||
pub struct Dereferencing<'tcx> {
|
||||
state: Option<(State, StateData)>,
|
||||
|
||||
// While parsing a `deref` method call in ufcs form, the path to the function is itself an
|
||||
@ -170,11 +174,16 @@ pub struct Dereferencing {
|
||||
/// e.g. `m!(x) | Foo::Bar(ref x)`
|
||||
ref_locals: FxIndexMap<HirId, Option<RefPat>>,
|
||||
|
||||
/// Stack of (body owner, `PossibleBorrowerMap`) pairs. Used by
|
||||
/// `needless_borrow_impl_arg_position` to determine when a borrowed expression can instead
|
||||
/// be moved.
|
||||
possible_borrowers: Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
|
||||
|
||||
// `IntoIterator` for arrays requires Rust 1.53.
|
||||
msrv: Option<RustcVersion>,
|
||||
}
|
||||
|
||||
impl Dereferencing {
|
||||
impl<'tcx> Dereferencing<'tcx> {
|
||||
#[must_use]
|
||||
pub fn new(msrv: Option<RustcVersion>) -> Self {
|
||||
Self {
|
||||
@ -244,7 +253,7 @@ struct RefPat {
|
||||
hir_id: HirId,
|
||||
}
|
||||
|
||||
impl<'tcx> LateLintPass<'tcx> for Dereferencing {
|
||||
impl<'tcx> LateLintPass<'tcx> for Dereferencing<'tcx> {
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
// Skip path expressions from deref calls. e.g. `Deref::deref(e)`
|
||||
@ -278,7 +287,7 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
match (self.state.take(), kind) {
|
||||
(None, kind) => {
|
||||
let expr_ty = typeck.expr_ty(expr);
|
||||
let (position, adjustments) = walk_parents(cx, expr, self.msrv);
|
||||
let (position, adjustments) = walk_parents(cx, &mut self.possible_borrowers, expr, self.msrv);
|
||||
match kind {
|
||||
RefOp::Deref => {
|
||||
if let Position::FieldAccess {
|
||||
@ -550,6 +559,12 @@ fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
|
||||
}
|
||||
|
||||
fn check_body_post(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
|
||||
if self.possible_borrowers.last().map_or(false, |&(local_def_id, _)| {
|
||||
local_def_id == cx.tcx.hir().body_owner_def_id(body.id())
|
||||
}) {
|
||||
self.possible_borrowers.pop();
|
||||
}
|
||||
|
||||
if Some(body.id()) == self.current_body {
|
||||
for pat in self.ref_locals.drain(..).filter_map(|(_, x)| x) {
|
||||
let replacements = pat.replacements;
|
||||
@ -682,6 +697,7 @@ fn precedence(self) -> i8 {
|
||||
#[expect(clippy::too_many_lines)]
|
||||
fn walk_parents<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
|
||||
e: &'tcx Expr<'_>,
|
||||
msrv: Option<RustcVersion>,
|
||||
) -> (Position, &'tcx [Adjustment<'tcx>]) {
|
||||
@ -796,7 +812,16 @@ fn walk_parents<'tcx>(
|
||||
Some(hir_ty) => binding_ty_auto_deref_stability(cx, hir_ty, precedence, ty.bound_vars()),
|
||||
None => {
|
||||
if let ty::Param(param_ty) = ty.skip_binder().kind() {
|
||||
needless_borrow_impl_arg_position(cx, parent, i, *param_ty, e, precedence, msrv)
|
||||
needless_borrow_impl_arg_position(
|
||||
cx,
|
||||
possible_borrowers,
|
||||
parent,
|
||||
i,
|
||||
*param_ty,
|
||||
e,
|
||||
precedence,
|
||||
msrv,
|
||||
)
|
||||
} else {
|
||||
ty_auto_deref_stability(cx, cx.tcx.erase_late_bound_regions(ty), precedence)
|
||||
.position_for_arg()
|
||||
@ -844,7 +869,16 @@ fn walk_parents<'tcx>(
|
||||
args.iter().position(|arg| arg.hir_id == child_id).map(|i| {
|
||||
let ty = cx.tcx.fn_sig(id).skip_binder().inputs()[i + 1];
|
||||
if let ty::Param(param_ty) = ty.kind() {
|
||||
needless_borrow_impl_arg_position(cx, parent, i + 1, *param_ty, e, precedence, msrv)
|
||||
needless_borrow_impl_arg_position(
|
||||
cx,
|
||||
possible_borrowers,
|
||||
parent,
|
||||
i + 1,
|
||||
*param_ty,
|
||||
e,
|
||||
precedence,
|
||||
msrv,
|
||||
)
|
||||
} else {
|
||||
ty_auto_deref_stability(
|
||||
cx,
|
||||
@ -1018,8 +1052,10 @@ fn visit_generic_arg(&mut self, arg: &GenericArg<'_>) {
|
||||
// If the conditions are met, returns `Some(Position::ImplArg(..))`; otherwise, returns `None`.
|
||||
// The "is copyable" condition is to avoid the case where removing the `&` means `e` would have to
|
||||
// be moved, but it cannot be.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn needless_borrow_impl_arg_position<'tcx>(
|
||||
cx: &LateContext<'tcx>,
|
||||
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
|
||||
parent: &Expr<'tcx>,
|
||||
arg_index: usize,
|
||||
param_ty: ParamTy,
|
||||
@ -1082,10 +1118,13 @@ fn needless_borrow_impl_arg_position<'tcx>(
|
||||
// elements are modified each time `check_referent` is called.
|
||||
let mut substs_with_referent_ty = substs_with_expr_ty.to_vec();
|
||||
|
||||
let mut check_referent = |referent| {
|
||||
let mut check_reference_and_referent = |reference, referent| {
|
||||
let referent_ty = cx.typeck_results().expr_ty(referent);
|
||||
|
||||
if !is_copy(cx, referent_ty) {
|
||||
if !is_copy(cx, referent_ty)
|
||||
&& (referent_ty.has_significant_drop(cx.tcx, cx.param_env)
|
||||
|| !referent_used_exactly_once(cx, possible_borrowers, reference))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1127,7 +1166,7 @@ fn needless_borrow_impl_arg_position<'tcx>(
|
||||
|
||||
let mut needless_borrow = false;
|
||||
while let ExprKind::AddrOf(_, _, referent) = expr.kind {
|
||||
if !check_referent(referent) {
|
||||
if !check_reference_and_referent(expr, referent) {
|
||||
break;
|
||||
}
|
||||
expr = referent;
|
||||
@ -1155,6 +1194,36 @@ fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
fn referent_used_exactly_once<'a, 'tcx>(
|
||||
cx: &'a LateContext<'tcx>,
|
||||
possible_borrowers: &mut Vec<(LocalDefId, PossibleBorrowerMap<'tcx, 'tcx>)>,
|
||||
reference: &Expr<'tcx>,
|
||||
) -> bool {
|
||||
let mir = enclosing_mir(cx.tcx, reference.hir_id);
|
||||
if let Some(local) = expr_local(cx.tcx, reference)
|
||||
&& let [location] = *local_assignments(mir, local).as_slice()
|
||||
&& let StatementKind::Assign(box (_, Rvalue::Ref(_, _, place))) =
|
||||
mir.basic_blocks[location.block].statements[location.statement_index].kind
|
||||
&& !place.has_deref()
|
||||
{
|
||||
let body_owner_local_def_id = cx.tcx.hir().enclosing_body_owner(reference.hir_id);
|
||||
if possible_borrowers
|
||||
.last()
|
||||
.map_or(true, |&(local_def_id, _)| local_def_id != body_owner_local_def_id)
|
||||
{
|
||||
possible_borrowers.push((body_owner_local_def_id, PossibleBorrowerMap::new(cx, mir)));
|
||||
}
|
||||
let possible_borrower = &mut possible_borrowers.last_mut().unwrap().1;
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// Iteratively replaces `param_ty` with `new_ty` in `substs`, and similarly for each resulting
|
||||
// projected type that is a type parameter. Returns `false` if replacing the types would have an
|
||||
// effect on the function signature beyond substituting `new_ty` for `param_ty`.
|
||||
@ -1439,8 +1508,8 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, state: State, data
|
||||
}
|
||||
}
|
||||
|
||||
impl Dereferencing {
|
||||
fn check_local_usage<'tcx>(&mut self, cx: &LateContext<'tcx>, e: &Expr<'tcx>, local: HirId) {
|
||||
impl<'tcx> Dereferencing<'tcx> {
|
||||
fn check_local_usage(&mut self, cx: &LateContext<'tcx>, e: &Expr<'tcx>, local: HirId) {
|
||||
if let Some(outer_pat) = self.ref_locals.get_mut(&local) {
|
||||
if let Some(pat) = outer_pat {
|
||||
// Check for auto-deref
|
||||
|
@ -38,7 +38,6 @@
|
||||
extern crate rustc_lexer;
|
||||
extern crate rustc_lint;
|
||||
extern crate rustc_middle;
|
||||
extern crate rustc_mir_dataflow;
|
||||
extern crate rustc_parse;
|
||||
extern crate rustc_session;
|
||||
extern crate rustc_span;
|
||||
@ -418,7 +417,7 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Se
|
||||
|
||||
let msrv = conf.msrv.as_ref().and_then(|s| {
|
||||
parse_msrv(s, None, None).or_else(|| {
|
||||
sess.err(&format!(
|
||||
sess.err(format!(
|
||||
"error reading Clippy's configuration file. `{s}` is not a valid Rust version"
|
||||
));
|
||||
None
|
||||
@ -434,7 +433,7 @@ fn read_msrv(conf: &Conf, sess: &Session) -> Option<RustcVersion> {
|
||||
.and_then(|v| parse_msrv(&v, None, None));
|
||||
let clippy_msrv = conf.msrv.as_ref().and_then(|s| {
|
||||
parse_msrv(s, None, None).or_else(|| {
|
||||
sess.err(&format!(
|
||||
sess.err(format!(
|
||||
"error reading Clippy's configuration file. `{s}` is not a valid Rust version"
|
||||
));
|
||||
None
|
||||
@ -445,7 +444,7 @@ fn read_msrv(conf: &Conf, sess: &Session) -> Option<RustcVersion> {
|
||||
if let Some(clippy_msrv) = clippy_msrv {
|
||||
// if both files have an msrv, let's compare them and emit a warning if they differ
|
||||
if clippy_msrv != cargo_msrv {
|
||||
sess.warn(&format!(
|
||||
sess.warn(format!(
|
||||
"the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`"
|
||||
));
|
||||
}
|
||||
@ -474,7 +473,7 @@ pub fn read_conf(sess: &Session) -> Conf {
|
||||
let TryConf { conf, errors, warnings } = utils::conf::read(&file_name);
|
||||
// all conf errors are non-fatal, we just use the default conf in case of error
|
||||
for error in errors {
|
||||
sess.err(&format!(
|
||||
sess.err(format!(
|
||||
"error reading Clippy's configuration file `{}`: {}",
|
||||
file_name.display(),
|
||||
format_error(error)
|
||||
|
@ -266,7 +266,7 @@ fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
|
||||
.iter()
|
||||
.find(|b| b.0 == brace)
|
||||
.map(|(o, c)| ((*o).to_owned(), (*c).to_owned()))
|
||||
.ok_or_else(|| de::Error::custom(&format!("expected one of `(`, `{{`, `[` found `{brace}`")))?,
|
||||
.ok_or_else(|| de::Error::custom(format!("expected one of `(`, `{{`, `[` found `{brace}`")))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,18 @@
|
||||
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
|
||||
use clippy_utils::mir::{visit_local_usage, LocalUsage, PossibleBorrowerMap};
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use clippy_utils::ty::{has_drop, is_copy, is_type_diagnostic_item, walk_ptrs_ty_depth};
|
||||
use clippy_utils::{fn_has_unsatisfiable_preds, match_def_path, paths};
|
||||
use if_chain::if_chain;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{def_id, Body, FnDecl, HirId};
|
||||
use rustc_index::bit_set::{BitSet, HybridBitSet};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::mir::{
|
||||
self, traversal,
|
||||
visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor as _},
|
||||
Mutability,
|
||||
};
|
||||
use rustc_middle::ty::{self, visit::TypeVisitor, Ty};
|
||||
use rustc_mir_dataflow::{Analysis, AnalysisDomain, CallReturnPlaces, GenKill, GenKillAnalysis, ResultsCursor};
|
||||
use rustc_middle::mir;
|
||||
use rustc_middle::ty::{self, Ty};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::source_map::{BytePos, Span};
|
||||
use rustc_span::sym;
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
macro_rules! unwrap_or_continue {
|
||||
($x:expr) => {
|
||||
@ -89,21 +82,7 @@ fn check_fn(
|
||||
|
||||
let mir = cx.tcx.optimized_mir(def_id.to_def_id());
|
||||
|
||||
let possible_origin = {
|
||||
let mut vis = PossibleOriginVisitor::new(mir);
|
||||
vis.visit_body(mir);
|
||||
vis.into_map(cx)
|
||||
};
|
||||
let maybe_storage_live_result = MaybeStorageLive
|
||||
.into_engine(cx.tcx, mir)
|
||||
.pass_name("redundant_clone")
|
||||
.iterate_to_fixpoint()
|
||||
.into_results_cursor(mir);
|
||||
let mut possible_borrower = {
|
||||
let mut vis = PossibleBorrowerVisitor::new(cx, mir, possible_origin);
|
||||
vis.visit_body(mir);
|
||||
vis.into_map(cx, maybe_storage_live_result)
|
||||
};
|
||||
let mut possible_borrower = PossibleBorrowerMap::new(cx, mir);
|
||||
|
||||
for (bb, bbdata) in mir.basic_blocks.iter_enumerated() {
|
||||
let terminator = bbdata.terminator();
|
||||
@ -374,403 +353,40 @@ struct CloneUsage {
|
||||
/// Whether the clone value is mutated.
|
||||
clone_consumed_or_mutated: bool,
|
||||
}
|
||||
|
||||
fn visit_clone_usage(cloned: mir::Local, clone: mir::Local, mir: &mir::Body<'_>, bb: mir::BasicBlock) -> CloneUsage {
|
||||
struct V {
|
||||
cloned: mir::Local,
|
||||
clone: mir::Local,
|
||||
result: CloneUsage,
|
||||
}
|
||||
impl<'tcx> mir::visit::Visitor<'tcx> for V {
|
||||
fn visit_basic_block_data(&mut self, block: mir::BasicBlock, data: &mir::BasicBlockData<'tcx>) {
|
||||
let statements = &data.statements;
|
||||
for (statement_index, statement) in statements.iter().enumerate() {
|
||||
self.visit_statement(statement, mir::Location { block, statement_index });
|
||||
}
|
||||
|
||||
self.visit_terminator(
|
||||
data.terminator(),
|
||||
mir::Location {
|
||||
block,
|
||||
statement_index: statements.len(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn visit_place(&mut self, place: &mir::Place<'tcx>, ctx: PlaceContext, loc: mir::Location) {
|
||||
let local = place.local;
|
||||
|
||||
if local == self.cloned
|
||||
&& !matches!(
|
||||
ctx,
|
||||
PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_)
|
||||
)
|
||||
{
|
||||
self.result.cloned_used = true;
|
||||
self.result.cloned_consume_or_mutate_loc = self.result.cloned_consume_or_mutate_loc.or_else(|| {
|
||||
matches!(
|
||||
ctx,
|
||||
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
|
||||
| PlaceContext::MutatingUse(MutatingUseContext::Borrow)
|
||||
)
|
||||
.then(|| loc)
|
||||
});
|
||||
} else if local == self.clone {
|
||||
match ctx {
|
||||
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
|
||||
| PlaceContext::MutatingUse(MutatingUseContext::Borrow) => {
|
||||
self.result.clone_consumed_or_mutated = true;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let init = CloneUsage {
|
||||
cloned_used: false,
|
||||
cloned_consume_or_mutate_loc: None,
|
||||
// Consider non-temporary clones consumed.
|
||||
// TODO: Actually check for mutation of non-temporaries.
|
||||
clone_consumed_or_mutated: mir.local_kind(clone) != mir::LocalKind::Temp,
|
||||
};
|
||||
traversal::ReversePostorder::new(mir, bb)
|
||||
.skip(1)
|
||||
.fold(init, |usage, (tbb, tdata)| {
|
||||
// Short-circuit
|
||||
if (usage.cloned_used && usage.clone_consumed_or_mutated) ||
|
||||
// Give up on loops
|
||||
tdata.terminator().successors().any(|s| s == bb)
|
||||
{
|
||||
return CloneUsage {
|
||||
cloned_used: true,
|
||||
clone_consumed_or_mutated: true,
|
||||
..usage
|
||||
};
|
||||
}
|
||||
|
||||
let mut v = V {
|
||||
cloned,
|
||||
clone,
|
||||
result: usage,
|
||||
};
|
||||
v.visit_basic_block_data(tbb, tdata);
|
||||
v.result
|
||||
})
|
||||
}
|
||||
|
||||
/// Determines liveness of each local purely based on `StorageLive`/`Dead`.
|
||||
#[derive(Copy, Clone)]
|
||||
struct MaybeStorageLive;
|
||||
|
||||
impl<'tcx> AnalysisDomain<'tcx> for MaybeStorageLive {
|
||||
type Domain = BitSet<mir::Local>;
|
||||
const NAME: &'static str = "maybe_storage_live";
|
||||
|
||||
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
|
||||
// bottom = dead
|
||||
BitSet::new_empty(body.local_decls.len())
|
||||
}
|
||||
|
||||
fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
|
||||
for arg in body.args_iter() {
|
||||
state.insert(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> GenKillAnalysis<'tcx> for MaybeStorageLive {
|
||||
type Idx = mir::Local;
|
||||
|
||||
fn statement_effect(&self, trans: &mut impl GenKill<Self::Idx>, stmt: &mir::Statement<'tcx>, _: mir::Location) {
|
||||
match stmt.kind {
|
||||
mir::StatementKind::StorageLive(l) => trans.gen(l),
|
||||
mir::StatementKind::StorageDead(l) => trans.kill(l),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn terminator_effect(
|
||||
&self,
|
||||
_trans: &mut impl GenKill<Self::Idx>,
|
||||
_terminator: &mir::Terminator<'tcx>,
|
||||
_loc: mir::Location,
|
||||
) {
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
&self,
|
||||
_trans: &mut impl GenKill<Self::Idx>,
|
||||
_block: mir::BasicBlock,
|
||||
_return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
) {
|
||||
// Nothing to do when a call returns successfully
|
||||
}
|
||||
}
|
||||
|
||||
/// Collects the possible borrowers of each local.
|
||||
/// For example, `b = &a; c = &a;` will make `b` and (transitively) `c`
|
||||
/// possible borrowers of `a`.
|
||||
struct PossibleBorrowerVisitor<'a, 'tcx> {
|
||||
possible_borrower: TransitiveRelation,
|
||||
body: &'a mir::Body<'tcx>,
|
||||
cx: &'a LateContext<'tcx>,
|
||||
possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> PossibleBorrowerVisitor<'a, 'tcx> {
|
||||
fn new(
|
||||
cx: &'a LateContext<'tcx>,
|
||||
body: &'a mir::Body<'tcx>,
|
||||
possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
possible_borrower: TransitiveRelation::default(),
|
||||
cx,
|
||||
body,
|
||||
possible_origin,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_map(
|
||||
self,
|
||||
cx: &LateContext<'tcx>,
|
||||
maybe_live: ResultsCursor<'tcx, 'tcx, MaybeStorageLive>,
|
||||
) -> PossibleBorrowerMap<'a, '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;
|
||||
}
|
||||
|
||||
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() {
|
||||
map.insert(row, borrowers);
|
||||
}
|
||||
}
|
||||
|
||||
let bs = BitSet::new_empty(self.body.local_decls.len());
|
||||
PossibleBorrowerMap {
|
||||
map,
|
||||
maybe_live,
|
||||
bitset: (bs.clone(), bs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleBorrowerVisitor<'a, '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);
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_terminator(&mut self, terminator: &mir::Terminator<'_>, _loc: mir::Location) {
|
||||
if let mir::TerminatorKind::Call {
|
||||
args,
|
||||
destination: mir::Place { local: dest, .. },
|
||||
..
|
||||
} = &terminator.kind
|
||||
{
|
||||
// TODO add doc
|
||||
// If the call returns something with lifetimes,
|
||||
// let's conservatively assume the returned value contains lifetime of all the arguments.
|
||||
// For example, given `let y: Foo<'a> = foo(x)`, `y` is considered to be a possible borrower of `x`.
|
||||
|
||||
let mut immutable_borrowers = vec![];
|
||||
let mut mutable_borrowers = vec![];
|
||||
|
||||
for op in args {
|
||||
match op {
|
||||
mir::Operand::Copy(p) | mir::Operand::Move(p) => {
|
||||
if let ty::Ref(_, _, Mutability::Mut) = self.body.local_decls[p.local].ty.kind() {
|
||||
mutable_borrowers.push(p.local);
|
||||
} else {
|
||||
immutable_borrowers.push(p.local);
|
||||
}
|
||||
},
|
||||
mir::Operand::Constant(..) => (),
|
||||
}
|
||||
}
|
||||
|
||||
let mut mutable_variables: Vec<mir::Local> = mutable_borrowers
|
||||
.iter()
|
||||
.filter_map(|r| self.possible_origin.get(r))
|
||||
.flat_map(HybridBitSet::iter)
|
||||
.collect();
|
||||
|
||||
if ContainsRegion.visit_ty(self.body.local_decls[*dest].ty).is_break() {
|
||||
mutable_variables.push(*dest);
|
||||
}
|
||||
|
||||
for y in mutable_variables {
|
||||
for x in &immutable_borrowers {
|
||||
self.possible_borrower.add(*x, y);
|
||||
}
|
||||
for x in &mutable_borrowers {
|
||||
self.possible_borrower.add(*x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect possible borrowed for every `&mut` local.
|
||||
/// For example, `_1 = &mut _2` generate _1: {_2,...}
|
||||
/// Known Problems: not sure all borrowed are tracked
|
||||
struct PossibleOriginVisitor<'a, 'tcx> {
|
||||
possible_origin: TransitiveRelation,
|
||||
body: &'a mir::Body<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> PossibleOriginVisitor<'a, 'tcx> {
|
||||
fn new(body: &'a mir::Body<'tcx>) -> Self {
|
||||
Self {
|
||||
possible_origin: TransitiveRelation::default(),
|
||||
body,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_map(self, cx: &LateContext<'tcx>) -> FxHashMap<mir::Local, HybridBitSet<mir::Local>> {
|
||||
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;
|
||||
}
|
||||
|
||||
let mut borrowers = self.possible_origin.reachable_from(row, self.body.local_decls.len());
|
||||
borrowers.remove(mir::Local::from_usize(0));
|
||||
if !borrowers.is_empty() {
|
||||
map.insert(row, borrowers);
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleOriginVisitor<'a, 'tcx> {
|
||||
fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _location: mir::Location) {
|
||||
let lhs = place.local;
|
||||
match rvalue {
|
||||
// Only consider `&mut`, which can modify origin place
|
||||
mir::Rvalue::Ref(_, rustc_middle::mir::BorrowKind::Mut { .. }, borrowed) |
|
||||
// _2: &mut _;
|
||||
// _3 = move _2
|
||||
mir::Rvalue::Use(mir::Operand::Move(borrowed)) |
|
||||
// _3 = move _2 as &mut _;
|
||||
mir::Rvalue::Cast(_, mir::Operand::Move(borrowed), _)
|
||||
=> {
|
||||
self.possible_origin.add(lhs, borrowed.local);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContainsRegion;
|
||||
|
||||
impl TypeVisitor<'_> for ContainsRegion {
|
||||
type BreakTy = ();
|
||||
|
||||
fn visit_region(&mut self, _: ty::Region<'_>) -> ControlFlow<Self::BreakTy> {
|
||||
ControlFlow::BREAK
|
||||
}
|
||||
}
|
||||
|
||||
fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) {
|
||||
use rustc_middle::mir::Rvalue::{Aggregate, BinaryOp, Cast, CheckedBinaryOp, Repeat, UnaryOp, Use};
|
||||
|
||||
let mut visit_op = |op: &mir::Operand<'_>| match op {
|
||||
mir::Operand::Copy(p) | mir::Operand::Move(p) => visit(p.local),
|
||||
mir::Operand::Constant(..) => (),
|
||||
};
|
||||
|
||||
match rvalue {
|
||||
Use(op) | Repeat(op, _) | Cast(_, op, _) | UnaryOp(_, op) => visit_op(op),
|
||||
Aggregate(_, ops) => ops.iter().for_each(visit_op),
|
||||
BinaryOp(_, box (lhs, rhs)) | CheckedBinaryOp(_, box (lhs, rhs)) => {
|
||||
visit_op(lhs);
|
||||
visit_op(rhs);
|
||||
if let Some((
|
||||
LocalUsage {
|
||||
local_use_locs: cloned_use_locs,
|
||||
local_consume_or_mutate_locs: cloned_consume_or_mutate_locs,
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of `PossibleBorrowerVisitor`.
|
||||
struct PossibleBorrowerMap<'a, 'tcx> {
|
||||
/// Mapping `Local -> its possible borrowers`
|
||||
map: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
|
||||
maybe_live: ResultsCursor<'a, 'tcx, MaybeStorageLive>,
|
||||
// Caches to avoid allocation of `BitSet` on every query
|
||||
bitset: (BitSet<mir::Local>, BitSet<mir::Local>),
|
||||
}
|
||||
|
||||
impl PossibleBorrowerMap<'_, '_> {
|
||||
/// Returns true if the set of borrowers of `borrowed` living at `at` matches with `borrowers`.
|
||||
fn only_borrowers(&mut self, borrowers: &[mir::Local], borrowed: mir::Local, at: mir::Location) -> bool {
|
||||
self.maybe_live.seek_after_primary_effect(at);
|
||||
|
||||
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 {
|
||||
return false;
|
||||
LocalUsage {
|
||||
local_use_locs: _,
|
||||
local_consume_or_mutate_locs: clone_consume_or_mutate_locs,
|
||||
},
|
||||
)) = visit_local_usage(
|
||||
&[cloned, clone],
|
||||
mir,
|
||||
mir::Location {
|
||||
block: bb,
|
||||
statement_index: mir.basic_blocks[bb].statements.len(),
|
||||
},
|
||||
)
|
||||
.map(|mut vec| (vec.remove(0), vec.remove(0)))
|
||||
{
|
||||
CloneUsage {
|
||||
cloned_used: !cloned_use_locs.is_empty(),
|
||||
cloned_consume_or_mutate_loc: cloned_consume_or_mutate_locs.first().copied(),
|
||||
// Consider non-temporary clones consumed.
|
||||
// TODO: Actually check for mutation of non-temporaries.
|
||||
clone_consumed_or_mutated: mir.local_kind(clone) != mir::LocalKind::Temp
|
||||
|| !clone_consume_or_mutate_locs.is_empty(),
|
||||
}
|
||||
|
||||
self.bitset.1.clear();
|
||||
for b in borrowers {
|
||||
self.bitset.1.insert(*b);
|
||||
} else {
|
||||
CloneUsage {
|
||||
cloned_used: true,
|
||||
cloned_consume_or_mutate_loc: None,
|
||||
clone_consumed_or_mutated: true,
|
||||
}
|
||||
|
||||
self.bitset.0 == self.bitset.1
|
||||
}
|
||||
|
||||
fn local_is_alive_at(&mut self, local: mir::Local, at: mir::Location) -> bool {
|
||||
self.maybe_live.seek_after_primary_effect(at);
|
||||
self.maybe_live.contains(local)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct TransitiveRelation {
|
||||
relations: FxHashMap<mir::Local, Vec<mir::Local>>,
|
||||
}
|
||||
impl TransitiveRelation {
|
||||
fn add(&mut self, a: mir::Local, b: mir::Local) {
|
||||
self.relations.entry(a).or_default().push(b);
|
||||
}
|
||||
|
||||
fn reachable_from(&self, a: mir::Local, domain_size: usize) -> HybridBitSet<mir::Local> {
|
||||
let mut seen = HybridBitSet::new_empty(domain_size);
|
||||
let mut stack = vec![a];
|
||||
while let Some(u) = stack.pop() {
|
||||
if let Some(edges) = self.relations.get(&u) {
|
||||
for &v in edges {
|
||||
if seen.insert(v) {
|
||||
stack.push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
seen
|
||||
}
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ pub fn get_unique_inner_attr(sess: &Session, attrs: &[ast::Attribute], name: &'s
|
||||
.emit();
|
||||
},
|
||||
ast::AttrStyle::Outer => {
|
||||
sess.span_err(attr.span, &format!("`{name}` cannot be an outer attribute"));
|
||||
sess.span_err(attr.span, format!("`{name}` cannot be an outer attribute"));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,12 @@
|
||||
extern crate rustc_errors;
|
||||
extern crate rustc_hir;
|
||||
extern crate rustc_hir_analysis;
|
||||
extern crate rustc_index;
|
||||
extern crate rustc_infer;
|
||||
extern crate rustc_lexer;
|
||||
extern crate rustc_lint;
|
||||
extern crate rustc_middle;
|
||||
extern crate rustc_mir_dataflow;
|
||||
extern crate rustc_parse_format;
|
||||
extern crate rustc_session;
|
||||
extern crate rustc_span;
|
||||
@ -48,6 +50,7 @@
|
||||
pub mod higher;
|
||||
mod hir_utils;
|
||||
pub mod macros;
|
||||
pub mod mir;
|
||||
pub mod msrvs;
|
||||
pub mod numeric_literal;
|
||||
pub mod paths;
|
||||
@ -122,7 +125,7 @@ pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Opt
|
||||
return Some(version);
|
||||
} else if let Some(sess) = sess {
|
||||
if let Some(span) = span {
|
||||
sess.span_err(span, &format!("`{msrv}` is not a valid Rust version"));
|
||||
sess.span_err(span, format!("`{msrv}` is not a valid Rust version"));
|
||||
}
|
||||
}
|
||||
None
|
||||
|
52
clippy_utils/src/mir/maybe_storage_live.rs
Normal file
52
clippy_utils/src/mir/maybe_storage_live.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use rustc_index::bit_set::BitSet;
|
||||
use rustc_middle::mir;
|
||||
use rustc_mir_dataflow::{AnalysisDomain, CallReturnPlaces, GenKill, GenKillAnalysis};
|
||||
|
||||
/// Determines liveness of each local purely based on `StorageLive`/`Dead`.
|
||||
#[derive(Copy, Clone)]
|
||||
pub(super) struct MaybeStorageLive;
|
||||
|
||||
impl<'tcx> AnalysisDomain<'tcx> for MaybeStorageLive {
|
||||
type Domain = BitSet<mir::Local>;
|
||||
const NAME: &'static str = "maybe_storage_live";
|
||||
|
||||
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
|
||||
// bottom = dead
|
||||
BitSet::new_empty(body.local_decls.len())
|
||||
}
|
||||
|
||||
fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
|
||||
for arg in body.args_iter() {
|
||||
state.insert(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> GenKillAnalysis<'tcx> for MaybeStorageLive {
|
||||
type Idx = mir::Local;
|
||||
|
||||
fn statement_effect(&self, trans: &mut impl GenKill<Self::Idx>, stmt: &mir::Statement<'tcx>, _: mir::Location) {
|
||||
match stmt.kind {
|
||||
mir::StatementKind::StorageLive(l) => trans.gen(l),
|
||||
mir::StatementKind::StorageDead(l) => trans.kill(l),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn terminator_effect(
|
||||
&self,
|
||||
_trans: &mut impl GenKill<Self::Idx>,
|
||||
_terminator: &mir::Terminator<'tcx>,
|
||||
_loc: mir::Location,
|
||||
) {
|
||||
}
|
||||
|
||||
fn call_return_effect(
|
||||
&self,
|
||||
_trans: &mut impl GenKill<Self::Idx>,
|
||||
_block: mir::BasicBlock,
|
||||
_return_places: CallReturnPlaces<'_, 'tcx>,
|
||||
) {
|
||||
// Nothing to do when a call returns successfully
|
||||
}
|
||||
}
|
165
clippy_utils/src/mir/mod.rs
Normal file
165
clippy_utils/src/mir/mod.rs
Normal file
@ -0,0 +1,165 @@
|
||||
use rustc_hir::{Expr, HirId};
|
||||
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
|
||||
use rustc_middle::mir::{
|
||||
traversal, Body, InlineAsmOperand, Local, Location, Place, StatementKind, TerminatorKind, START_BLOCK,
|
||||
};
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
|
||||
mod maybe_storage_live;
|
||||
|
||||
mod possible_borrower;
|
||||
pub use possible_borrower::PossibleBorrowerMap;
|
||||
|
||||
mod possible_origin;
|
||||
|
||||
mod transitive_relation;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct LocalUsage {
|
||||
/// The locations where the local is used, if any.
|
||||
pub local_use_locs: Vec<Location>,
|
||||
/// The locations where the local is consumed or mutated, if any.
|
||||
pub local_consume_or_mutate_locs: Vec<Location>,
|
||||
}
|
||||
|
||||
pub fn visit_local_usage(locals: &[Local], mir: &Body<'_>, location: Location) -> Option<Vec<LocalUsage>> {
|
||||
let init = vec![
|
||||
LocalUsage {
|
||||
local_use_locs: Vec::new(),
|
||||
local_consume_or_mutate_locs: Vec::new(),
|
||||
};
|
||||
locals.len()
|
||||
];
|
||||
|
||||
traversal::ReversePostorder::new(mir, location.block).try_fold(init, |usage, (tbb, tdata)| {
|
||||
// Give up on loops
|
||||
if tdata.terminator().successors().any(|s| s == location.block) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut v = V {
|
||||
locals,
|
||||
location,
|
||||
results: usage,
|
||||
};
|
||||
v.visit_basic_block_data(tbb, tdata);
|
||||
Some(v.results)
|
||||
})
|
||||
}
|
||||
|
||||
struct V<'a> {
|
||||
locals: &'a [Local],
|
||||
location: Location,
|
||||
results: Vec<LocalUsage>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> Visitor<'tcx> for V<'a> {
|
||||
fn visit_place(&mut self, place: &Place<'tcx>, ctx: PlaceContext, loc: Location) {
|
||||
if loc.block == self.location.block && loc.statement_index <= self.location.statement_index {
|
||||
return;
|
||||
}
|
||||
|
||||
let local = place.local;
|
||||
|
||||
for (i, self_local) in self.locals.iter().enumerate() {
|
||||
if local == *self_local {
|
||||
if !matches!(
|
||||
ctx,
|
||||
PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_)
|
||||
) {
|
||||
self.results[i].local_use_locs.push(loc);
|
||||
}
|
||||
if matches!(
|
||||
ctx,
|
||||
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
|
||||
| PlaceContext::MutatingUse(MutatingUseContext::Borrow)
|
||||
) {
|
||||
self.results[i].local_consume_or_mutate_locs.push(loc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience wrapper around `visit_local_usage`.
|
||||
pub fn used_exactly_once(mir: &rustc_middle::mir::Body<'_>, local: rustc_middle::mir::Local) -> Option<bool> {
|
||||
visit_local_usage(
|
||||
&[local],
|
||||
mir,
|
||||
Location {
|
||||
block: START_BLOCK,
|
||||
statement_index: 0,
|
||||
},
|
||||
)
|
||||
.map(|mut vec| {
|
||||
let LocalUsage { local_use_locs, .. } = vec.remove(0);
|
||||
local_use_locs
|
||||
.into_iter()
|
||||
.filter(|location| !is_local_assignment(mir, local, *location))
|
||||
.count()
|
||||
== 1
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the `mir::Body` containing the node associated with `hir_id`.
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub fn enclosing_mir(tcx: TyCtxt<'_>, hir_id: HirId) -> &Body<'_> {
|
||||
let body_owner_local_def_id = tcx.hir().enclosing_body_owner(hir_id);
|
||||
tcx.optimized_mir(body_owner_local_def_id.to_def_id())
|
||||
}
|
||||
|
||||
/// Tries to determine the `Local` corresponding to `expr`, if any.
|
||||
/// This function is expensive and should be used sparingly.
|
||||
pub fn expr_local(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> Option<Local> {
|
||||
let mir = enclosing_mir(tcx, expr.hir_id);
|
||||
mir.local_decls.iter_enumerated().find_map(|(local, local_decl)| {
|
||||
if local_decl.source_info.span == expr.span {
|
||||
Some(local)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a vector of `mir::Location` where `local` is assigned. Each statement referred to has
|
||||
/// kind `StatementKind::Assign`.
|
||||
pub fn local_assignments(mir: &Body<'_>, local: Local) -> Vec<Location> {
|
||||
let mut locations = Vec::new();
|
||||
for (block, data) in mir.basic_blocks.iter_enumerated() {
|
||||
for statement_index in 0..=data.statements.len() {
|
||||
let location = Location { block, statement_index };
|
||||
if is_local_assignment(mir, local, location) {
|
||||
locations.push(location);
|
||||
}
|
||||
}
|
||||
}
|
||||
locations
|
||||
}
|
||||
|
||||
// `is_local_assignment` is based on `is_place_assignment`:
|
||||
// https://github.com/rust-lang/rust/blob/b7413511dc85ec01ef4b91785f86614589ac6103/compiler/rustc_middle/src/mir/visit.rs#L1350
|
||||
fn is_local_assignment(mir: &Body<'_>, local: Local, location: Location) -> bool {
|
||||
let Location { block, statement_index } = location;
|
||||
let basic_block = &mir.basic_blocks[block];
|
||||
if statement_index < basic_block.statements.len() {
|
||||
let statement = &basic_block.statements[statement_index];
|
||||
if let StatementKind::Assign(box (place, _)) = statement.kind {
|
||||
place.as_local() == Some(local)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
let terminator = basic_block.terminator();
|
||||
match &terminator.kind {
|
||||
TerminatorKind::Call { destination, .. } => destination.as_local() == Some(local),
|
||||
TerminatorKind::InlineAsm { operands, .. } => operands.iter().any(|operand| {
|
||||
if let InlineAsmOperand::Out { place: Some(place), .. } = operand {
|
||||
place.as_local() == Some(local)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
241
clippy_utils/src/mir/possible_borrower.rs
Normal file
241
clippy_utils/src/mir/possible_borrower.rs
Normal file
@ -0,0 +1,241 @@
|
||||
use super::{
|
||||
maybe_storage_live::MaybeStorageLive, possible_origin::PossibleOriginVisitor,
|
||||
transitive_relation::TransitiveRelation,
|
||||
};
|
||||
use crate::ty::is_copy;
|
||||
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 _, Mutability};
|
||||
use rustc_middle::ty::{self, visit::TypeVisitor};
|
||||
use rustc_mir_dataflow::{Analysis, ResultsCursor};
|
||||
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 PossibleBorrowerVisitor<'a, 'b, 'tcx> {
|
||||
possible_borrower: TransitiveRelation,
|
||||
body: &'b mir::Body<'tcx>,
|
||||
cx: &'a LateContext<'tcx>,
|
||||
possible_origin: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
|
||||
}
|
||||
|
||||
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 {
|
||||
possible_borrower: TransitiveRelation::default(),
|
||||
cx,
|
||||
body,
|
||||
possible_origin,
|
||||
}
|
||||
}
|
||||
|
||||
fn into_map(
|
||||
self,
|
||||
cx: &'a LateContext<'tcx>,
|
||||
maybe_live: ResultsCursor<'b, 'tcx, MaybeStorageLive>,
|
||||
) -> 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;
|
||||
}
|
||||
|
||||
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() {
|
||||
map.insert(row, borrowers);
|
||||
}
|
||||
}
|
||||
|
||||
let bs = BitSet::new_empty(self.body.local_decls.len());
|
||||
PossibleBorrowerMap {
|
||||
map,
|
||||
maybe_live,
|
||||
bitset: (bs.clone(), bs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_terminator(&mut self, terminator: &mir::Terminator<'_>, _loc: mir::Location) {
|
||||
if let mir::TerminatorKind::Call {
|
||||
args,
|
||||
destination: mir::Place { local: dest, .. },
|
||||
..
|
||||
} = &terminator.kind
|
||||
{
|
||||
// TODO add doc
|
||||
// If the call returns something with lifetimes,
|
||||
// let's conservatively assume the returned value contains lifetime of all the arguments.
|
||||
// For example, given `let y: Foo<'a> = foo(x)`, `y` is considered to be a possible borrower of `x`.
|
||||
|
||||
let mut immutable_borrowers = vec![];
|
||||
let mut mutable_borrowers = vec![];
|
||||
|
||||
for op in args {
|
||||
match op {
|
||||
mir::Operand::Copy(p) | mir::Operand::Move(p) => {
|
||||
if let ty::Ref(_, _, Mutability::Mut) = self.body.local_decls[p.local].ty.kind() {
|
||||
mutable_borrowers.push(p.local);
|
||||
} else {
|
||||
immutable_borrowers.push(p.local);
|
||||
}
|
||||
},
|
||||
mir::Operand::Constant(..) => (),
|
||||
}
|
||||
}
|
||||
|
||||
let mut mutable_variables: Vec<mir::Local> = mutable_borrowers
|
||||
.iter()
|
||||
.filter_map(|r| self.possible_origin.get(r))
|
||||
.flat_map(HybridBitSet::iter)
|
||||
.collect();
|
||||
|
||||
if ContainsRegion.visit_ty(self.body.local_decls[*dest].ty).is_break() {
|
||||
mutable_variables.push(*dest);
|
||||
}
|
||||
|
||||
for y in mutable_variables {
|
||||
for x in &immutable_borrowers {
|
||||
self.possible_borrower.add(*x, y);
|
||||
}
|
||||
for x in &mutable_borrowers {
|
||||
self.possible_borrower.add(*x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContainsRegion;
|
||||
|
||||
impl TypeVisitor<'_> for ContainsRegion {
|
||||
type BreakTy = ();
|
||||
|
||||
fn visit_region(&mut self, _: ty::Region<'_>) -> ControlFlow<Self::BreakTy> {
|
||||
ControlFlow::BREAK
|
||||
}
|
||||
}
|
||||
|
||||
fn rvalue_locals(rvalue: &mir::Rvalue<'_>, mut visit: impl FnMut(mir::Local)) {
|
||||
use rustc_middle::mir::Rvalue::{Aggregate, BinaryOp, Cast, CheckedBinaryOp, Repeat, UnaryOp, Use};
|
||||
|
||||
let mut visit_op = |op: &mir::Operand<'_>| match op {
|
||||
mir::Operand::Copy(p) | mir::Operand::Move(p) => visit(p.local),
|
||||
mir::Operand::Constant(..) => (),
|
||||
};
|
||||
|
||||
match rvalue {
|
||||
Use(op) | Repeat(op, _) | Cast(_, op, _) | UnaryOp(_, op) => visit_op(op),
|
||||
Aggregate(_, ops) => ops.iter().for_each(visit_op),
|
||||
BinaryOp(_, box (lhs, rhs)) | CheckedBinaryOp(_, box (lhs, rhs)) => {
|
||||
visit_op(lhs);
|
||||
visit_op(rhs);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of `PossibleBorrowerVisitor`.
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct PossibleBorrowerMap<'b, 'tcx> {
|
||||
/// Mapping `Local -> its possible borrowers`
|
||||
pub map: FxHashMap<mir::Local, HybridBitSet<mir::Local>>,
|
||||
maybe_live: ResultsCursor<'b, 'tcx, MaybeStorageLive>,
|
||||
// Caches to avoid allocation of `BitSet` on every query
|
||||
pub bitset: (BitSet<mir::Local>, BitSet<mir::Local>),
|
||||
}
|
||||
|
||||
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 maybe_storage_live_result = MaybeStorageLive
|
||||
.into_engine(cx.tcx, mir)
|
||||
.pass_name("redundant_clone")
|
||||
.iterate_to_fixpoint()
|
||||
.into_results_cursor(mir);
|
||||
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` 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,
|
||||
below: &[mir::Local],
|
||||
above: &[mir::Local],
|
||||
borrowed: mir::Local,
|
||||
at: mir::Location,
|
||||
) -> bool {
|
||||
self.maybe_live.seek_after_primary_effect(at);
|
||||
|
||||
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 {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.bitset.1.clear();
|
||||
for b in below {
|
||||
self.bitset.1.insert(*b);
|
||||
}
|
||||
|
||||
if !self.bitset.0.superset(&self.bitset.1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for b in above {
|
||||
self.bitset.0.remove(*b);
|
||||
}
|
||||
|
||||
self.bitset.0.is_empty()
|
||||
}
|
||||
|
||||
pub fn local_is_alive_at(&mut self, local: mir::Local, at: mir::Location) -> bool {
|
||||
self.maybe_live.seek_after_primary_effect(at);
|
||||
self.maybe_live.contains(local)
|
||||
}
|
||||
}
|
59
clippy_utils/src/mir/possible_origin.rs
Normal file
59
clippy_utils/src/mir/possible_origin.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use super::transitive_relation::TransitiveRelation;
|
||||
use crate::ty::is_copy;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_index::bit_set::HybridBitSet;
|
||||
use rustc_lint::LateContext;
|
||||
use rustc_middle::mir;
|
||||
|
||||
/// Collect possible borrowed for every `&mut` local.
|
||||
/// For example, `_1 = &mut _2` generate _1: {_2,...}
|
||||
/// Known Problems: not sure all borrowed are tracked
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub(super) struct PossibleOriginVisitor<'a, 'tcx> {
|
||||
possible_origin: TransitiveRelation,
|
||||
body: &'a mir::Body<'tcx>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> PossibleOriginVisitor<'a, 'tcx> {
|
||||
pub fn new(body: &'a mir::Body<'tcx>) -> Self {
|
||||
Self {
|
||||
possible_origin: TransitiveRelation::default(),
|
||||
body,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_map(self, cx: &LateContext<'tcx>) -> FxHashMap<mir::Local, HybridBitSet<mir::Local>> {
|
||||
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;
|
||||
}
|
||||
|
||||
let mut borrowers = self.possible_origin.reachable_from(row, self.body.local_decls.len());
|
||||
borrowers.remove(mir::Local::from_usize(0));
|
||||
if !borrowers.is_empty() {
|
||||
map.insert(row, borrowers);
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> mir::visit::Visitor<'tcx> for PossibleOriginVisitor<'a, 'tcx> {
|
||||
fn visit_assign(&mut self, place: &mir::Place<'tcx>, rvalue: &mir::Rvalue<'_>, _location: mir::Location) {
|
||||
let lhs = place.local;
|
||||
match rvalue {
|
||||
// Only consider `&mut`, which can modify origin place
|
||||
mir::Rvalue::Ref(_, rustc_middle::mir::BorrowKind::Mut { .. }, borrowed) |
|
||||
// _2: &mut _;
|
||||
// _3 = move _2
|
||||
mir::Rvalue::Use(mir::Operand::Move(borrowed)) |
|
||||
// _3 = move _2 as &mut _;
|
||||
mir::Rvalue::Cast(_, mir::Operand::Move(borrowed), _)
|
||||
=> {
|
||||
self.possible_origin.add(lhs, borrowed.local);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
29
clippy_utils/src/mir/transitive_relation.rs
Normal file
29
clippy_utils/src/mir/transitive_relation.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_index::bit_set::HybridBitSet;
|
||||
use rustc_middle::mir;
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct TransitiveRelation {
|
||||
relations: FxHashMap<mir::Local, Vec<mir::Local>>,
|
||||
}
|
||||
|
||||
impl TransitiveRelation {
|
||||
pub fn add(&mut self, a: mir::Local, b: mir::Local) {
|
||||
self.relations.entry(a).or_default().push(b);
|
||||
}
|
||||
|
||||
pub fn reachable_from(&self, a: mir::Local, domain_size: usize) -> HybridBitSet<mir::Local> {
|
||||
let mut seen = HybridBitSet::new_empty(domain_size);
|
||||
let mut stack = vec![a];
|
||||
while let Some(u) = stack.pop() {
|
||||
if let Some(edges) = self.relations.get(&u) {
|
||||
for &v in edges {
|
||||
if seen.insert(v) {
|
||||
stack.push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
seen
|
||||
}
|
||||
}
|
@ -119,17 +119,11 @@
|
||||
pub const RC_PTR_EQ: [&str; 4] = ["alloc", "rc", "Rc", "ptr_eq"];
|
||||
pub const REFCELL_REF: [&str; 3] = ["core", "cell", "Ref"];
|
||||
pub const REFCELL_REFMUT: [&str; 3] = ["core", "cell", "RefMut"];
|
||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||
pub const REGEX_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "unicode", "RegexBuilder", "new"];
|
||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||
pub const REGEX_BYTES_BUILDER_NEW: [&str; 5] = ["regex", "re_builder", "bytes", "RegexBuilder", "new"];
|
||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||
pub const REGEX_BYTES_NEW: [&str; 4] = ["regex", "re_bytes", "Regex", "new"];
|
||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||
pub const REGEX_BYTES_SET_NEW: [&str; 5] = ["regex", "re_set", "bytes", "RegexSet", "new"];
|
||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||
pub const REGEX_NEW: [&str; 4] = ["regex", "re_unicode", "Regex", "new"];
|
||||
#[expect(clippy::invalid_paths)] // internal lints do not know about all external crates
|
||||
pub const REGEX_SET_NEW: [&str; 5] = ["regex", "re_set", "unicode", "RegexSet", "new"];
|
||||
/// Preferably use the diagnostic item `sym::Result` where possible
|
||||
pub const RESULT: [&str; 3] = ["core", "result", "Result"];
|
||||
|
@ -345,7 +345,7 @@ fn run_clippy_lints(
|
||||
clippy_args.push(opt);
|
||||
}
|
||||
} else {
|
||||
clippy_args.extend(&["-Wclippy::pedantic", "-Wclippy::cargo"])
|
||||
clippy_args.extend(["-Wclippy::pedantic", "-Wclippy::cargo"])
|
||||
}
|
||||
|
||||
if lint_filter.is_empty() {
|
||||
@ -457,15 +457,11 @@ fn build_clippy() {
|
||||
/// Read a `lintcheck_crates.toml` file
|
||||
fn read_crates(toml_path: &Path) -> (Vec<CrateSource>, RecursiveOptions) {
|
||||
let toml_content: String =
|
||||
std::fs::read_to_string(&toml_path).unwrap_or_else(|_| panic!("Failed to read {}", toml_path.display()));
|
||||
std::fs::read_to_string(toml_path).unwrap_or_else(|_| panic!("Failed to read {}", toml_path.display()));
|
||||
let crate_list: SourceList =
|
||||
toml::from_str(&toml_content).unwrap_or_else(|e| panic!("Failed to parse {}: \n{}", toml_path.display(), e));
|
||||
// parse the hashmap of the toml file into a list of crates
|
||||
let tomlcrates: Vec<TomlCrate> = crate_list
|
||||
.crates
|
||||
.into_iter()
|
||||
.map(|(_cratename, tomlcrate)| tomlcrate)
|
||||
.collect();
|
||||
let tomlcrates: Vec<TomlCrate> = crate_list.crates.into_values().collect();
|
||||
|
||||
// flatten TomlCrates into CrateSources (one TomlCrates may represent several versions of a crate =>
|
||||
// multiple Cratesources)
|
||||
@ -602,10 +598,10 @@ fn main() {
|
||||
) {
|
||||
let shared_target_dir = "target/lintcheck/shared_target_dir";
|
||||
// if we get an Err here, the shared target dir probably does simply not exist
|
||||
if let Ok(metadata) = std::fs::metadata(&shared_target_dir) {
|
||||
if let Ok(metadata) = std::fs::metadata(shared_target_dir) {
|
||||
if metadata.is_dir() {
|
||||
println!("Clippy is newer than lint check logs, clearing lintcheck shared target dir...");
|
||||
std::fs::remove_dir_all(&shared_target_dir)
|
||||
std::fs::remove_dir_all(shared_target_dir)
|
||||
.expect("failed to remove target/lintcheck/shared_target_dir");
|
||||
}
|
||||
}
|
||||
@ -779,7 +775,7 @@ fn read_stats_from_file(file_path: &Path) -> HashMap<String, usize> {
|
||||
fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, usize>, lint_filter: &Vec<String>) {
|
||||
let same_in_both_hashmaps = old_stats
|
||||
.iter()
|
||||
.filter(|(old_key, old_val)| new_stats.get::<&String>(&old_key) == Some(old_val))
|
||||
.filter(|(old_key, old_val)| new_stats.get::<&String>(old_key) == Some(old_val))
|
||||
.map(|(k, v)| (k.to_string(), *v))
|
||||
.collect::<Vec<(String, usize)>>();
|
||||
|
||||
@ -797,7 +793,7 @@ fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, us
|
||||
// list all new counts (key is in new stats but not in old stats)
|
||||
new_stats_deduped
|
||||
.iter()
|
||||
.filter(|(new_key, _)| old_stats_deduped.get::<str>(&new_key).is_none())
|
||||
.filter(|(new_key, _)| old_stats_deduped.get::<str>(new_key).is_none())
|
||||
.for_each(|(new_key, new_value)| {
|
||||
println!("{} 0 => {}", new_key, new_value);
|
||||
});
|
||||
@ -805,16 +801,16 @@ fn print_stats(old_stats: HashMap<String, usize>, new_stats: HashMap<&String, us
|
||||
// list all changed counts (key is in both maps but value differs)
|
||||
new_stats_deduped
|
||||
.iter()
|
||||
.filter(|(new_key, _new_val)| old_stats_deduped.get::<str>(&new_key).is_some())
|
||||
.filter(|(new_key, _new_val)| old_stats_deduped.get::<str>(new_key).is_some())
|
||||
.for_each(|(new_key, new_val)| {
|
||||
let old_val = old_stats_deduped.get::<str>(&new_key).unwrap();
|
||||
let old_val = old_stats_deduped.get::<str>(new_key).unwrap();
|
||||
println!("{} {} => {}", new_key, old_val, new_val);
|
||||
});
|
||||
|
||||
// list all gone counts (key is in old status but not in new stats)
|
||||
old_stats_deduped
|
||||
.iter()
|
||||
.filter(|(old_key, _)| new_stats_deduped.get::<&String>(&old_key).is_none())
|
||||
.filter(|(old_key, _)| new_stats_deduped.get::<&String>(old_key).is_none())
|
||||
.filter(|(old_key, _)| lint_filter.is_empty() || lint_filter.contains(old_key))
|
||||
.for_each(|(old_key, old_value)| {
|
||||
println!("{} {} => 0", old_key, old_value);
|
||||
@ -832,12 +828,12 @@ fn create_dirs(krate_download_dir: &Path, extract_dir: &Path) {
|
||||
panic!("cannot create lintcheck target dir");
|
||||
}
|
||||
});
|
||||
std::fs::create_dir(&krate_download_dir).unwrap_or_else(|err| {
|
||||
std::fs::create_dir(krate_download_dir).unwrap_or_else(|err| {
|
||||
if err.kind() != ErrorKind::AlreadyExists {
|
||||
panic!("cannot create crate download dir");
|
||||
}
|
||||
});
|
||||
std::fs::create_dir(&extract_dir).unwrap_or_else(|err| {
|
||||
std::fs::create_dir(extract_dir).unwrap_or_else(|err| {
|
||||
if err.kind() != ErrorKind::AlreadyExists {
|
||||
panic!("cannot create crate extraction dir");
|
||||
}
|
||||
@ -863,7 +859,7 @@ fn lintcheck_test() {
|
||||
"lintcheck/test_sources.toml",
|
||||
];
|
||||
let status = std::process::Command::new("cargo")
|
||||
.args(&args)
|
||||
.args(args)
|
||||
.current_dir("..") // repo root
|
||||
.status();
|
||||
//.output();
|
||||
|
@ -283,7 +283,7 @@ fn run_tests(
|
||||
env::set_current_dir(&src_path)?;
|
||||
|
||||
let cargo_toml_path = case.path().join("Cargo.toml");
|
||||
let cargo_content = fs::read(&cargo_toml_path)?;
|
||||
let cargo_content = fs::read(cargo_toml_path)?;
|
||||
let cargo_parsed: toml::Value = toml::from_str(
|
||||
std::str::from_utf8(&cargo_content).expect("`Cargo.toml` is not a valid utf-8 file!"),
|
||||
)
|
||||
|
@ -3,7 +3,11 @@
|
||||
|
||||
#[warn(clippy::all, clippy::needless_borrow)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::uninlined_format_args, clippy::unnecessary_mut_passed)]
|
||||
#[allow(
|
||||
clippy::uninlined_format_args,
|
||||
clippy::unnecessary_mut_passed,
|
||||
clippy::unnecessary_to_owned
|
||||
)]
|
||||
fn main() {
|
||||
let a = 5;
|
||||
let ref_a = &a;
|
||||
@ -134,6 +138,7 @@ fn main() {
|
||||
multiple_constraints([[""]]);
|
||||
multiple_constraints_normalizes_to_same(X, X);
|
||||
let _ = Some("").unwrap_or("");
|
||||
let _ = std::fs::write("x", "".to_string());
|
||||
|
||||
only_sized(&""); // Don't lint. `Sized` is only bound
|
||||
let _ = std::any::Any::type_id(&""); // Don't lint. `Any` is only bound
|
||||
@ -276,8 +281,9 @@ mod copyable_iterator {
|
||||
fn dont_warn(mut x: Iter) {
|
||||
takes_iter(&mut x);
|
||||
}
|
||||
#[allow(unused_mut)]
|
||||
fn warn(mut x: &mut Iter) {
|
||||
takes_iter(&mut x)
|
||||
takes_iter(x)
|
||||
}
|
||||
}
|
||||
|
||||
@ -327,3 +333,55 @@ fn issue9383() {
|
||||
ManuallyDrop::drop(&mut ocean.coral);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn closure_test() {
|
||||
let env = "env".to_owned();
|
||||
let arg = "arg".to_owned();
|
||||
let f = |arg| {
|
||||
let loc = "loc".to_owned();
|
||||
let _ = std::fs::write("x", &env); // Don't lint. In environment
|
||||
let _ = std::fs::write("x", arg);
|
||||
let _ = std::fs::write("x", loc);
|
||||
};
|
||||
let _ = std::fs::write("x", &env); // Don't lint. Borrowed by `f`
|
||||
f(arg);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod significant_drop {
|
||||
#[derive(Debug)]
|
||||
struct X;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Y;
|
||||
|
||||
impl Drop for Y {
|
||||
fn drop(&mut self) {}
|
||||
}
|
||||
|
||||
fn foo(x: X, y: Y) {
|
||||
debug(x);
|
||||
debug(&y); // Don't lint. Has significant drop
|
||||
}
|
||||
|
||||
fn debug(_: impl std::fmt::Debug) {}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod used_exactly_once {
|
||||
fn foo(x: String) {
|
||||
use_x(x);
|
||||
}
|
||||
fn use_x(_: impl AsRef<str>) {}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod used_more_than_once {
|
||||
fn foo(x: String) {
|
||||
use_x(&x);
|
||||
use_x_again(&x);
|
||||
}
|
||||
fn use_x(_: impl AsRef<str>) {}
|
||||
fn use_x_again(_: impl AsRef<str>) {}
|
||||
}
|
||||
|
@ -3,7 +3,11 @@
|
||||
|
||||
#[warn(clippy::all, clippy::needless_borrow)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::uninlined_format_args, clippy::unnecessary_mut_passed)]
|
||||
#[allow(
|
||||
clippy::uninlined_format_args,
|
||||
clippy::unnecessary_mut_passed,
|
||||
clippy::unnecessary_to_owned
|
||||
)]
|
||||
fn main() {
|
||||
let a = 5;
|
||||
let ref_a = &a;
|
||||
@ -134,6 +138,7 @@ fn from(s: &S) -> Self {
|
||||
multiple_constraints(&[[""]]);
|
||||
multiple_constraints_normalizes_to_same(&X, X);
|
||||
let _ = Some("").unwrap_or(&"");
|
||||
let _ = std::fs::write("x", &"".to_string());
|
||||
|
||||
only_sized(&""); // Don't lint. `Sized` is only bound
|
||||
let _ = std::any::Any::type_id(&""); // Don't lint. `Any` is only bound
|
||||
@ -276,6 +281,7 @@ fn takes_iter(_: impl Iterator) {}
|
||||
fn dont_warn(mut x: Iter) {
|
||||
takes_iter(&mut x);
|
||||
}
|
||||
#[allow(unused_mut)]
|
||||
fn warn(mut x: &mut Iter) {
|
||||
takes_iter(&mut x)
|
||||
}
|
||||
@ -327,3 +333,55 @@ union Ocean {
|
||||
ManuallyDrop::drop(&mut ocean.coral);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn closure_test() {
|
||||
let env = "env".to_owned();
|
||||
let arg = "arg".to_owned();
|
||||
let f = |arg| {
|
||||
let loc = "loc".to_owned();
|
||||
let _ = std::fs::write("x", &env); // Don't lint. In environment
|
||||
let _ = std::fs::write("x", &arg);
|
||||
let _ = std::fs::write("x", &loc);
|
||||
};
|
||||
let _ = std::fs::write("x", &env); // Don't lint. Borrowed by `f`
|
||||
f(arg);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod significant_drop {
|
||||
#[derive(Debug)]
|
||||
struct X;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Y;
|
||||
|
||||
impl Drop for Y {
|
||||
fn drop(&mut self) {}
|
||||
}
|
||||
|
||||
fn foo(x: X, y: Y) {
|
||||
debug(&x);
|
||||
debug(&y); // Don't lint. Has significant drop
|
||||
}
|
||||
|
||||
fn debug(_: impl std::fmt::Debug) {}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod used_exactly_once {
|
||||
fn foo(x: String) {
|
||||
use_x(&x);
|
||||
}
|
||||
fn use_x(_: impl AsRef<str>) {}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod used_more_than_once {
|
||||
fn foo(x: String) {
|
||||
use_x(&x);
|
||||
use_x_again(&x);
|
||||
}
|
||||
fn use_x(_: impl AsRef<str>) {}
|
||||
fn use_x_again(_: impl AsRef<str>) {}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:11:15
|
||||
--> $DIR/needless_borrow.rs:15:15
|
||||
|
|
||||
LL | let _ = x(&&a); // warn
|
||||
| ^^^ help: change this to: `&a`
|
||||
@ -7,172 +7,208 @@ LL | let _ = x(&&a); // warn
|
||||
= note: `-D clippy::needless-borrow` implied by `-D warnings`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:15:13
|
||||
--> $DIR/needless_borrow.rs:19:13
|
||||
|
|
||||
LL | mut_ref(&mut &mut b); // warn
|
||||
| ^^^^^^^^^^^ help: change this to: `&mut b`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:27:13
|
||||
--> $DIR/needless_borrow.rs:31:13
|
||||
|
|
||||
LL | &&a
|
||||
| ^^^ help: change this to: `&a`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:29:15
|
||||
--> $DIR/needless_borrow.rs:33:15
|
||||
|
|
||||
LL | 46 => &&a,
|
||||
| ^^^ help: change this to: `&a`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:35:27
|
||||
--> $DIR/needless_borrow.rs:39:27
|
||||
|
|
||||
LL | break &ref_a;
|
||||
| ^^^^^^ help: change this to: `ref_a`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:42:15
|
||||
--> $DIR/needless_borrow.rs:46:15
|
||||
|
|
||||
LL | let _ = x(&&&a);
|
||||
| ^^^^ help: change this to: `&a`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:43:15
|
||||
--> $DIR/needless_borrow.rs:47:15
|
||||
|
|
||||
LL | let _ = x(&mut &&a);
|
||||
| ^^^^^^^^ help: change this to: `&a`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:44:15
|
||||
--> $DIR/needless_borrow.rs:48:15
|
||||
|
|
||||
LL | let _ = x(&&&mut b);
|
||||
| ^^^^^^^^ help: change this to: `&mut b`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:45:15
|
||||
--> $DIR/needless_borrow.rs:49:15
|
||||
|
|
||||
LL | let _ = x(&&ref_a);
|
||||
| ^^^^^^^ help: change this to: `ref_a`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:48:11
|
||||
--> $DIR/needless_borrow.rs:52:11
|
||||
|
|
||||
LL | x(&b);
|
||||
| ^^ help: change this to: `b`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:55:13
|
||||
--> $DIR/needless_borrow.rs:59:13
|
||||
|
|
||||
LL | mut_ref(&mut x);
|
||||
| ^^^^^^ help: change this to: `x`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:56:13
|
||||
--> $DIR/needless_borrow.rs:60:13
|
||||
|
|
||||
LL | mut_ref(&mut &mut x);
|
||||
| ^^^^^^^^^^^ help: change this to: `x`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:57:23
|
||||
--> $DIR/needless_borrow.rs:61:23
|
||||
|
|
||||
LL | let y: &mut i32 = &mut x;
|
||||
| ^^^^^^ help: change this to: `x`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:58:23
|
||||
--> $DIR/needless_borrow.rs:62:23
|
||||
|
|
||||
LL | let y: &mut i32 = &mut &mut x;
|
||||
| ^^^^^^^^^^^ help: change this to: `x`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:67:14
|
||||
--> $DIR/needless_borrow.rs:71:14
|
||||
|
|
||||
LL | 0 => &mut x,
|
||||
| ^^^^^^ help: change this to: `x`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:73:14
|
||||
--> $DIR/needless_borrow.rs:77:14
|
||||
|
|
||||
LL | 0 => &mut x,
|
||||
| ^^^^^^ help: change this to: `x`
|
||||
|
||||
error: this expression borrows a value the compiler would automatically borrow
|
||||
--> $DIR/needless_borrow.rs:85:13
|
||||
--> $DIR/needless_borrow.rs:89:13
|
||||
|
|
||||
LL | let _ = (&x).0;
|
||||
| ^^^^ help: change this to: `x`
|
||||
|
||||
error: this expression borrows a value the compiler would automatically borrow
|
||||
--> $DIR/needless_borrow.rs:87:22
|
||||
--> $DIR/needless_borrow.rs:91:22
|
||||
|
|
||||
LL | let _ = unsafe { (&*x).0 };
|
||||
| ^^^^^ help: change this to: `(*x)`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:97:5
|
||||
--> $DIR/needless_borrow.rs:101:5
|
||||
|
|
||||
LL | (&&()).foo();
|
||||
| ^^^^^^ help: change this to: `(&())`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:106:5
|
||||
--> $DIR/needless_borrow.rs:110:5
|
||||
|
|
||||
LL | (&&5).foo();
|
||||
| ^^^^^ help: change this to: `(&5)`
|
||||
|
||||
error: the borrowed expression implements the required traits
|
||||
--> $DIR/needless_borrow.rs:131:51
|
||||
--> $DIR/needless_borrow.rs:135:51
|
||||
|
|
||||
LL | let _ = std::process::Command::new("ls").args(&["-a", "-l"]).status().unwrap();
|
||||
| ^^^^^^^^^^^^^ help: change this to: `["-a", "-l"]`
|
||||
|
||||
error: the borrowed expression implements the required traits
|
||||
--> $DIR/needless_borrow.rs:132:44
|
||||
--> $DIR/needless_borrow.rs:136:44
|
||||
|
|
||||
LL | let _ = std::path::Path::new(".").join(&&".");
|
||||
| ^^^^^ help: change this to: `"."`
|
||||
|
||||
error: the borrowed expression implements the required traits
|
||||
--> $DIR/needless_borrow.rs:133:23
|
||||
--> $DIR/needless_borrow.rs:137:23
|
||||
|
|
||||
LL | deref_target_is_x(&X);
|
||||
| ^^ help: change this to: `X`
|
||||
|
||||
error: the borrowed expression implements the required traits
|
||||
--> $DIR/needless_borrow.rs:134:26
|
||||
--> $DIR/needless_borrow.rs:138:26
|
||||
|
|
||||
LL | multiple_constraints(&[[""]]);
|
||||
| ^^^^^^^ help: change this to: `[[""]]`
|
||||
|
||||
error: the borrowed expression implements the required traits
|
||||
--> $DIR/needless_borrow.rs:135:45
|
||||
--> $DIR/needless_borrow.rs:139:45
|
||||
|
|
||||
LL | multiple_constraints_normalizes_to_same(&X, X);
|
||||
| ^^ help: change this to: `X`
|
||||
|
||||
error: this expression creates a reference which is immediately dereferenced by the compiler
|
||||
--> $DIR/needless_borrow.rs:136:32
|
||||
--> $DIR/needless_borrow.rs:140:32
|
||||
|
|
||||
LL | let _ = Some("").unwrap_or(&"");
|
||||
| ^^^ help: change this to: `""`
|
||||
|
||||
error: the borrowed expression implements the required traits
|
||||
--> $DIR/needless_borrow.rs:141:33
|
||||
|
|
||||
LL | let _ = std::fs::write("x", &"".to_string());
|
||||
| ^^^^^^^^^^^^^^^ help: change this to: `"".to_string()`
|
||||
|
||||
error: this expression borrows a value the compiler would automatically borrow
|
||||
--> $DIR/needless_borrow.rs:187:13
|
||||
--> $DIR/needless_borrow.rs:192:13
|
||||
|
|
||||
LL | (&self.f)()
|
||||
| ^^^^^^^^^ help: change this to: `(self.f)`
|
||||
|
||||
error: this expression borrows a value the compiler would automatically borrow
|
||||
--> $DIR/needless_borrow.rs:196:13
|
||||
--> $DIR/needless_borrow.rs:201:13
|
||||
|
|
||||
LL | (&mut self.f)()
|
||||
| ^^^^^^^^^^^^^ help: change this to: `(self.f)`
|
||||
|
||||
error: the borrowed expression implements the required traits
|
||||
--> $DIR/needless_borrow.rs:298:55
|
||||
--> $DIR/needless_borrow.rs:286:20
|
||||
|
|
||||
LL | takes_iter(&mut x)
|
||||
| ^^^^^^ help: change this to: `x`
|
||||
|
||||
error: the borrowed expression implements the required traits
|
||||
--> $DIR/needless_borrow.rs:304:55
|
||||
|
|
||||
LL | let _ = std::process::Command::new("ls").args(&["-a", "-l"]).status().unwrap();
|
||||
| ^^^^^^^^^^^^^ help: change this to: `["-a", "-l"]`
|
||||
|
||||
error: aborting due to 29 previous errors
|
||||
error: the borrowed expression implements the required traits
|
||||
--> $DIR/needless_borrow.rs:344:37
|
||||
|
|
||||
LL | let _ = std::fs::write("x", &arg);
|
||||
| ^^^^ help: change this to: `arg`
|
||||
|
||||
error: the borrowed expression implements the required traits
|
||||
--> $DIR/needless_borrow.rs:345:37
|
||||
|
|
||||
LL | let _ = std::fs::write("x", &loc);
|
||||
| ^^^^ help: change this to: `loc`
|
||||
|
||||
error: the borrowed expression implements the required traits
|
||||
--> $DIR/needless_borrow.rs:364:15
|
||||
|
|
||||
LL | debug(&x);
|
||||
| ^^ help: change this to: `x`
|
||||
|
||||
error: the borrowed expression implements the required traits
|
||||
--> $DIR/needless_borrow.rs:374:15
|
||||
|
|
||||
LL | use_x(&x);
|
||||
| ^^ help: change this to: `x`
|
||||
|
||||
error: aborting due to 35 previous errors
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
// run-rustfix
|
||||
|
||||
#![allow(clippy::ptr_arg)]
|
||||
#![allow(clippy::needless_borrow, clippy::ptr_arg)]
|
||||
#![warn(clippy::unnecessary_to_owned)]
|
||||
#![feature(custom_inner_attributes)]
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
// run-rustfix
|
||||
|
||||
#![allow(clippy::ptr_arg)]
|
||||
#![allow(clippy::needless_borrow, clippy::ptr_arg)]
|
||||
#![warn(clippy::unnecessary_to_owned)]
|
||||
#![feature(custom_inner_attributes)]
|
||||
|
||||
|
@ -48,7 +48,7 @@ fn check_that_clippy_has_the_same_major_version_as_rustc() {
|
||||
// `RUSTC_REAL` if Clippy is build in the Rust repo with `./x.py`.
|
||||
let rustc = std::env::var("RUSTC_REAL").unwrap_or_else(|_| "rustc".to_string());
|
||||
let rustc_version = String::from_utf8(
|
||||
std::process::Command::new(&rustc)
|
||||
std::process::Command::new(rustc)
|
||||
.arg("--version")
|
||||
.output()
|
||||
.expect("failed to run `rustc --version`")
|
||||
|
Loading…
Reference in New Issue
Block a user