]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/redundant_clone.rs
Auto merge of #3511 - phansch:remove_allow_doc_markdown, r=phansch
[rust.git] / clippy_lints / src / redundant_clone.rs
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::{MutatingUseContext, 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     has_drop, in_macro, is_copy, match_def_path, match_type, paths, snippet_opt, span_lint_node,
27     span_lint_node_and_then, walk_ptrs_ty_depth,
28 };
29 use if_chain::if_chain;
30 use matches::matches;
31 use std::convert::TryFrom;
32
33 macro_rules! unwrap_or_continue {
34     ($x:expr) => {
35         match $x {
36             Some(x) => x,
37             None => continue,
38         }
39     };
40 }
41
42 /// **What it does:** Checks for a redudant `clone()` (and its relatives) which clones an owned
43 /// value that is going to be dropped without further use.
44 ///
45 /// **Why is this bad?** It is not always possible for the compiler to eliminate useless
46 /// allocations and deallocations generated by redundant `clone()`s.
47 ///
48 /// **Known problems:**
49 ///
50 /// * Suggestions made by this lint could require NLL to be enabled.
51 /// * False-positive if there is a borrow preventing the value from moving out.
52 ///
53 /// ```rust
54 /// let x = String::new();
55 ///
56 /// let y = &x;
57 ///
58 /// foo(x.clone()); // This lint suggests to remove this `clone()`
59 /// ```
60 ///
61 /// **Example:**
62 /// ```rust
63 /// {
64 ///     let x = Foo::new();
65 ///     call(x.clone());
66 ///     call(x.clone()); // this can just pass `x`
67 /// }
68 ///
69 /// ["lorem", "ipsum"].join(" ").to_string()
70 ///
71 /// Path::new("/a/b").join("c").to_path_buf()
72 /// ```
73 declare_clippy_lint! {
74     pub REDUNDANT_CLONE,
75     nursery,
76     "`clone()` of an owned value that is going to be dropped immediately"
77 }
78
79 pub struct RedundantClone;
80
81 impl LintPass for RedundantClone {
82     fn get_lints(&self) -> LintArray {
83         lint_array!(REDUNDANT_CLONE)
84     }
85 }
86
87 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for RedundantClone {
88     fn check_fn(
89         &mut self,
90         cx: &LateContext<'a, 'tcx>,
91         _: FnKind<'tcx>,
92         _: &'tcx FnDecl,
93         body: &'tcx Body,
94         _: Span,
95         _: NodeId,
96     ) {
97         let def_id = cx.tcx.hir().body_owner_def_id(body.id());
98         let mir = cx.tcx.optimized_mir(def_id);
99
100         for (bb, bbdata) in mir.basic_blocks().iter_enumerated() {
101             let terminator = bbdata.terminator();
102
103             if in_macro(terminator.source_info.span) {
104                 continue;
105             }
106
107             // Give up on loops
108             if terminator.successors().any(|s| *s == bb) {
109                 continue;
110             }
111
112             let (fn_def_id, arg, arg_ty, _) = unwrap_or_continue!(is_call_with_ref_arg(cx, mir, &terminator.kind));
113
114             let from_borrow = match_def_path(cx.tcx, fn_def_id, &paths::CLONE_TRAIT_METHOD)
115                 || match_def_path(cx.tcx, fn_def_id, &paths::TO_OWNED_METHOD)
116                 || (match_def_path(cx.tcx, fn_def_id, &paths::TO_STRING_METHOD)
117                     && match_type(cx, arg_ty, &paths::STRING));
118
119             let from_deref = !from_borrow
120                 && (match_def_path(cx.tcx, fn_def_id, &paths::PATH_TO_PATH_BUF)
121                     || match_def_path(cx.tcx, fn_def_id, &paths::OS_STR_TO_OS_STRING));
122
123             if !from_borrow && !from_deref {
124                 continue;
125             }
126
127             // _1 in MIR `{ _2 = &_1; clone(move _2); }` or `{ _2 = _1; to_path_buf(_2); } (from_deref)
128             // In case of `from_deref`, `arg` is already a reference since it is `deref`ed in the previous
129             // block.
130             let (cloned, cannot_move_out) = unwrap_or_continue!(find_stmt_assigns_to(
131                 cx,
132                 mir,
133                 arg,
134                 from_borrow,
135                 bbdata.statements.iter()
136             ));
137
138             if from_borrow && cannot_move_out {
139                 continue;
140             }
141
142             // _1 in MIR `{ _2 = &_1; _3 = deref(move _2); } -> { _4 = _3; to_path_buf(move _4); }`
143             let referent = if from_deref {
144                 let ps = mir.predecessors_for(bb);
145                 if ps.len() != 1 {
146                     continue;
147                 }
148                 let pred_terminator = mir[ps[0]].terminator();
149
150                 let pred_arg = if_chain! {
151                     if let Some((pred_fn_def_id, pred_arg, pred_arg_ty, Some(res))) =
152                         is_call_with_ref_arg(cx, mir, &pred_terminator.kind);
153                     if *res == mir::Place::Local(cloned);
154                     if match_def_path(cx.tcx, pred_fn_def_id, &paths::DEREF_TRAIT_METHOD);
155                     if match_type(cx, pred_arg_ty, &paths::PATH_BUF)
156                         || match_type(cx, pred_arg_ty, &paths::OS_STRING);
157                     then {
158                         pred_arg
159                     } else {
160                         continue;
161                     }
162                 };
163
164                 let (local, cannot_move_out) = unwrap_or_continue!(find_stmt_assigns_to(
165                     cx,
166                     mir,
167                     pred_arg,
168                     true,
169                     mir[ps[0]].statements.iter()
170                 ));
171                 if cannot_move_out {
172                     continue;
173                 }
174                 local
175             } else {
176                 cloned
177             };
178
179             let used_later = traversal::ReversePostorder::new(&mir, bb).skip(1).any(|(tbb, tdata)| {
180                 // Give up on loops
181                 if tdata.terminator().successors().any(|s| *s == bb) {
182                     return true;
183                 }
184
185                 let mut vis = LocalUseVisitor {
186                     local: referent,
187                     used_other_than_drop: false,
188                 };
189                 vis.visit_basic_block_data(tbb, tdata);
190                 vis.used_other_than_drop
191             });
192
193             if !used_later {
194                 let span = terminator.source_info.span;
195                 let node = if let mir::ClearCrossCrate::Set(scope_local_data) = &mir.source_scope_local_data {
196                     scope_local_data[terminator.source_info.scope].lint_root
197                 } else {
198                     unreachable!()
199                 };
200
201                 if_chain! {
202                     if let Some(snip) = snippet_opt(cx, span);
203                     if let Some(dot) = snip.rfind('.');
204                     then {
205                         let sugg_span = span.with_lo(
206                             span.lo() + BytePos(u32::try_from(dot).unwrap())
207                         );
208
209                         span_lint_node_and_then(cx, REDUNDANT_CLONE, node, sugg_span, "redundant clone", |db| {
210                             db.span_suggestion_with_applicability(
211                                 sugg_span,
212                                 "remove this",
213                                 String::new(),
214                                 Applicability::MaybeIncorrect,
215                             );
216                             db.span_note(
217                                 span.with_hi(span.lo() + BytePos(u32::try_from(dot).unwrap())),
218                                 "this value is dropped without further use",
219                             );
220                         });
221                     } else {
222                         span_lint_node(cx, REDUNDANT_CLONE, node, span, "redundant clone");
223                     }
224                 }
225             }
226         }
227     }
228 }
229
230 /// If `kind` is `y = func(x: &T)` where `T: !Copy`, returns `(DefId of func, x, T, y)`.
231 fn is_call_with_ref_arg<'tcx>(
232     cx: &LateContext<'_, 'tcx>,
233     mir: &'tcx mir::Mir<'tcx>,
234     kind: &'tcx mir::TerminatorKind<'tcx>,
235 ) -> Option<(def_id::DefId, mir::Local, ty::Ty<'tcx>, Option<&'tcx mir::Place<'tcx>>)> {
236     if_chain! {
237         if let TerminatorKind::Call { func, args, destination, .. } = kind;
238         if args.len() == 1;
239         if let mir::Operand::Move(mir::Place::Local(local)) = &args[0];
240         if let ty::FnDef(def_id, _) = func.ty(&*mir, cx.tcx).sty;
241         if let (inner_ty, 1) = walk_ptrs_ty_depth(args[0].ty(&*mir, cx.tcx));
242         if !is_copy(cx, inner_ty);
243         then {
244             Some((def_id, *local, inner_ty, destination.as_ref().map(|(dest, _)| dest)))
245         } else {
246             None
247         }
248     }
249 }
250
251 type CannotMoveOut = bool;
252
253 /// Finds the first `to = (&)from`, and returns
254 /// ``Some((from, [`true` if `from` cannot be moved out]))``.
255 fn find_stmt_assigns_to<'a, 'tcx: 'a>(
256     cx: &LateContext<'_, 'tcx>,
257     mir: &mir::Mir<'tcx>,
258     to: mir::Local,
259     by_ref: bool,
260     stmts: impl DoubleEndedIterator<Item = &'a mir::Statement<'tcx>>,
261 ) -> Option<(mir::Local, CannotMoveOut)> {
262     stmts
263         .rev()
264         .find_map(|stmt| {
265             if let mir::StatementKind::Assign(mir::Place::Local(local), v) = &stmt.kind {
266                 if *local == to {
267                     return Some(v);
268                 }
269             }
270
271             None
272         })
273         .and_then(|v| {
274             if by_ref {
275                 if let mir::Rvalue::Ref(_, _, ref place) = **v {
276                     return base_local_and_movability(cx, mir, place);
277                 }
278             } else if let mir::Rvalue::Use(mir::Operand::Copy(ref place)) = **v {
279                 return base_local_and_movability(cx, mir, place);
280             }
281             None
282         })
283 }
284
285 /// Extracts and returns the undermost base `Local` of given `place`. Returns `place` itself
286 /// if it is already a `Local`.
287 ///
288 /// Also reports whether given `place` cannot be moved out.
289 fn base_local_and_movability<'tcx>(
290     cx: &LateContext<'_, 'tcx>,
291     mir: &mir::Mir<'tcx>,
292     mut place: &mir::Place<'tcx>,
293 ) -> Option<(mir::Local, CannotMoveOut)> {
294     use rustc::mir::Place::*;
295
296     // Dereference. You cannot move things out from a borrowed value.
297     let mut deref = false;
298     // Accessing a field of an ADT that has `Drop`. Moving the field out will cause E0509.
299     let mut field = false;
300
301     loop {
302         match place {
303             Local(local) => return Some((*local, deref || field)),
304             Projection(proj) => {
305                 place = &proj.base;
306                 deref = deref || matches!(proj.elem, mir::ProjectionElem::Deref);
307                 if !field && matches!(proj.elem, mir::ProjectionElem::Field(..)) {
308                     field = has_drop(cx, place.ty(&mir.local_decls, cx.tcx).to_ty(cx.tcx));
309                 }
310             },
311             _ => return None,
312         }
313     }
314 }
315
316 struct LocalUseVisitor {
317     local: mir::Local,
318     used_other_than_drop: bool,
319 }
320
321 impl<'tcx> mir::visit::Visitor<'tcx> for LocalUseVisitor {
322     fn visit_basic_block_data(&mut self, block: mir::BasicBlock, data: &mir::BasicBlockData<'tcx>) {
323         let statements = &data.statements;
324         for (statement_index, statement) in statements.iter().enumerate() {
325             self.visit_statement(block, statement, mir::Location { block, statement_index });
326
327             // Once flagged, skip remaining statements
328             if self.used_other_than_drop {
329                 return;
330             }
331         }
332
333         self.visit_terminator(
334             block,
335             data.terminator(),
336             mir::Location {
337                 block,
338                 statement_index: statements.len(),
339             },
340         );
341     }
342
343     fn visit_local(&mut self, local: &mir::Local, ctx: PlaceContext<'tcx>, _: mir::Location) {
344         match ctx {
345             PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_) => return,
346             _ => {},
347         }
348
349         if *local == self.local {
350             self.used_other_than_drop = true;
351         }
352     }
353 }