]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 | 1 | use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; |
2b03887a | 2 | use clippy_utils::mir::{visit_local_usage, LocalUsage, PossibleBorrowerMap}; |
cdc7bbd5 | 3 | use clippy_utils::source::snippet_opt; |
487cf647 | 4 | use clippy_utils::ty::{has_drop, is_copy, is_type_diagnostic_item, is_type_lang_item, walk_ptrs_ty_depth}; |
cdc7bbd5 | 5 | use clippy_utils::{fn_has_unsatisfiable_preds, match_def_path, paths}; |
f20569fa | 6 | use if_chain::if_chain; |
f20569fa XL |
7 | use rustc_errors::Applicability; |
8 | use rustc_hir::intravisit::FnKind; | |
9ffffee4 | 9 | use rustc_hir::{def_id, Body, FnDecl, LangItem}; |
f20569fa | 10 | use rustc_lint::{LateContext, LateLintPass}; |
2b03887a FG |
11 | use rustc_middle::mir; |
12 | use rustc_middle::ty::{self, Ty}; | |
f20569fa | 13 | use rustc_session::{declare_lint_pass, declare_tool_lint}; |
9ffffee4 | 14 | use rustc_span::def_id::LocalDefId; |
f20569fa XL |
15 | use rustc_span::source_map::{BytePos, Span}; |
16 | use rustc_span::sym; | |
f20569fa XL |
17 | |
18 | macro_rules! unwrap_or_continue { | |
19 | ($x:expr) => { | |
20 | match $x { | |
21 | Some(x) => x, | |
22 | None => continue, | |
23 | } | |
24 | }; | |
25 | } | |
26 | ||
27 | declare_clippy_lint! { | |
94222f64 XL |
28 | /// ### What it does |
29 | /// Checks for a redundant `clone()` (and its relatives) which clones an owned | |
f20569fa XL |
30 | /// value that is going to be dropped without further use. |
31 | /// | |
94222f64 XL |
32 | /// ### Why is this bad? |
33 | /// It is not always possible for the compiler to eliminate useless | |
f20569fa XL |
34 | /// allocations and deallocations generated by redundant `clone()`s. |
35 | /// | |
94222f64 | 36 | /// ### Known problems |
f20569fa XL |
37 | /// False-negatives: analysis performed by this lint is conservative and limited. |
38 | /// | |
94222f64 | 39 | /// ### Example |
f20569fa XL |
40 | /// ```rust |
41 | /// # use std::path::Path; | |
42 | /// # #[derive(Clone)] | |
43 | /// # struct Foo; | |
44 | /// # impl Foo { | |
45 | /// # fn new() -> Self { Foo {} } | |
46 | /// # } | |
47 | /// # fn call(x: Foo) {} | |
48 | /// { | |
49 | /// let x = Foo::new(); | |
50 | /// call(x.clone()); | |
51 | /// call(x.clone()); // this can just pass `x` | |
52 | /// } | |
53 | /// | |
54 | /// ["lorem", "ipsum"].join(" ").to_string(); | |
55 | /// | |
56 | /// Path::new("/a/b").join("c").to_path_buf(); | |
57 | /// ``` | |
a2a8927a | 58 | #[clippy::version = "1.32.0"] |
f20569fa | 59 | pub REDUNDANT_CLONE, |
49aad941 | 60 | nursery, |
f20569fa XL |
61 | "`clone()` of an owned value that is going to be dropped immediately" |
62 | } | |
63 | ||
64 | declare_lint_pass!(RedundantClone => [REDUNDANT_CLONE]); | |
65 | ||
66 | impl<'tcx> LateLintPass<'tcx> for RedundantClone { | |
923072b8 | 67 | #[expect(clippy::too_many_lines)] |
f20569fa XL |
68 | fn check_fn( |
69 | &mut self, | |
70 | cx: &LateContext<'tcx>, | |
71 | _: FnKind<'tcx>, | |
72 | _: &'tcx FnDecl<'_>, | |
9ffffee4 | 73 | _: &'tcx Body<'_>, |
f20569fa | 74 | _: Span, |
9ffffee4 | 75 | def_id: LocalDefId, |
f20569fa | 76 | ) { |
f20569fa XL |
77 | // Building MIR for `fn`s with unsatisfiable preds results in ICE. |
78 | if fn_has_unsatisfiable_preds(cx, def_id.to_def_id()) { | |
79 | return; | |
80 | } | |
81 | ||
82 | let mir = cx.tcx.optimized_mir(def_id.to_def_id()); | |
83 | ||
2b03887a | 84 | let mut possible_borrower = PossibleBorrowerMap::new(cx, mir); |
f20569fa | 85 | |
f2b60f7d | 86 | for (bb, bbdata) in mir.basic_blocks.iter_enumerated() { |
f20569fa XL |
87 | let terminator = bbdata.terminator(); |
88 | ||
89 | if terminator.source_info.span.from_expansion() { | |
90 | continue; | |
91 | } | |
92 | ||
93 | // Give up on loops | |
923072b8 | 94 | if terminator.successors().any(|s| s == bb) { |
f20569fa XL |
95 | continue; |
96 | } | |
97 | ||
98 | let (fn_def_id, arg, arg_ty, clone_ret) = | |
99 | unwrap_or_continue!(is_call_with_ref_arg(cx, mir, &terminator.kind)); | |
100 | ||
101 | let from_borrow = match_def_path(cx, fn_def_id, &paths::CLONE_TRAIT_METHOD) | |
102 | || match_def_path(cx, fn_def_id, &paths::TO_OWNED_METHOD) | |
103 | || (match_def_path(cx, fn_def_id, &paths::TO_STRING_METHOD) | |
487cf647 | 104 | && is_type_lang_item(cx, arg_ty, LangItem::String)); |
f20569fa XL |
105 | |
106 | let from_deref = !from_borrow | |
107 | && (match_def_path(cx, fn_def_id, &paths::PATH_TO_PATH_BUF) | |
108 | || match_def_path(cx, fn_def_id, &paths::OS_STR_TO_OS_STRING)); | |
109 | ||
110 | if !from_borrow && !from_deref { | |
111 | continue; | |
112 | } | |
113 | ||
cdc7bbd5 | 114 | if let ty::Adt(def, _) = arg_ty.kind() { |
5e7ed085 | 115 | if def.is_manually_drop() { |
f20569fa XL |
116 | continue; |
117 | } | |
118 | } | |
119 | ||
136023e0 | 120 | // `{ arg = &cloned; clone(move arg); }` or `{ arg = &cloned; to_path_buf(arg); }` |
f20569fa XL |
121 | let (cloned, cannot_move_out) = unwrap_or_continue!(find_stmt_assigns_to(cx, mir, arg, from_borrow, bb)); |
122 | ||
123 | let loc = mir::Location { | |
124 | block: bb, | |
125 | statement_index: bbdata.statements.len(), | |
126 | }; | |
127 | ||
128 | // `Local` to be cloned, and a local of `clone` call's destination | |
129 | let (local, ret_local) = if from_borrow { | |
130 | // `res = clone(arg)` can be turned into `res = move arg;` | |
131 | // if `arg` is the only borrow of `cloned` at this point. | |
132 | ||
133 | if cannot_move_out || !possible_borrower.only_borrowers(&[arg], cloned, loc) { | |
134 | continue; | |
135 | } | |
136 | ||
137 | (cloned, clone_ret) | |
138 | } else { | |
139 | // `arg` is a reference as it is `.deref()`ed in the previous block. | |
140 | // Look into the predecessor block and find out the source of deref. | |
141 | ||
064997fb | 142 | let ps = &mir.basic_blocks.predecessors()[bb]; |
f20569fa XL |
143 | if ps.len() != 1 { |
144 | continue; | |
145 | } | |
146 | let pred_terminator = mir[ps[0]].terminator(); | |
147 | ||
148 | // receiver of the `deref()` call | |
149 | let (pred_arg, deref_clone_ret) = if_chain! { | |
150 | if let Some((pred_fn_def_id, pred_arg, pred_arg_ty, res)) = | |
151 | is_call_with_ref_arg(cx, mir, &pred_terminator.kind); | |
152 | if res == cloned; | |
153 | if cx.tcx.is_diagnostic_item(sym::deref_method, pred_fn_def_id); | |
154 | if is_type_diagnostic_item(cx, pred_arg_ty, sym::PathBuf) | |
155 | || is_type_diagnostic_item(cx, pred_arg_ty, sym::OsString); | |
156 | then { | |
157 | (pred_arg, res) | |
158 | } else { | |
159 | continue; | |
160 | } | |
161 | }; | |
162 | ||
163 | let (local, cannot_move_out) = | |
164 | unwrap_or_continue!(find_stmt_assigns_to(cx, mir, pred_arg, true, ps[0])); | |
165 | let loc = mir::Location { | |
166 | block: bb, | |
f2b60f7d | 167 | statement_index: mir.basic_blocks[bb].statements.len(), |
f20569fa XL |
168 | }; |
169 | ||
170 | // This can be turned into `res = move local` if `arg` and `cloned` are not borrowed | |
171 | // at the last statement: | |
172 | // | |
173 | // ``` | |
174 | // pred_arg = &local; | |
175 | // cloned = deref(pred_arg); | |
176 | // arg = &cloned; | |
177 | // StorageDead(pred_arg); | |
178 | // res = to_path_buf(cloned); | |
179 | // ``` | |
180 | if cannot_move_out || !possible_borrower.only_borrowers(&[arg, cloned], local, loc) { | |
181 | continue; | |
182 | } | |
183 | ||
184 | (local, deref_clone_ret) | |
185 | }; | |
186 | ||
cdc7bbd5 XL |
187 | let clone_usage = if local == ret_local { |
188 | CloneUsage { | |
189 | cloned_used: false, | |
190 | cloned_consume_or_mutate_loc: None, | |
191 | clone_consumed_or_mutated: true, | |
192 | } | |
193 | } else { | |
194 | let clone_usage = visit_clone_usage(local, ret_local, mir, bb); | |
195 | if clone_usage.cloned_used && clone_usage.clone_consumed_or_mutated { | |
196 | // cloned value is used, and the clone is modified or moved | |
197 | continue; | |
198 | } else if let Some(loc) = clone_usage.cloned_consume_or_mutate_loc { | |
199 | // cloned value is mutated, and the clone is alive. | |
5099ac24 | 200 | if possible_borrower.local_is_alive_at(ret_local, loc) { |
cdc7bbd5 | 201 | continue; |
f20569fa | 202 | } |
cdc7bbd5 XL |
203 | } |
204 | clone_usage | |
205 | }; | |
f20569fa | 206 | |
cdc7bbd5 XL |
207 | let span = terminator.source_info.span; |
208 | let scope = terminator.source_info.scope; | |
209 | let node = mir.source_scopes[scope] | |
210 | .local_data | |
211 | .as_ref() | |
212 | .assert_crate_local() | |
213 | .lint_root; | |
214 | ||
215 | if_chain! { | |
216 | if let Some(snip) = snippet_opt(cx, span); | |
217 | if let Some(dot) = snip.rfind('.'); | |
218 | then { | |
219 | let sugg_span = span.with_lo( | |
220 | span.lo() + BytePos(u32::try_from(dot).unwrap()) | |
221 | ); | |
222 | let mut app = Applicability::MaybeIncorrect; | |
223 | ||
224 | let call_snip = &snip[dot + 1..]; | |
225 | // Machine applicable when `call_snip` looks like `foobar()` | |
226 | if let Some(call_snip) = call_snip.strip_suffix("()").map(str::trim) { | |
227 | if call_snip.as_bytes().iter().all(|b| b.is_ascii_alphabetic() || *b == b'_') { | |
228 | app = Applicability::MachineApplicable; | |
f20569fa | 229 | } |
cdc7bbd5 | 230 | } |
f20569fa | 231 | |
cdc7bbd5 XL |
232 | span_lint_hir_and_then(cx, REDUNDANT_CLONE, node, sugg_span, "redundant clone", |diag| { |
233 | diag.span_suggestion( | |
234 | sugg_span, | |
235 | "remove this", | |
923072b8 | 236 | "", |
cdc7bbd5 XL |
237 | app, |
238 | ); | |
239 | if clone_usage.cloned_used { | |
240 | diag.span_note( | |
241 | span, | |
242 | "cloned value is neither consumed nor mutated", | |
f20569fa | 243 | ); |
cdc7bbd5 XL |
244 | } else { |
245 | diag.span_note( | |
246 | span.with_hi(span.lo() + BytePos(u32::try_from(dot).unwrap())), | |
247 | "this value is dropped without further use", | |
248 | ); | |
249 | } | |
250 | }); | |
251 | } else { | |
252 | span_lint_hir(cx, REDUNDANT_CLONE, node, span, "redundant clone"); | |
f20569fa XL |
253 | } |
254 | } | |
255 | } | |
256 | } | |
257 | } | |
258 | ||
259 | /// If `kind` is `y = func(x: &T)` where `T: !Copy`, returns `(DefId of func, x, T, y)`. | |
260 | fn is_call_with_ref_arg<'tcx>( | |
261 | cx: &LateContext<'tcx>, | |
262 | mir: &'tcx mir::Body<'tcx>, | |
263 | kind: &'tcx mir::TerminatorKind<'tcx>, | |
264 | ) -> Option<(def_id::DefId, mir::Local, Ty<'tcx>, mir::Local)> { | |
265 | if_chain! { | |
266 | if let mir::TerminatorKind::Call { func, args, destination, .. } = kind; | |
267 | if args.len() == 1; | |
268 | if let mir::Operand::Move(mir::Place { local, .. }) = &args[0]; | |
923072b8 FG |
269 | if let ty::FnDef(def_id, _) = *func.ty(mir, cx.tcx).kind(); |
270 | if let (inner_ty, 1) = walk_ptrs_ty_depth(args[0].ty(mir, cx.tcx)); | |
f20569fa XL |
271 | if !is_copy(cx, inner_ty); |
272 | then { | |
923072b8 | 273 | Some((def_id, *local, inner_ty, destination.as_local()?)) |
f20569fa XL |
274 | } else { |
275 | None | |
276 | } | |
277 | } | |
278 | } | |
279 | ||
280 | type CannotMoveOut = bool; | |
281 | ||
282 | /// Finds the first `to = (&)from`, and returns | |
283 | /// ``Some((from, whether `from` cannot be moved out))``. | |
284 | fn find_stmt_assigns_to<'tcx>( | |
285 | cx: &LateContext<'tcx>, | |
286 | mir: &mir::Body<'tcx>, | |
287 | to_local: mir::Local, | |
288 | by_ref: bool, | |
289 | bb: mir::BasicBlock, | |
290 | ) -> Option<(mir::Local, CannotMoveOut)> { | |
f2b60f7d | 291 | let rvalue = mir.basic_blocks[bb].statements.iter().rev().find_map(|stmt| { |
f20569fa XL |
292 | if let mir::StatementKind::Assign(box (mir::Place { local, .. }, v)) = &stmt.kind { |
293 | return if *local == to_local { Some(v) } else { None }; | |
294 | } | |
295 | ||
296 | None | |
297 | })?; | |
298 | ||
923072b8 | 299 | match (by_ref, rvalue) { |
f20569fa XL |
300 | (true, mir::Rvalue::Ref(_, _, place)) | (false, mir::Rvalue::Use(mir::Operand::Copy(place))) => { |
301 | Some(base_local_and_movability(cx, mir, *place)) | |
302 | }, | |
303 | (false, mir::Rvalue::Ref(_, _, place)) => { | |
304 | if let [mir::ProjectionElem::Deref] = place.as_ref().projection { | |
305 | Some(base_local_and_movability(cx, mir, *place)) | |
306 | } else { | |
307 | None | |
308 | } | |
309 | }, | |
310 | _ => None, | |
311 | } | |
312 | } | |
313 | ||
314 | /// Extracts and returns the undermost base `Local` of given `place`. Returns `place` itself | |
315 | /// if it is already a `Local`. | |
316 | /// | |
317 | /// Also reports whether given `place` cannot be moved out. | |
318 | fn base_local_and_movability<'tcx>( | |
319 | cx: &LateContext<'tcx>, | |
320 | mir: &mir::Body<'tcx>, | |
321 | place: mir::Place<'tcx>, | |
322 | ) -> (mir::Local, CannotMoveOut) { | |
f20569fa XL |
323 | // Dereference. You cannot move things out from a borrowed value. |
324 | let mut deref = false; | |
325 | // Accessing a field of an ADT that has `Drop`. Moving the field out will cause E0509. | |
326 | let mut field = false; | |
327 | // If projection is a slice index then clone can be removed only if the | |
328 | // underlying type implements Copy | |
329 | let mut slice = false; | |
330 | ||
fe692bf9 FG |
331 | for (base, elem) in place.as_ref().iter_projections() { |
332 | let base_ty = base.ty(&mir.local_decls, cx.tcx).ty; | |
f20569fa | 333 | deref |= matches!(elem, mir::ProjectionElem::Deref); |
fe692bf9 FG |
334 | field |= matches!(elem, mir::ProjectionElem::Field(..)) && has_drop(cx, base_ty); |
335 | slice |= matches!(elem, mir::ProjectionElem::Index(..)) && !is_copy(cx, base_ty); | |
f20569fa XL |
336 | } |
337 | ||
fe692bf9 | 338 | (place.local, deref || field || slice) |
f20569fa XL |
339 | } |
340 | ||
cdc7bbd5 XL |
341 | #[derive(Default)] |
342 | struct CloneUsage { | |
343 | /// Whether the cloned value is used after the clone. | |
344 | cloned_used: bool, | |
345 | /// The first location where the cloned value is consumed or mutated, if any. | |
346 | cloned_consume_or_mutate_loc: Option<mir::Location>, | |
347 | /// Whether the clone value is mutated. | |
348 | clone_consumed_or_mutated: bool, | |
f20569fa | 349 | } |
f20569fa | 350 | |
2b03887a FG |
351 | fn visit_clone_usage(cloned: mir::Local, clone: mir::Local, mir: &mir::Body<'_>, bb: mir::BasicBlock) -> CloneUsage { |
352 | if let Some(( | |
353 | LocalUsage { | |
354 | local_use_locs: cloned_use_locs, | |
355 | local_consume_or_mutate_locs: cloned_consume_or_mutate_locs, | |
94222f64 | 356 | }, |
2b03887a FG |
357 | LocalUsage { |
358 | local_use_locs: _, | |
359 | local_consume_or_mutate_locs: clone_consume_or_mutate_locs, | |
360 | }, | |
361 | )) = visit_local_usage( | |
362 | &[cloned, clone], | |
363 | mir, | |
364 | mir::Location { | |
365 | block: bb, | |
366 | statement_index: mir.basic_blocks[bb].statements.len(), | |
367 | }, | |
368 | ) | |
369 | .map(|mut vec| (vec.remove(0), vec.remove(0))) | |
370 | { | |
371 | CloneUsage { | |
372 | cloned_used: !cloned_use_locs.is_empty(), | |
373 | cloned_consume_or_mutate_loc: cloned_consume_or_mutate_locs.first().copied(), | |
374 | // Consider non-temporary clones consumed. | |
375 | // TODO: Actually check for mutation of non-temporaries. | |
376 | clone_consumed_or_mutated: mir.local_kind(clone) != mir::LocalKind::Temp | |
377 | || !clone_consume_or_mutate_locs.is_empty(), | |
f20569fa | 378 | } |
2b03887a FG |
379 | } else { |
380 | CloneUsage { | |
381 | cloned_used: true, | |
382 | cloned_consume_or_mutate_loc: None, | |
383 | clone_consumed_or_mutated: true, | |
5e7ed085 | 384 | } |
5e7ed085 FG |
385 | } |
386 | } |