Skip to content

Commit 20d1bb1

Browse files
committed
[assigning_clones]: bail out when the source borrows from target
1 parent dff9164 commit 20d1bb1

File tree

3 files changed

+122
-1
lines changed

3 files changed

+122
-1
lines changed

clippy_lints/src/assigning_clones.rs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
use clippy_config::msrvs::{self, Msrv};
22
use clippy_utils::diagnostics::span_lint_and_then;
33
use clippy_utils::macros::HirNode;
4+
use clippy_utils::mir::{enclosing_mir, PossibleBorrowerMap};
45
use clippy_utils::sugg::Sugg;
56
use clippy_utils::{is_trait_method, local_is_initialized, path_to_local};
67
use rustc_errors::Applicability;
78
use rustc_hir::{self as hir, Expr, ExprKind};
89
use rustc_lint::{LateContext, LateLintPass};
10+
use rustc_middle::mir;
911
use rustc_middle::ty::{self, Instance, Mutability};
1012
use rustc_session::impl_lint_pass;
1113
use rustc_span::def_id::DefId;
1214
use rustc_span::symbol::sym;
13-
use rustc_span::{ExpnKind, SyntaxContext};
15+
use rustc_span::{ExpnKind, Span, SyntaxContext};
1416

1517
declare_clippy_lint! {
1618
/// ### What it does
@@ -144,6 +146,7 @@ fn extract_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<
144146
};
145147

146148
Some(CallCandidate {
149+
span: expr.span,
147150
target,
148151
kind,
149152
method_def_id: resolved_method.def_id(),
@@ -215,13 +218,72 @@ fn is_ok_to_suggest<'tcx>(cx: &LateContext<'tcx>, lhs: &Expr<'tcx>, call: &CallC
215218
return false;
216219
};
217220

221+
if clone_source_borrows_from_dest(cx, lhs, call.span) {
222+
return false;
223+
}
224+
218225
// Now take a look if the impl block defines an implementation for the method that we're interested
219226
// in. If not, then we're using a default implementation, which is not interesting, so we will
220227
// not suggest the lint.
221228
let implemented_fns = cx.tcx.impl_item_implementor_ids(impl_block);
222229
implemented_fns.contains_key(&provided_fn.def_id)
223230
}
224231

232+
/// Checks if the data being cloned borrows from the place that is being assigned to:
233+
///
234+
/// ```
235+
/// let mut s = String::new();
236+
/// let s2 = &s;
237+
/// s = s2.to_owned();
238+
/// ```
239+
///
240+
/// This cannot be written `s2.clone_into(&mut s)` because it has conflicting borrows.
241+
fn clone_source_borrows_from_dest(cx: &LateContext<'_>, lhs: &Expr<'_>, call_span: Span) -> bool {
242+
let Some(mir) = enclosing_mir(cx.tcx, lhs.hir_id) else {
243+
return false;
244+
};
245+
let PossibleBorrowerMap { map: borrow_map, .. } = PossibleBorrowerMap::new(cx, mir);
246+
247+
// The operation `dest = src.to_owned()` in MIR is split up across 3 blocks.
248+
// For the doc example above that would be roughly:
249+
//
250+
// bb0:
251+
// s2 = &s
252+
// s_temp = ToOwned::to_owned(move s2) -> bb1
253+
//
254+
// bb1:
255+
// drop(s) -> bb2 // drop the old string
256+
//
257+
// bb2:
258+
// s = s_temp
259+
for bb in mir.basic_blocks.iter() {
260+
let terminator = bb.terminator();
261+
262+
// Look for the to_owned/clone call.
263+
if terminator.source_info.span == call_span
264+
&& let mir::TerminatorKind::Call {
265+
args,
266+
target: Some(drop_bb),
267+
..
268+
} = &terminator.kind
269+
&& let [source] = &**args
270+
&& let mir::Operand::Move(source) = &source.node
271+
// Block 2 only has the `drop()` terminator from to the assignment
272+
&& let drop_bb = &mir.basic_blocks[*drop_bb]
273+
&& let mir::TerminatorKind::Drop { target: assign_bb, .. } = drop_bb.terminator().kind
274+
// Block 3 has the final assignment to the original local
275+
&& let assign_bb = &mir.basic_blocks[assign_bb]
276+
&& let [assignment, ..] = &*assign_bb.statements
277+
&& let mir::StatementKind::Assign(box (borrowed, _)) = &assignment.kind
278+
&& let Some(borrowers) = borrow_map.get(&borrowed.local)
279+
&& borrowers.contains(source.local)
280+
{
281+
return true;
282+
}
283+
}
284+
false
285+
}
286+
225287
fn suggest<'tcx>(
226288
cx: &LateContext<'tcx>,
227289
ctxt: SyntaxContext,
@@ -255,6 +317,7 @@ enum TargetTrait {
255317

256318
#[derive(Debug)]
257319
struct CallCandidate<'tcx> {
320+
span: Span,
258321
target: TargetTrait,
259322
kind: CallKind<'tcx>,
260323
// DefId of the called method from an impl block that implements the target trait

tests/ui/assigning_clones.fixed

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,3 +272,32 @@ impl<'a> Add for &'a mut HasCloneFrom {
272272
self
273273
}
274274
}
275+
276+
mod borrowck_conflicts {
277+
//! Cases where clone_into and friends cannot be used because the src/dest have conflicting
278+
//! borrows.
279+
use std::path::PathBuf;
280+
281+
fn issue12444(mut name: String) {
282+
let parts = name.split(", ").collect::<Vec<_>>();
283+
let first = *parts.first().unwrap();
284+
name = first.to_owned();
285+
}
286+
287+
fn issue12444_simple() {
288+
let mut s = String::new();
289+
let s2 = &s;
290+
s = s2.to_owned();
291+
}
292+
293+
fn issue12460(mut name: String) {
294+
if let Some(stripped_name) = name.strip_prefix("baz-") {
295+
name = stripped_name.to_owned();
296+
}
297+
}
298+
299+
fn issue12749() {
300+
let mut path = PathBuf::from("/a/b/c");
301+
path = path.components().as_path().to_owned();
302+
}
303+
}

tests/ui/assigning_clones.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,3 +272,32 @@ impl<'a> Add for &'a mut HasCloneFrom {
272272
self
273273
}
274274
}
275+
276+
mod borrowck_conflicts {
277+
//! Cases where clone_into and friends cannot be used because the src/dest have conflicting
278+
//! borrows.
279+
use std::path::PathBuf;
280+
281+
fn issue12444(mut name: String) {
282+
let parts = name.split(", ").collect::<Vec<_>>();
283+
let first = *parts.first().unwrap();
284+
name = first.to_owned();
285+
}
286+
287+
fn issue12444_simple() {
288+
let mut s = String::new();
289+
let s2 = &s;
290+
s = s2.to_owned();
291+
}
292+
293+
fn issue12460(mut name: String) {
294+
if let Some(stripped_name) = name.strip_prefix("baz-") {
295+
name = stripped_name.to_owned();
296+
}
297+
}
298+
299+
fn issue12749() {
300+
let mut path = PathBuf::from("/a/b/c");
301+
path = path.components().as_path().to_owned();
302+
}
303+
}

0 commit comments

Comments
 (0)