Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 92c4fc3

Browse files
committed
Auto merge of rust-lang#7011 - Jarcho:redundant_clone_fp, r=flip1995
Fix `redundant_clone` fp fixes: rust-lang#5973 fixes: rust-lang#5595 fixes: rust-lang#6998 changelog: Fix `redundant_clone` fp where the cloned value is modified while the clone is in use.
2 parents 38b1fd0 + aaba9b7 commit 92c4fc3

File tree

4 files changed

+207
-113
lines changed

4 files changed

+207
-113
lines changed

clippy_lints/src/redundant_clone.rs

Lines changed: 149 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -199,79 +199,72 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
199199
(local, deref_clone_ret)
200200
};
201201

202-
let is_temp = mir.local_kind(ret_local) == mir::LocalKind::Temp;
203-
204-
// 1. `local` can be moved out if it is not used later.
205-
// 2. If `ret_local` is a temporary and is neither consumed nor mutated, we can remove this `clone`
206-
// call anyway.
207-
let (used, consumed_or_mutated) = traversal::ReversePostorder::new(&mir, bb).skip(1).fold(
208-
(false, !is_temp),
209-
|(used, consumed), (tbb, tdata)| {
210-
// Short-circuit
211-
if (used && consumed) ||
212-
// Give up on loops
213-
tdata.terminator().successors().any(|s| *s == bb)
214-
{
215-
return (true, true);
202+
let clone_usage = if local == ret_local {
203+
CloneUsage {
204+
cloned_used: false,
205+
cloned_consume_or_mutate_loc: None,
206+
clone_consumed_or_mutated: true,
207+
}
208+
} else {
209+
let clone_usage = visit_clone_usage(local, ret_local, &mir, bb);
210+
if clone_usage.cloned_used && clone_usage.clone_consumed_or_mutated {
211+
// cloned value is used, and the clone is modified or moved
212+
continue;
213+
} else if let Some(loc) = clone_usage.cloned_consume_or_mutate_loc {
214+
// cloned value is mutated, and the clone is alive.
215+
if possible_borrower.is_alive_at(ret_local, loc) {
216+
continue;
216217
}
218+
}
219+
clone_usage
220+
};
217221

218-
let mut vis = LocalUseVisitor {
219-
used: (local, false),
220-
consumed_or_mutated: (ret_local, false),
221-
};
222-
vis.visit_basic_block_data(tbb, tdata);
223-
(used || vis.used.1, consumed || vis.consumed_or_mutated.1)
224-
},
225-
);
226-
227-
if !used || !consumed_or_mutated {
228-
let span = terminator.source_info.span;
229-
let scope = terminator.source_info.scope;
230-
let node = mir.source_scopes[scope]
231-
.local_data
232-
.as_ref()
233-
.assert_crate_local()
234-
.lint_root;
235-
236-
if_chain! {
237-
if let Some(snip) = snippet_opt(cx, span);
238-
if let Some(dot) = snip.rfind('.');
239-
then {
240-
let sugg_span = span.with_lo(
241-
span.lo() + BytePos(u32::try_from(dot).unwrap())
242-
);
243-
let mut app = Applicability::MaybeIncorrect;
244-
245-
let call_snip = &snip[dot + 1..];
246-
// Machine applicable when `call_snip` looks like `foobar()`
247-
if let Some(call_snip) = call_snip.strip_suffix("()").map(str::trim) {
248-
if call_snip.as_bytes().iter().all(|b| b.is_ascii_alphabetic() || *b == b'_') {
249-
app = Applicability::MachineApplicable;
250-
}
222+
let span = terminator.source_info.span;
223+
let scope = terminator.source_info.scope;
224+
let node = mir.source_scopes[scope]
225+
.local_data
226+
.as_ref()
227+
.assert_crate_local()
228+
.lint_root;
229+
230+
if_chain! {
231+
if let Some(snip) = snippet_opt(cx, span);
232+
if let Some(dot) = snip.rfind('.');
233+
then {
234+
let sugg_span = span.with_lo(
235+
span.lo() + BytePos(u32::try_from(dot).unwrap())
236+
);
237+
let mut app = Applicability::MaybeIncorrect;
238+
239+
let call_snip = &snip[dot + 1..];
240+
// Machine applicable when `call_snip` looks like `foobar()`
241+
if let Some(call_snip) = call_snip.strip_suffix("()").map(str::trim) {
242+
if call_snip.as_bytes().iter().all(|b| b.is_ascii_alphabetic() || *b == b'_') {
243+
app = Applicability::MachineApplicable;
251244
}
245+
}
252246

253-
span_lint_hir_and_then(cx, REDUNDANT_CLONE, node, sugg_span, "redundant clone", |diag| {
254-
diag.span_suggestion(
255-
sugg_span,
256-
"remove this",
257-
String::new(),
258-
app,
247+
span_lint_hir_and_then(cx, REDUNDANT_CLONE, node, sugg_span, "redundant clone", |diag| {
248+
diag.span_suggestion(
249+
sugg_span,
250+
"remove this",
251+
String::new(),
252+
app,
253+
);
254+
if clone_usage.cloned_used {
255+
diag.span_note(
256+
span,
257+
"cloned value is neither consumed nor mutated",
259258
);
260-
if used {
261-
diag.span_note(
262-
span,
263-
"cloned value is neither consumed nor mutated",
264-
);
265-
} else {
266-
diag.span_note(
267-
span.with_hi(span.lo() + BytePos(u32::try_from(dot).unwrap())),
268-
"this value is dropped without further use",
269-
);
270-
}
271-
});
272-
} else {
273-
span_lint_hir(cx, REDUNDANT_CLONE, node, span, "redundant clone");
274-
}
259+
} else {
260+
diag.span_note(
261+
span.with_hi(span.lo() + BytePos(u32::try_from(dot).unwrap())),
262+
"this value is dropped without further use",
263+
);
264+
}
265+
});
266+
} else {
267+
span_lint_hir(cx, REDUNDANT_CLONE, node, span, "redundant clone");
275268
}
276269
}
277270
}
@@ -365,49 +358,97 @@ fn base_local_and_movability<'tcx>(
365358
(local, deref || field || slice)
366359
}
367360

368-
struct LocalUseVisitor {
369-
used: (mir::Local, bool),
370-
consumed_or_mutated: (mir::Local, bool),
361+
#[derive(Default)]
362+
struct CloneUsage {
363+
/// Whether the cloned value is used after the clone.
364+
cloned_used: bool,
365+
/// The first location where the cloned value is consumed or mutated, if any.
366+
cloned_consume_or_mutate_loc: Option<mir::Location>,
367+
/// Whether the clone value is mutated.
368+
clone_consumed_or_mutated: bool,
371369
}
372-
373-
impl<'tcx> mir::visit::Visitor<'tcx> for LocalUseVisitor {
374-
fn visit_basic_block_data(&mut self, block: mir::BasicBlock, data: &mir::BasicBlockData<'tcx>) {
375-
let statements = &data.statements;
376-
for (statement_index, statement) in statements.iter().enumerate() {
377-
self.visit_statement(statement, mir::Location { block, statement_index });
378-
}
379-
380-
self.visit_terminator(
381-
data.terminator(),
382-
mir::Location {
383-
block,
384-
statement_index: statements.len(),
385-
},
386-
);
370+
fn visit_clone_usage(cloned: mir::Local, clone: mir::Local, mir: &mir::Body<'_>, bb: mir::BasicBlock) -> CloneUsage {
371+
struct V {
372+
cloned: mir::Local,
373+
clone: mir::Local,
374+
result: CloneUsage,
387375
}
376+
impl<'tcx> mir::visit::Visitor<'tcx> for V {
377+
fn visit_basic_block_data(&mut self, block: mir::BasicBlock, data: &mir::BasicBlockData<'tcx>) {
378+
let statements = &data.statements;
379+
for (statement_index, statement) in statements.iter().enumerate() {
380+
self.visit_statement(statement, mir::Location { block, statement_index });
381+
}
388382

389-
fn visit_place(&mut self, place: &mir::Place<'tcx>, ctx: PlaceContext, _: mir::Location) {
390-
let local = place.local;
391-
392-
if local == self.used.0
393-
&& !matches!(
394-
ctx,
395-
PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_)
396-
)
397-
{
398-
self.used.1 = true;
383+
self.visit_terminator(
384+
data.terminator(),
385+
mir::Location {
386+
block,
387+
statement_index: statements.len(),
388+
},
389+
);
399390
}
400391

401-
if local == self.consumed_or_mutated.0 {
402-
match ctx {
403-
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
404-
| PlaceContext::MutatingUse(MutatingUseContext::Borrow) => {
405-
self.consumed_or_mutated.1 = true;
406-
},
407-
_ => {},
392+
fn visit_place(&mut self, place: &mir::Place<'tcx>, ctx: PlaceContext, loc: mir::Location) {
393+
let local = place.local;
394+
395+
if local == self.cloned
396+
&& !matches!(
397+
ctx,
398+
PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_)
399+
)
400+
{
401+
self.result.cloned_used = true;
402+
self.result.cloned_consume_or_mutate_loc = self.result.cloned_consume_or_mutate_loc.or_else(|| {
403+
matches!(
404+
ctx,
405+
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
406+
| PlaceContext::MutatingUse(MutatingUseContext::Borrow)
407+
)
408+
.then(|| loc)
409+
});
410+
} else if local == self.clone {
411+
match ctx {
412+
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
413+
| PlaceContext::MutatingUse(MutatingUseContext::Borrow) => {
414+
self.result.clone_consumed_or_mutated = true;
415+
},
416+
_ => {},
417+
}
408418
}
409419
}
410420
}
421+
422+
let init = CloneUsage {
423+
cloned_used: false,
424+
cloned_consume_or_mutate_loc: None,
425+
// Consider non-temporary clones consumed.
426+
// TODO: Actually check for mutation of non-temporaries.
427+
clone_consumed_or_mutated: mir.local_kind(clone) != mir::LocalKind::Temp,
428+
};
429+
traversal::ReversePostorder::new(&mir, bb)
430+
.skip(1)
431+
.fold(init, |usage, (tbb, tdata)| {
432+
// Short-circuit
433+
if (usage.cloned_used && usage.clone_consumed_or_mutated) ||
434+
// Give up on loops
435+
tdata.terminator().successors().any(|s| *s == bb)
436+
{
437+
return CloneUsage {
438+
cloned_used: true,
439+
clone_consumed_or_mutated: true,
440+
..usage
441+
};
442+
}
443+
444+
let mut v = V {
445+
cloned,
446+
clone,
447+
result: usage,
448+
};
449+
v.visit_basic_block_data(tbb, tdata);
450+
v.result
451+
})
411452
}
412453

413454
/// Determines liveness of each local purely based on `StorageLive`/`Dead`.
@@ -623,4 +664,9 @@ impl PossibleBorrowerMap<'_, '_> {
623664

624665
self.bitset.0 == self.bitset.1
625666
}
667+
668+
fn is_alive_at(&mut self, local: mir::Local, at: mir::Location) -> bool {
669+
self.maybe_live.seek_after_primary_effect(at);
670+
self.maybe_live.contains(local)
671+
}
626672
}

tests/ui/redundant_clone.fixed

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ fn main() {
5454
not_consumed();
5555
issue_5405();
5656
manually_drop();
57+
clone_then_move_cloned();
5758
}
5859

5960
#[derive(Clone)]
@@ -182,3 +183,26 @@ fn manually_drop() {
182183
Arc::from_raw(p);
183184
}
184185
}
186+
187+
fn clone_then_move_cloned() {
188+
// issue #5973
189+
let x = Some(String::new());
190+
// ok, x is moved while the clone is in use.
191+
assert_eq!(x.clone(), None, "not equal {}", x.unwrap());
192+
193+
// issue #5595
194+
fn foo<F: Fn()>(_: &Alpha, _: F) {}
195+
let x = Alpha;
196+
// ok, data is moved while the clone is in use.
197+
foo(&x.clone(), move || {
198+
let _ = x;
199+
});
200+
201+
// issue #6998
202+
struct S(String);
203+
impl S {
204+
fn m(&mut self) {}
205+
}
206+
let mut x = S(String::new());
207+
x.0.clone().chars().for_each(|_| x.m());
208+
}

tests/ui/redundant_clone.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ fn main() {
5454
not_consumed();
5555
issue_5405();
5656
manually_drop();
57+
clone_then_move_cloned();
5758
}
5859

5960
#[derive(Clone)]
@@ -182,3 +183,26 @@ fn manually_drop() {
182183
Arc::from_raw(p);
183184
}
184185
}
186+
187+
fn clone_then_move_cloned() {
188+
// issue #5973
189+
let x = Some(String::new());
190+
// ok, x is moved while the clone is in use.
191+
assert_eq!(x.clone(), None, "not equal {}", x.unwrap());
192+
193+
// issue #5595
194+
fn foo<F: Fn()>(_: &Alpha, _: F) {}
195+
let x = Alpha;
196+
// ok, data is moved while the clone is in use.
197+
foo(&x.clone(), move || {
198+
let _ = x;
199+
});
200+
201+
// issue #6998
202+
struct S(String);
203+
impl S {
204+
fn m(&mut self) {}
205+
}
206+
let mut x = S(String::new());
207+
x.0.clone().chars().for_each(|_| x.m());
208+
}

0 commit comments

Comments
 (0)