Fix redundant_clone
fp where the cloned value is modified while the clone is in use.
This commit is contained in:
parent
44bf60f62d
commit
aaba9b78a2
@ -199,79 +199,72 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
|
|||||||
(local, deref_clone_ret)
|
(local, deref_clone_ret)
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_temp = mir.local_kind(ret_local) == mir::LocalKind::Temp;
|
let clone_usage = if local == ret_local {
|
||||||
|
CloneUsage {
|
||||||
// 1. `local` can be moved out if it is not used later.
|
cloned_used: false,
|
||||||
// 2. If `ret_local` is a temporary and is neither consumed nor mutated, we can remove this `clone`
|
cloned_consume_or_mutate_loc: None,
|
||||||
// call anyway.
|
clone_consumed_or_mutated: true,
|
||||||
let (used, consumed_or_mutated) = traversal::ReversePostorder::new(&mir, bb).skip(1).fold(
|
}
|
||||||
(false, !is_temp),
|
} else {
|
||||||
|(used, consumed), (tbb, tdata)| {
|
let clone_usage = visit_clone_usage(local, ret_local, &mir, bb);
|
||||||
// Short-circuit
|
if clone_usage.cloned_used && clone_usage.clone_consumed_or_mutated {
|
||||||
if (used && consumed) ||
|
// cloned value is used, and the clone is modified or moved
|
||||||
// Give up on loops
|
continue;
|
||||||
tdata.terminator().successors().any(|s| *s == bb)
|
} else if let Some(loc) = clone_usage.cloned_consume_or_mutate_loc {
|
||||||
{
|
// cloned value is mutated, and the clone is alive.
|
||||||
return (true, true);
|
if possible_borrower.is_alive_at(ret_local, loc) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
clone_usage
|
||||||
|
};
|
||||||
|
|
||||||
let mut vis = LocalUseVisitor {
|
let span = terminator.source_info.span;
|
||||||
used: (local, false),
|
let scope = terminator.source_info.scope;
|
||||||
consumed_or_mutated: (ret_local, false),
|
let node = mir.source_scopes[scope]
|
||||||
};
|
.local_data
|
||||||
vis.visit_basic_block_data(tbb, tdata);
|
.as_ref()
|
||||||
(used || vis.used.1, consumed || vis.consumed_or_mutated.1)
|
.assert_crate_local()
|
||||||
},
|
.lint_root;
|
||||||
);
|
|
||||||
|
|
||||||
if !used || !consumed_or_mutated {
|
if_chain! {
|
||||||
let span = terminator.source_info.span;
|
if let Some(snip) = snippet_opt(cx, span);
|
||||||
let scope = terminator.source_info.scope;
|
if let Some(dot) = snip.rfind('.');
|
||||||
let node = mir.source_scopes[scope]
|
then {
|
||||||
.local_data
|
let sugg_span = span.with_lo(
|
||||||
.as_ref()
|
span.lo() + BytePos(u32::try_from(dot).unwrap())
|
||||||
.assert_crate_local()
|
);
|
||||||
.lint_root;
|
let mut app = Applicability::MaybeIncorrect;
|
||||||
|
|
||||||
if_chain! {
|
let call_snip = &snip[dot + 1..];
|
||||||
if let Some(snip) = snippet_opt(cx, span);
|
// Machine applicable when `call_snip` looks like `foobar()`
|
||||||
if let Some(dot) = snip.rfind('.');
|
if let Some(call_snip) = call_snip.strip_suffix("()").map(str::trim) {
|
||||||
then {
|
if call_snip.as_bytes().iter().all(|b| b.is_ascii_alphabetic() || *b == b'_') {
|
||||||
let sugg_span = span.with_lo(
|
app = Applicability::MachineApplicable;
|
||||||
span.lo() + BytePos(u32::try_from(dot).unwrap())
|
|
||||||
);
|
|
||||||
let mut app = Applicability::MaybeIncorrect;
|
|
||||||
|
|
||||||
let call_snip = &snip[dot + 1..];
|
|
||||||
// Machine applicable when `call_snip` looks like `foobar()`
|
|
||||||
if let Some(call_snip) = call_snip.strip_suffix("()").map(str::trim) {
|
|
||||||
if call_snip.as_bytes().iter().all(|b| b.is_ascii_alphabetic() || *b == b'_') {
|
|
||||||
app = Applicability::MachineApplicable;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
span_lint_hir_and_then(cx, REDUNDANT_CLONE, node, sugg_span, "redundant clone", |diag| {
|
|
||||||
diag.span_suggestion(
|
|
||||||
sugg_span,
|
|
||||||
"remove this",
|
|
||||||
String::new(),
|
|
||||||
app,
|
|
||||||
);
|
|
||||||
if used {
|
|
||||||
diag.span_note(
|
|
||||||
span,
|
|
||||||
"cloned value is neither consumed nor mutated",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
diag.span_note(
|
|
||||||
span.with_hi(span.lo() + BytePos(u32::try_from(dot).unwrap())),
|
|
||||||
"this value is dropped without further use",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
span_lint_hir(cx, REDUNDANT_CLONE, node, span, "redundant clone");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span_lint_hir_and_then(cx, REDUNDANT_CLONE, node, sugg_span, "redundant clone", |diag| {
|
||||||
|
diag.span_suggestion(
|
||||||
|
sugg_span,
|
||||||
|
"remove this",
|
||||||
|
String::new(),
|
||||||
|
app,
|
||||||
|
);
|
||||||
|
if clone_usage.cloned_used {
|
||||||
|
diag.span_note(
|
||||||
|
span,
|
||||||
|
"cloned value is neither consumed nor mutated",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
diag.span_note(
|
||||||
|
span.with_hi(span.lo() + BytePos(u32::try_from(dot).unwrap())),
|
||||||
|
"this value is dropped without further use",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
span_lint_hir(cx, REDUNDANT_CLONE, node, span, "redundant clone");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -365,49 +358,97 @@ fn base_local_and_movability<'tcx>(
|
|||||||
(local, deref || field || slice)
|
(local, deref || field || slice)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LocalUseVisitor {
|
#[derive(Default)]
|
||||||
used: (mir::Local, bool),
|
struct CloneUsage {
|
||||||
consumed_or_mutated: (mir::Local, bool),
|
/// Whether the cloned value is used after the clone.
|
||||||
|
cloned_used: bool,
|
||||||
|
/// The first location where the cloned value is consumed or mutated, if any.
|
||||||
|
cloned_consume_or_mutate_loc: Option<mir::Location>,
|
||||||
|
/// 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 {
|
||||||
impl<'tcx> mir::visit::Visitor<'tcx> for LocalUseVisitor {
|
struct V {
|
||||||
fn visit_basic_block_data(&mut self, block: mir::BasicBlock, data: &mir::BasicBlockData<'tcx>) {
|
cloned: mir::Local,
|
||||||
let statements = &data.statements;
|
clone: mir::Local,
|
||||||
for (statement_index, statement) in statements.iter().enumerate() {
|
result: CloneUsage,
|
||||||
self.visit_statement(statement, mir::Location { block, statement_index });
|
|
||||||
}
|
|
||||||
|
|
||||||
self.visit_terminator(
|
|
||||||
data.terminator(),
|
|
||||||
mir::Location {
|
|
||||||
block,
|
|
||||||
statement_index: statements.len(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
|
||||||
fn visit_place(&mut self, place: &mir::Place<'tcx>, ctx: PlaceContext, _: mir::Location) {
|
self.visit_terminator(
|
||||||
let local = place.local;
|
data.terminator(),
|
||||||
|
mir::Location {
|
||||||
if local == self.used.0
|
block,
|
||||||
&& !matches!(
|
statement_index: statements.len(),
|
||||||
ctx,
|
},
|
||||||
PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_)
|
);
|
||||||
)
|
|
||||||
{
|
|
||||||
self.used.1 = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if local == self.consumed_or_mutated.0 {
|
fn visit_place(&mut self, place: &mir::Place<'tcx>, ctx: PlaceContext, loc: mir::Location) {
|
||||||
match ctx {
|
let local = place.local;
|
||||||
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
|
|
||||||
| PlaceContext::MutatingUse(MutatingUseContext::Borrow) => {
|
if local == self.cloned
|
||||||
self.consumed_or_mutated.1 = true;
|
&& !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`.
|
/// Determines liveness of each local purely based on `StorageLive`/`Dead`.
|
||||||
@ -623,4 +664,9 @@ impl PossibleBorrowerMap<'_, '_> {
|
|||||||
|
|
||||||
self.bitset.0 == self.bitset.1
|
self.bitset.0 == self.bitset.1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,7 @@ fn main() {
|
|||||||
not_consumed();
|
not_consumed();
|
||||||
issue_5405();
|
issue_5405();
|
||||||
manually_drop();
|
manually_drop();
|
||||||
|
clone_then_move_cloned();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -182,3 +183,26 @@ fn manually_drop() {
|
|||||||
Arc::from_raw(p);
|
Arc::from_raw(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clone_then_move_cloned() {
|
||||||
|
// issue #5973
|
||||||
|
let x = Some(String::new());
|
||||||
|
// ok, x is moved while the clone is in use.
|
||||||
|
assert_eq!(x.clone(), None, "not equal {}", x.unwrap());
|
||||||
|
|
||||||
|
// issue #5595
|
||||||
|
fn foo<F: Fn()>(_: &Alpha, _: F) {}
|
||||||
|
let x = Alpha;
|
||||||
|
// ok, data is moved while the clone is in use.
|
||||||
|
foo(&x.clone(), move || {
|
||||||
|
let _ = x;
|
||||||
|
});
|
||||||
|
|
||||||
|
// issue #6998
|
||||||
|
struct S(String);
|
||||||
|
impl S {
|
||||||
|
fn m(&mut self) {}
|
||||||
|
}
|
||||||
|
let mut x = S(String::new());
|
||||||
|
x.0.clone().chars().for_each(|_| x.m());
|
||||||
|
}
|
||||||
|
@ -54,6 +54,7 @@ fn main() {
|
|||||||
not_consumed();
|
not_consumed();
|
||||||
issue_5405();
|
issue_5405();
|
||||||
manually_drop();
|
manually_drop();
|
||||||
|
clone_then_move_cloned();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -182,3 +183,26 @@ fn manually_drop() {
|
|||||||
Arc::from_raw(p);
|
Arc::from_raw(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clone_then_move_cloned() {
|
||||||
|
// issue #5973
|
||||||
|
let x = Some(String::new());
|
||||||
|
// ok, x is moved while the clone is in use.
|
||||||
|
assert_eq!(x.clone(), None, "not equal {}", x.unwrap());
|
||||||
|
|
||||||
|
// issue #5595
|
||||||
|
fn foo<F: Fn()>(_: &Alpha, _: F) {}
|
||||||
|
let x = Alpha;
|
||||||
|
// ok, data is moved while the clone is in use.
|
||||||
|
foo(&x.clone(), move || {
|
||||||
|
let _ = x;
|
||||||
|
});
|
||||||
|
|
||||||
|
// issue #6998
|
||||||
|
struct S(String);
|
||||||
|
impl S {
|
||||||
|
fn m(&mut self) {}
|
||||||
|
}
|
||||||
|
let mut x = S(String::new());
|
||||||
|
x.0.clone().chars().for_each(|_| x.m());
|
||||||
|
}
|
||||||
|
@ -108,61 +108,61 @@ LL | let _t = tup.0.clone();
|
|||||||
| ^^^^^
|
| ^^^^^
|
||||||
|
|
||||||
error: redundant clone
|
error: redundant clone
|
||||||
--> $DIR/redundant_clone.rs:62:25
|
--> $DIR/redundant_clone.rs:63:25
|
||||||
|
|
|
|
||||||
LL | if b { (a.clone(), a.clone()) } else { (Alpha, a) }
|
LL | if b { (a.clone(), a.clone()) } else { (Alpha, a) }
|
||||||
| ^^^^^^^^ help: remove this
|
| ^^^^^^^^ help: remove this
|
||||||
|
|
|
|
||||||
note: this value is dropped without further use
|
note: this value is dropped without further use
|
||||||
--> $DIR/redundant_clone.rs:62:24
|
--> $DIR/redundant_clone.rs:63:24
|
||||||
|
|
|
|
||||||
LL | if b { (a.clone(), a.clone()) } else { (Alpha, a) }
|
LL | if b { (a.clone(), a.clone()) } else { (Alpha, a) }
|
||||||
| ^
|
| ^
|
||||||
|
|
||||||
error: redundant clone
|
|
||||||
--> $DIR/redundant_clone.rs:119:15
|
|
||||||
|
|
|
||||||
LL | let _s = s.clone();
|
|
||||||
| ^^^^^^^^ help: remove this
|
|
||||||
|
|
|
||||||
note: this value is dropped without further use
|
|
||||||
--> $DIR/redundant_clone.rs:119:14
|
|
||||||
|
|
|
||||||
LL | let _s = s.clone();
|
|
||||||
| ^
|
|
||||||
|
|
||||||
error: redundant clone
|
error: redundant clone
|
||||||
--> $DIR/redundant_clone.rs:120:15
|
--> $DIR/redundant_clone.rs:120:15
|
||||||
|
|
|
|
||||||
LL | let _t = t.clone();
|
LL | let _s = s.clone();
|
||||||
| ^^^^^^^^ help: remove this
|
| ^^^^^^^^ help: remove this
|
||||||
|
|
|
|
||||||
note: this value is dropped without further use
|
note: this value is dropped without further use
|
||||||
--> $DIR/redundant_clone.rs:120:14
|
--> $DIR/redundant_clone.rs:120:14
|
||||||
|
|
|
|
||||||
|
LL | let _s = s.clone();
|
||||||
|
| ^
|
||||||
|
|
||||||
|
error: redundant clone
|
||||||
|
--> $DIR/redundant_clone.rs:121:15
|
||||||
|
|
|
||||||
|
LL | let _t = t.clone();
|
||||||
|
| ^^^^^^^^ help: remove this
|
||||||
|
|
|
||||||
|
note: this value is dropped without further use
|
||||||
|
--> $DIR/redundant_clone.rs:121:14
|
||||||
|
|
|
||||||
LL | let _t = t.clone();
|
LL | let _t = t.clone();
|
||||||
| ^
|
| ^
|
||||||
|
|
||||||
error: redundant clone
|
error: redundant clone
|
||||||
--> $DIR/redundant_clone.rs:130:19
|
--> $DIR/redundant_clone.rs:131:19
|
||||||
|
|
|
|
||||||
LL | let _f = f.clone();
|
LL | let _f = f.clone();
|
||||||
| ^^^^^^^^ help: remove this
|
| ^^^^^^^^ help: remove this
|
||||||
|
|
|
|
||||||
note: this value is dropped without further use
|
note: this value is dropped without further use
|
||||||
--> $DIR/redundant_clone.rs:130:18
|
--> $DIR/redundant_clone.rs:131:18
|
||||||
|
|
|
|
||||||
LL | let _f = f.clone();
|
LL | let _f = f.clone();
|
||||||
| ^
|
| ^
|
||||||
|
|
||||||
error: redundant clone
|
error: redundant clone
|
||||||
--> $DIR/redundant_clone.rs:142:14
|
--> $DIR/redundant_clone.rs:143:14
|
||||||
|
|
|
|
||||||
LL | let y = x.clone().join("matthias");
|
LL | let y = x.clone().join("matthias");
|
||||||
| ^^^^^^^^ help: remove this
|
| ^^^^^^^^ help: remove this
|
||||||
|
|
|
|
||||||
note: cloned value is neither consumed nor mutated
|
note: cloned value is neither consumed nor mutated
|
||||||
--> $DIR/redundant_clone.rs:142:13
|
--> $DIR/redundant_clone.rs:143:13
|
||||||
|
|
|
|
||||||
LL | let y = x.clone().join("matthias");
|
LL | let y = x.clone().join("matthias");
|
||||||
| ^^^^^^^^^
|
| ^^^^^^^^^
|
||||||
|
Loading…
x
Reference in New Issue
Block a user