Fix redundant_clone fp where the cloned value is modified while the clone is in use.

This commit is contained in:
Jason Newcomb 2021-03-31 14:58:17 -04:00
parent 44bf60f62d
commit aaba9b78a2
No known key found for this signature in database
GPG Key ID: DA59E8643A37ED06
4 changed files with 215 additions and 121 deletions

View File

@ -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)
}
} }

View File

@ -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());
}

View File

@ -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());
}

View File

@ -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");
| ^^^^^^^^^ | ^^^^^^^^^