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) (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),
|(used, consumed), (tbb, tdata)| {
// Short-circuit
if (used && consumed) ||
// Give up on loops
tdata.terminator().successors().any(|s| *s == bb)
{
return (true, true);
} }
} else {
let mut vis = LocalUseVisitor { let clone_usage = visit_clone_usage(local, ret_local, &mir, bb);
used: (local, false), if clone_usage.cloned_used && clone_usage.clone_consumed_or_mutated {
consumed_or_mutated: (ret_local, false), // 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 span = terminator.source_info.span;
let scope = terminator.source_info.scope; let scope = terminator.source_info.scope;
let node = mir.source_scopes[scope] let node = mir.source_scopes[scope]
@ -257,7 +251,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
String::new(), String::new(),
app, app,
); );
if used { if clone_usage.cloned_used {
diag.span_note( diag.span_note(
span, span,
"cloned value is neither consumed nor mutated", "cloned value is neither consumed nor mutated",
@ -275,7 +269,6 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClone {
} }
} }
} }
}
} }
/// If `kind` is `y = func(x: &T)` where `T: !Copy`, returns `(DefId of func, x, T, y)`. /// If `kind` is `y = func(x: &T)` where `T: !Copy`, returns `(DefId of func, x, T, y)`.
@ -365,12 +358,22 @@ 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 {
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>) { fn visit_basic_block_data(&mut self, block: mir::BasicBlock, data: &mir::BasicBlockData<'tcx>) {
let statements = &data.statements; let statements = &data.statements;
for (statement_index, statement) in statements.iter().enumerate() { for (statement_index, statement) in statements.iter().enumerate() {
@ -386,28 +389,66 @@ 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; let local = place.local;
if local == self.used.0 if local == self.cloned
&& !matches!( && !matches!(
ctx, ctx,
PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_) PlaceContext::MutatingUse(MutatingUseContext::Drop) | PlaceContext::NonUse(_)
) )
{ {
self.used.1 = true; self.result.cloned_used = true;
} self.result.cloned_consume_or_mutate_loc = self.result.cloned_consume_or_mutate_loc.or_else(|| {
matches!(
if local == self.consumed_or_mutated.0 { ctx,
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
| PlaceContext::MutatingUse(MutatingUseContext::Borrow)
)
.then(|| loc)
});
} else if local == self.clone {
match ctx { match ctx {
PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) PlaceContext::NonMutatingUse(NonMutatingUseContext::Move)
| PlaceContext::MutatingUse(MutatingUseContext::Borrow) => { | PlaceContext::MutatingUse(MutatingUseContext::Borrow) => {
self.consumed_or_mutated.1 = true; 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");
| ^^^^^^^^^ | ^^^^^^^^^