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,32 +199,26 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
(local, deref_clone_ret)
};
let is_temp = mir.local_kind(ret_local) == mir::LocalKind::Temp;
// 1. `local` can be moved out if it is not used later.
// 2. If `ret_local` is a temporary and is neither consumed nor mutated, we can remove this `clone`
// call anyway.
let (used, consumed_or_mutated) = traversal::ReversePostorder::new(&mir, bb).skip(1).fold(
(false, !is_temp),
|(used, consumed), (tbb, tdata)| {
// Short-circuit
if (used && consumed) ||
// Give up on loops
tdata.terminator().successors().any(|s| *s == bb)
{
return (true, true);
let clone_usage = if local == ret_local {
CloneUsage {
cloned_used: false,
cloned_consume_or_mutate_loc: None,
clone_consumed_or_mutated: true,
}
let mut vis = LocalUseVisitor {
used: (local, false),
consumed_or_mutated: (ret_local, false),
} else {
let clone_usage = visit_clone_usage(local, ret_local, &mir, bb);
if clone_usage.cloned_used && clone_usage.clone_consumed_or_mutated {
// cloned value is used, and the clone is modified or moved
continue;
} else if let Some(loc) = clone_usage.cloned_consume_or_mutate_loc {
// cloned value is mutated, and the clone is alive.
if possible_borrower.is_alive_at(ret_local, loc) {
continue;
}
}
clone_usage
};
vis.visit_basic_block_data(tbb, tdata);
(used || vis.used.1, consumed || vis.consumed_or_mutated.1)
},
);
if !used || !consumed_or_mutated {
let span = terminator.source_info.span;
let scope = terminator.source_info.scope;
let node = mir.source_scopes[scope]
@ -257,7 +251,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
String::new(),
app,
);
if used {
if clone_usage.cloned_used {
diag.span_note(
span,
"cloned value is neither consumed nor mutated",
@ -276,7 +270,6 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
}
}
}
}
/// If `kind` is `y = func(x: &T)` where `T: !Copy`, returns `(DefId of func, x, T, y)`.
fn is_call_with_ref_arg<'tcx>(
@ -365,12 +358,22 @@ fn base_local_and_movability<'tcx>(
(local, deref || field || slice)
}
struct LocalUseVisitor {
used: (mir::Local, bool),
consumed_or_mutated: (mir::Local, bool),
#[derive(Default)]
struct CloneUsage {
/// 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,
}
impl<'tcx> mir::visit::Visitor<'tcx> for LocalUseVisitor {
fn visit_clone_usage(cloned: mir::Local, clone: mir::Local, mir: &mir::Body<'_>, bb: mir::BasicBlock) -> CloneUsage {
struct V {
cloned: mir::Local,
clone: mir::Local,
result: CloneUsage,
}
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() {
@ -386,23 +389,29 @@ impl<'tcx> mir::visit::Visitor<'tcx> for LocalUseVisitor {
);
}
fn visit_place(&mut self, place: &mir::Place<'tcx>, ctx: PlaceContext, _: mir::Location) {
fn visit_place(&mut self, place: &mir::Place<'tcx>, ctx: PlaceContext, loc: mir::Location) {
let local = place.local;
if local == self.used.0
if local == self.cloned
&& !matches!(
ctx,
PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_)
)
{
self.used.1 = true;
}
if local == self.consumed_or_mutated.0 {
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.consumed_or_mutated.1 = true;
self.result.clone_consumed_or_mutated = true;
},
_ => {},
}
@ -410,6 +419,38 @@ impl<'tcx> mir::visit::Visitor<'tcx> for LocalUseVisitor {
}
}
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`.
#[derive(Copy, Clone)]
struct MaybeStorageLive;
@ -623,4 +664,9 @@ impl PossibleBorrowerMap<'_, '_> {
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();
issue_5405();
manually_drop();
clone_then_move_cloned();
}
#[derive(Clone)]
@ -182,3 +183,26 @@ fn manually_drop() {
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();
issue_5405();
manually_drop();
clone_then_move_cloned();
}
#[derive(Clone)]
@ -182,3 +183,26 @@ fn manually_drop() {
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
--> $DIR/redundant_clone.rs:62:25
--> $DIR/redundant_clone.rs:63:25
|
LL | if b { (a.clone(), a.clone()) } else { (Alpha, a) }
| ^^^^^^^^ help: remove this
|
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) }
| ^
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
--> $DIR/redundant_clone.rs:120:15
|
LL | let _t = t.clone();
LL | let _s = s.clone();
| ^^^^^^^^ help: remove this
|
note: this value is dropped without further use
--> $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();
| ^
error: redundant clone
--> $DIR/redundant_clone.rs:130:19
--> $DIR/redundant_clone.rs:131:19
|
LL | let _f = f.clone();
| ^^^^^^^^ help: remove this
|
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();
| ^
error: redundant clone
--> $DIR/redundant_clone.rs:142:14
--> $DIR/redundant_clone.rs:143:14
|
LL | let y = x.clone().join("matthias");
| ^^^^^^^^ help: remove this
|
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");
| ^^^^^^^^^