Skip to content

Commit 457e7f1

Browse files
bors[bot]sinkuu
andcommitted
Merge #3355
3355: Lint to remove redundant `clone()`s r=oli-obk a=sinkuu This PR adds lint `redundant_clone`. It suggests to remove redundant `clone()` that clones a owned value that will be dropped without any usage after that. Real-world example: https://github.com/rust-lang/rust/compare/7b0735a..sinkuu:redundant_clone2 Co-authored-by: Shotaro Yamada <[email protected]>
2 parents 5e1c736 + 9034b87 commit 457e7f1

File tree

9 files changed

+505
-2
lines changed

9 files changed

+505
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,7 @@ All notable changes to this project will be documented in this file.
813813
[`range_plus_one`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#range_plus_one
814814
[`range_step_by_zero`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#range_step_by_zero
815815
[`range_zip_with_len`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#range_zip_with_len
816+
[`redundant_clone`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#redundant_clone
816817
[`redundant_closure`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#redundant_closure
817818
[`redundant_closure_call`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#redundant_closure_call
818819
[`redundant_field_names`]: https://rust-lang-nursery.github.io/rust-clippy/master/index.html#redundant_field_names

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ We are currently in the process of discussing Clippy 1.0 via the RFC process in
99

1010
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
1111

12-
[There are 282 lints included in this crate!](https://rust-lang-nursery.github.io/rust-clippy/master/index.html)
12+
[There are 283 lints included in this crate!](https://rust-lang-nursery.github.io/rust-clippy/master/index.html)
1313

1414
We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:
1515

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ pub mod ptr;
179179
pub mod ptr_offset_with_cast;
180180
pub mod question_mark;
181181
pub mod ranges;
182+
pub mod redundant_clone;
182183
pub mod redundant_field_names;
183184
pub mod redundant_pattern_matching;
184185
pub mod reference;
@@ -452,6 +453,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
452453
reg.register_late_lint_pass(box indexing_slicing::IndexingSlicing);
453454
reg.register_late_lint_pass(box non_copy_const::NonCopyConst);
454455
reg.register_late_lint_pass(box ptr_offset_with_cast::Pass);
456+
reg.register_late_lint_pass(box redundant_clone::RedundantClone);
455457

456458
reg.register_lint_group("clippy::restriction", Some("clippy_restriction"), vec![
457459
arithmetic::FLOAT_ARITHMETIC,
@@ -981,6 +983,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
981983
fallible_impl_from::FALLIBLE_IMPL_FROM,
982984
mutex_atomic::MUTEX_INTEGER,
983985
needless_borrow::NEEDLESS_BORROW,
986+
redundant_clone::REDUNDANT_CLONE,
984987
unwrap::PANICKING_UNWRAP,
985988
unwrap::UNNECESSARY_UNWRAP,
986989
]);

clippy_lints/src/redundant_clone.rs

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution.
3+
//
4+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7+
// option. This file may not be copied, modified, or distributed
8+
// except according to those terms.
9+
10+
use crate::rustc::hir::intravisit::FnKind;
11+
use crate::rustc::hir::{def_id, Body, FnDecl};
12+
use crate::rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
13+
use crate::rustc::mir::{
14+
self, traversal,
15+
visit::{PlaceContext, Visitor},
16+
TerminatorKind,
17+
};
18+
use crate::rustc::ty;
19+
use crate::rustc::{declare_tool_lint, lint_array};
20+
use crate::rustc_errors::Applicability;
21+
use crate::syntax::{
22+
ast::NodeId,
23+
source_map::{BytePos, Span},
24+
};
25+
use crate::utils::{
26+
in_macro, is_copy, match_def_path, match_type, paths, snippet_opt, span_lint_node, span_lint_node_and_then,
27+
walk_ptrs_ty_depth,
28+
};
29+
use if_chain::if_chain;
30+
use std::convert::TryFrom;
31+
32+
macro_rules! unwrap_or_continue {
33+
($x:expr) => {
34+
match $x {
35+
Some(x) => x,
36+
None => continue,
37+
}
38+
};
39+
}
40+
41+
/// **What it does:** Checks for a redudant `clone()` (and its relatives) which clones an owned
42+
/// value that is going to be dropped without further use.
43+
///
44+
/// **Why is this bad?** It is not always possible for the compiler to eliminate useless
45+
/// allocations and deallocations generated by redundant `clone()`s.
46+
///
47+
/// **Known problems:**
48+
///
49+
/// * Suggestions made by this lint could require NLL to be enabled.
50+
/// * False-positive if there is a borrow preventing the value from moving out.
51+
///
52+
/// ```rust
53+
/// let x = String::new();
54+
///
55+
/// let y = &x;
56+
///
57+
/// foo(x.clone()); // This lint suggests to remove this `clone()`
58+
/// ```
59+
///
60+
/// **Example:**
61+
/// ```rust
62+
/// {
63+
/// let x = Foo::new();
64+
/// call(x.clone());
65+
/// call(x.clone()); // this can just pass `x`
66+
/// }
67+
///
68+
/// ["lorem", "ipsum"].join(" ").to_string()
69+
///
70+
/// Path::new("/a/b").join("c").to_path_buf()
71+
/// ```
72+
declare_clippy_lint! {
73+
pub REDUNDANT_CLONE,
74+
nursery,
75+
"`clone()` of an owned value that is going to be dropped immediately"
76+
}
77+
78+
pub struct RedundantClone;
79+
80+
impl LintPass for RedundantClone {
81+
fn get_lints(&self) -> LintArray {
82+
lint_array!(REDUNDANT_CLONE)
83+
}
84+
}
85+
86+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for RedundantClone {
87+
fn check_fn(
88+
&mut self,
89+
cx: &LateContext<'a, 'tcx>,
90+
_: FnKind<'tcx>,
91+
_: &'tcx FnDecl,
92+
body: &'tcx Body,
93+
_: Span,
94+
_: NodeId,
95+
) {
96+
let def_id = cx.tcx.hir.body_owner_def_id(body.id());
97+
let mir = cx.tcx.optimized_mir(def_id);
98+
99+
for (bb, bbdata) in mir.basic_blocks().iter_enumerated() {
100+
let terminator = bbdata.terminator();
101+
102+
if in_macro(terminator.source_info.span) {
103+
continue;
104+
}
105+
106+
// Give up on loops
107+
if terminator.successors().any(|s| *s == bb) {
108+
continue;
109+
}
110+
111+
let (fn_def_id, arg, arg_ty, _) = unwrap_or_continue!(is_call_with_ref_arg(cx, mir, &terminator.kind));
112+
113+
let from_borrow = match_def_path(cx.tcx, fn_def_id, &paths::CLONE_TRAIT_METHOD)
114+
|| match_def_path(cx.tcx, fn_def_id, &paths::TO_OWNED_METHOD)
115+
|| (match_def_path(cx.tcx, fn_def_id, &paths::TO_STRING_METHOD)
116+
&& match_type(cx, arg_ty, &paths::STRING));
117+
118+
let from_deref = !from_borrow
119+
&& (match_def_path(cx.tcx, fn_def_id, &paths::PATH_TO_PATH_BUF)
120+
|| match_def_path(cx.tcx, fn_def_id, &paths::OS_STR_TO_OS_STRING));
121+
122+
if !from_borrow && !from_deref {
123+
continue;
124+
}
125+
126+
// _1 in MIR `{ _2 = &_1; clone(move _2); }` or `{ _2 = _1; to_path_buf(_2); } (from_deref)
127+
// In case of `from_deref`, `arg` is already a reference since it is `deref`ed in the previous
128+
// block.
129+
let cloned = unwrap_or_continue!(find_stmt_assigns_to(arg, from_borrow, bbdata.statements.iter().rev()));
130+
131+
// _1 in MIR `{ _2 = &_1; _3 = deref(move _2); } -> { _4 = _3; to_path_buf(move _4); }`
132+
let referent = if from_deref {
133+
let ps = mir.predecessors_for(bb);
134+
if ps.len() != 1 {
135+
continue;
136+
}
137+
let pred_terminator = mir[ps[0]].terminator();
138+
139+
let pred_arg = if_chain! {
140+
if let Some((pred_fn_def_id, pred_arg, pred_arg_ty, Some(res))) =
141+
is_call_with_ref_arg(cx, mir, &pred_terminator.kind);
142+
if *res == mir::Place::Local(cloned);
143+
if match_def_path(cx.tcx, pred_fn_def_id, &paths::DEREF_TRAIT_METHOD);
144+
if match_type(cx, pred_arg_ty, &paths::PATH_BUF)
145+
|| match_type(cx, pred_arg_ty, &paths::OS_STRING);
146+
then {
147+
pred_arg
148+
} else {
149+
continue;
150+
}
151+
};
152+
153+
unwrap_or_continue!(find_stmt_assigns_to(pred_arg, true, mir[ps[0]].statements.iter().rev()))
154+
} else {
155+
cloned
156+
};
157+
158+
let used_later = traversal::ReversePostorder::new(&mir, bb).skip(1).any(|(tbb, tdata)| {
159+
// Give up on loops
160+
if tdata.terminator().successors().any(|s| *s == bb) {
161+
return true;
162+
}
163+
164+
let mut vis = LocalUseVisitor {
165+
local: referent,
166+
used_other_than_drop: false,
167+
};
168+
vis.visit_basic_block_data(tbb, tdata);
169+
vis.used_other_than_drop
170+
});
171+
172+
if !used_later {
173+
let span = terminator.source_info.span;
174+
let node = if let mir::ClearCrossCrate::Set(scope_local_data) = &mir.source_scope_local_data {
175+
scope_local_data[terminator.source_info.scope].lint_root
176+
} else {
177+
unreachable!()
178+
};
179+
180+
if_chain! {
181+
if let Some(snip) = snippet_opt(cx, span);
182+
if let Some(dot) = snip.rfind('.');
183+
then {
184+
let sugg_span = span.with_lo(
185+
span.lo() + BytePos(u32::try_from(dot).unwrap())
186+
);
187+
188+
span_lint_node_and_then(cx, REDUNDANT_CLONE, node, sugg_span, "redundant clone", |db| {
189+
db.span_suggestion_with_applicability(
190+
sugg_span,
191+
"remove this",
192+
String::new(),
193+
Applicability::MaybeIncorrect,
194+
);
195+
db.span_note(
196+
span.with_hi(span.lo() + BytePos(u32::try_from(dot).unwrap())),
197+
"this value is dropped without further use",
198+
);
199+
});
200+
} else {
201+
span_lint_node(cx, REDUNDANT_CLONE, node, span, "redundant clone");
202+
}
203+
}
204+
}
205+
}
206+
}
207+
}
208+
209+
/// If `kind` is `y = func(x: &T)` where `T: !Copy`, returns `(DefId of func, x, T, y)`.
210+
fn is_call_with_ref_arg<'tcx>(
211+
cx: &LateContext<'_, 'tcx>,
212+
mir: &'tcx mir::Mir<'tcx>,
213+
kind: &'tcx mir::TerminatorKind<'tcx>,
214+
) -> Option<(def_id::DefId, mir::Local, ty::Ty<'tcx>, Option<&'tcx mir::Place<'tcx>>)> {
215+
if_chain! {
216+
if let TerminatorKind::Call { func, args, destination, .. } = kind;
217+
if args.len() == 1;
218+
if let mir::Operand::Move(mir::Place::Local(local)) = &args[0];
219+
if let ty::FnDef(def_id, _) = func.ty(&*mir, cx.tcx).sty;
220+
if let (inner_ty, 1) = walk_ptrs_ty_depth(args[0].ty(&*mir, cx.tcx));
221+
if !is_copy(cx, inner_ty);
222+
then {
223+
Some((def_id, *local, inner_ty, destination.as_ref().map(|(dest, _)| dest)))
224+
} else {
225+
None
226+
}
227+
}
228+
}
229+
230+
/// Finds the first `to = (&)from`, and returns `Some(from)`.
231+
fn find_stmt_assigns_to<'a, 'tcx: 'a>(
232+
to: mir::Local,
233+
by_ref: bool,
234+
mut stmts: impl Iterator<Item = &'a mir::Statement<'tcx>>,
235+
) -> Option<mir::Local> {
236+
stmts.find_map(|stmt| {
237+
if let mir::StatementKind::Assign(mir::Place::Local(local), v) = &stmt.kind {
238+
if *local == to {
239+
if by_ref {
240+
if let mir::Rvalue::Ref(_, _, mir::Place::Local(r)) = **v {
241+
return Some(r);
242+
}
243+
} else if let mir::Rvalue::Use(mir::Operand::Copy(mir::Place::Local(r))) = **v {
244+
return Some(r);
245+
}
246+
}
247+
}
248+
249+
None
250+
})
251+
}
252+
253+
struct LocalUseVisitor {
254+
local: mir::Local,
255+
used_other_than_drop: bool,
256+
}
257+
258+
impl<'tcx> mir::visit::Visitor<'tcx> for LocalUseVisitor {
259+
fn visit_basic_block_data(&mut self, block: mir::BasicBlock, data: &mir::BasicBlockData<'tcx>) {
260+
let statements = &data.statements;
261+
for (statement_index, statement) in statements.iter().enumerate() {
262+
self.visit_statement(block, statement, mir::Location { block, statement_index });
263+
264+
// Once flagged, skip remaining statements
265+
if self.used_other_than_drop {
266+
return;
267+
}
268+
}
269+
270+
self.visit_terminator(
271+
block,
272+
data.terminator(),
273+
mir::Location {
274+
block,
275+
statement_index: statements.len(),
276+
},
277+
);
278+
}
279+
280+
fn visit_local(&mut self, local: &mir::Local, ctx: PlaceContext<'tcx>, _: mir::Location) {
281+
match ctx {
282+
PlaceContext::Drop | PlaceContext::StorageDead => return,
283+
_ => {},
284+
}
285+
286+
if *local == self.local {
287+
self.used_other_than_drop = true;
288+
}
289+
}
290+
}

clippy_lints/src/utils/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,23 @@ pub fn span_lint_and_then<'a, 'tcx: 'a, T: LintContext<'tcx>, F>(
562562
db.docs_link(lint);
563563
}
564564

565+
pub fn span_lint_node(cx: &LateContext<'_, '_>, lint: &'static Lint, node: NodeId, sp: Span, msg: &str) {
566+
DiagnosticWrapper(cx.tcx.struct_span_lint_node(lint, node, sp, msg)).docs_link(lint);
567+
}
568+
569+
pub fn span_lint_node_and_then(
570+
cx: &LateContext<'_, '_>,
571+
lint: &'static Lint,
572+
node: NodeId,
573+
sp: Span,
574+
msg: &str,
575+
f: impl FnOnce(&mut DiagnosticBuilder<'_>),
576+
) {
577+
let mut db = DiagnosticWrapper(cx.tcx.struct_span_lint_node(lint, node, sp, msg));
578+
f(&mut db.0);
579+
db.docs_link(lint);
580+
}
581+
565582
/// Add a span lint with a suggestion on how to fix it.
566583
///
567584
/// These suggestions can be parsed by rustfix to allow it to automatically fix your code.

clippy_lints/src/utils/paths.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub const BTREEMAP: [&str; 5] = ["alloc", "collections", "btree", "map", "BTreeM
2323
pub const BTREEMAP_ENTRY: [&str; 5] = ["alloc", "collections", "btree", "map", "Entry"];
2424
pub const BTREESET: [&str; 5] = ["alloc", "collections", "btree", "set", "BTreeSet"];
2525
pub const CLONE_TRAIT: [&str; 3] = ["core", "clone", "Clone"];
26+
pub const CLONE_TRAIT_METHOD: [&str; 4] = ["core", "clone", "Clone", "clone"];
2627
pub const CMP_MAX: [&str; 3] = ["core", "cmp", "max"];
2728
pub const CMP_MIN: [&str; 3] = ["core", "cmp", "min"];
2829
pub const COW: [&str; 3] = ["alloc", "borrow", "Cow"];
@@ -31,6 +32,7 @@ pub const C_VOID: [&str; 3] = ["core", "ffi", "c_void"];
3132
pub const C_VOID_LIBC: [&str; 2] = ["libc", "c_void"];
3233
pub const DEFAULT_TRAIT: [&str; 3] = ["core", "default", "Default"];
3334
pub const DEFAULT_TRAIT_METHOD: [&str; 4] = ["core", "default", "Default", "default"];
35+
pub const DEREF_TRAIT_METHOD: [&str; 5] = ["core", "ops", "deref", "Deref", "deref"];
3436
pub const DISPLAY_FMT_METHOD: [&str; 4] = ["core", "fmt", "Display", "fmt"];
3537
pub const DOUBLE_ENDED_ITERATOR: [&str; 4] = ["core", "iter", "traits", "DoubleEndedIterator"];
3638
pub const DROP: [&str; 3] = ["core", "mem", "drop"];
@@ -67,7 +69,11 @@ pub const OPTION: [&str; 3] = ["core", "option", "Option"];
6769
pub const OPTION_NONE: [&str; 4] = ["core", "option", "Option", "None"];
6870
pub const OPTION_SOME: [&str; 4] = ["core", "option", "Option", "Some"];
6971
pub const ORD: [&str; 3] = ["core", "cmp", "Ord"];
72+
pub const OS_STRING: [&str; 4] = ["std", "ffi", "os_str", "OsString"];
73+
pub const OS_STR_TO_OS_STRING: [&str; 5] = ["std", "ffi", "os_str", "OsStr", "to_os_string"];
7074
pub const PARTIAL_ORD: [&str; 3] = ["core", "cmp", "PartialOrd"];
75+
pub const PATH_BUF: [&str; 3] = ["std", "path", "PathBuf"];
76+
pub const PATH_TO_PATH_BUF: [&str; 4] = ["std", "path", "Path", "to_path_buf"];
7177
pub const PTR_NULL: [&str; 2] = ["ptr", "null"];
7278
pub const PTR_NULL_MUT: [&str; 2] = ["ptr", "null_mut"];
7379
pub const RANGE: [&str; 3] = ["core", "ops", "Range"];
@@ -100,7 +106,9 @@ pub const SLICE_INTO_VEC: [&str; 4] = ["alloc", "slice", "<impl [T]>", "into_vec
100106
pub const SLICE_ITER: [&str; 3] = ["core", "slice", "Iter"];
101107
pub const STRING: [&str; 3] = ["alloc", "string", "String"];
102108
pub const TO_OWNED: [&str; 3] = ["alloc", "borrow", "ToOwned"];
109+
pub const TO_OWNED_METHOD: [&str; 4] = ["alloc", "borrow", "ToOwned", "to_owned"];
103110
pub const TO_STRING: [&str; 3] = ["alloc", "string", "ToString"];
111+
pub const TO_STRING_METHOD: [&str; 4] = ["alloc", "string", "ToString", "to_string"];
104112
pub const TRANSMUTE: [&str; 4] = ["core", "intrinsics", "", "transmute"];
105113
pub const TRY_INTO_RESULT: [&str; 4] = ["std", "ops", "Try", "into_result"];
106114
pub const UNINIT: [&str; 4] = ["core", "intrinsics", "", "uninit"];

0 commit comments

Comments
 (0)