Skip to content

Commit ec7bbc4

Browse files
committed
New lint: mutable_borrow_of_copy
1 parent 62589a2 commit ec7bbc4

17 files changed

+524
-30
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6648,6 +6648,7 @@ Released 2018-09-13
66486648
[`mut_mut`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mut
66496649
[`mut_mutex_lock`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_mutex_lock
66506650
[`mut_range_bound`]: https://rust-lang.github.io/rust-clippy/master/index.html#mut_range_bound
6651+
[`mutable_borrow_of_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutable_borrow_of_copy
66516652
[`mutable_key_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type
66526653
[`mutex_atomic`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_atomic
66536654
[`mutex_integer`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_integer
@@ -7104,6 +7105,7 @@ Released 2018-09-13
71047105
[`cargo-ignore-publish`]: https://doc.rust-lang.org/clippy/lint_configuration.html#cargo-ignore-publish
71057106
[`check-incompatible-msrv-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-incompatible-msrv-in-tests
71067107
[`check-inconsistent-struct-field-initializers`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-inconsistent-struct-field-initializers
7108+
[`check-mutable-borrow-of-copy-in-tests`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-mutable-borrow-of-copy-in-tests
71077109
[`check-private-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#check-private-items
71087110
[`cognitive-complexity-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#cognitive-complexity-threshold
71097111
[`const-literal-digits-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#const-literal-digits-threshold

book/src/lint_configuration.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,16 @@ fn main() {
462462
* [`inconsistent_struct_constructor`](https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor)
463463

464464

465+
## `check-mutable-borrow-of-copy-in-tests`
466+
Whether to search for mutable borrows of freshly copied data in tests.
467+
468+
**Default Value:** `true`
469+
470+
---
471+
**Affected lints:**
472+
* [`mutable_borrow_of_copy`](https://rust-lang.github.io/rust-clippy/master/index.html#mutable_borrow_of_copy)
473+
474+
465475
## `check-private-items`
466476
Whether to also run the listed lints on private items.
467477

clippy_config/src/conf.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,9 @@ define_Conf! {
564564
/// [from rust-clippy#11846]: https://github.com/rust-lang/rust-clippy/issues/11846#issuecomment-1820747924
565565
#[lints(inconsistent_struct_constructor)]
566566
check_inconsistent_struct_field_initializers: bool = false,
567+
/// Whether to search for mutable borrows of freshly copied data in tests.
568+
#[lints(mutable_borrow_of_copy)]
569+
check_mutable_borrow_of_copy_in_tests: bool = true,
567570
/// Whether to also run the listed lints on private items.
568571
#[lints(missing_errors_doc, missing_panics_doc, missing_safety_doc, unnecessary_safety_doc)]
569572
check_private_items: bool = false,

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
531531
crate::multiple_unsafe_ops_per_block::MULTIPLE_UNSAFE_OPS_PER_BLOCK_INFO,
532532
crate::mut_key::MUTABLE_KEY_TYPE_INFO,
533533
crate::mut_mut::MUT_MUT_INFO,
534+
crate::mutable_borrow_of_copy::MUTABLE_BORROW_OF_COPY_INFO,
534535
crate::mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL_INFO,
535536
crate::mutex_atomic::MUTEX_ATOMIC_INFO,
536537
crate::mutex_atomic::MUTEX_INTEGER_INFO,

clippy_lints/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ mod multiple_bound_locations;
240240
mod multiple_unsafe_ops_per_block;
241241
mod mut_key;
242242
mod mut_mut;
243+
mod mutable_borrow_of_copy;
243244
mod mutable_debug_assertion;
244245
mod mutex_atomic;
245246
mod needless_arbitrary_self_type;
@@ -447,6 +448,8 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
447448

448449
let format_args_storage = FormatArgsStorage::default();
449450
let attr_storage = AttrStorage::default();
451+
<<<<<<< Conflict 1 of 1
452+
+++++++ Contents of side #1
450453

451454
let early_lints: [Box<dyn Fn() -> Box<dyn EarlyLintPass + 'static> + sync::DynSend + sync::DynSync>; _] = [
452455
{
@@ -848,6 +851,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
848851
Box::new(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)),
849852
Box::new(|_| Box::new(volatile_composites::VolatileComposites)),
850853
Box::new(|_| Box::<replace_box::ReplaceBox>::default()),
854+
Box::new(|_| Box::new(mutable_borrow_of_copy::MutableBorrowOfCopy::new(conf))),
851855
// add late passes here, used by `cargo dev new_lint`
852856
];
853857
store.late_passes.extend(late_lints);
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use clippy_config::Conf;
2+
use clippy_utils::diagnostics::span_lint_and_then;
3+
use clippy_utils::res::MaybeResPath as _;
4+
use clippy_utils::ty::is_copy;
5+
use clippy_utils::{get_enclosing_block, is_in_test};
6+
use rustc_ast::BindingMode;
7+
use rustc_errors::Applicability;
8+
use rustc_hir::{Block, BorrowKind, Expr, ExprKind, Mutability, Node, PatKind};
9+
use rustc_lint::{LateContext, LateLintPass};
10+
use rustc_session::impl_lint_pass;
11+
12+
declare_clippy_lint! {
13+
/// ### What it does
14+
/// Checks for taking a mutable reference on a freshly copied variable due to the use of a block returning a value implementing `Copy`.
15+
///
16+
/// ### Why is this bad?
17+
/// Using a block will make a copy of the block result if its type
18+
/// implements `Copy`. This might be an indication of a failed attempt
19+
/// to borrow a variable.
20+
///
21+
/// ### Example
22+
/// ```no_run
23+
/// # unsafe fn unsafe_func(_: &mut i32) {}
24+
/// let mut a = 10;
25+
/// let double_a_ref = &mut unsafe { // Unsafe block needed to call `unsafe_func`
26+
/// unsafe_func(&mut a);
27+
/// a
28+
/// };
29+
/// ```
30+
/// If you intend to take a reference on `a` and you need the block,
31+
/// create the reference inside the block instead:
32+
/// ```no_run
33+
/// # unsafe fn unsafe_func(_: &mut i32) {}
34+
/// let mut a = 10;
35+
/// let double_a_ref = unsafe { // Unsafe block needed to call `unsafe_func`
36+
/// unsafe_func(&mut a);
37+
/// &mut a
38+
/// };
39+
/// ```
40+
#[clippy::version = "1.90.0"]
41+
pub MUTABLE_BORROW_OF_COPY,
42+
suspicious,
43+
"mutable borrow of a data which was just copied"
44+
}
45+
46+
pub struct MutableBorrowOfCopy {
47+
check_in_tests: bool,
48+
}
49+
50+
impl MutableBorrowOfCopy {
51+
pub const fn new(conf: &Conf) -> Self {
52+
Self {
53+
check_in_tests: conf.check_mutable_borrow_of_copy_in_tests,
54+
}
55+
}
56+
}
57+
58+
impl_lint_pass!(MutableBorrowOfCopy => [MUTABLE_BORROW_OF_COPY]);
59+
60+
impl LateLintPass<'_> for MutableBorrowOfCopy {
61+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
62+
if !expr.span.from_expansion()
63+
&& let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, sub_expr) = expr.kind
64+
&& let ExprKind::Block(block, _) = sub_expr.kind
65+
&& !block.targeted_by_break
66+
&& block.span.eq_ctxt(expr.span)
67+
&& let Some(block_expr) = block.expr
68+
&& let block_ty = cx.typeck_results().expr_ty_adjusted(block_expr)
69+
&& is_copy(cx, block_ty)
70+
&& is_copied_defined_outside_block(cx, block_expr, block)
71+
&& (self.check_in_tests || !is_in_test(cx.tcx, expr.hir_id))
72+
{
73+
span_lint_and_then(
74+
cx,
75+
MUTABLE_BORROW_OF_COPY,
76+
expr.span,
77+
"mutable borrow of a value which was just copied",
78+
|diag| {
79+
diag.multipart_suggestion(
80+
"try building the reference inside the block",
81+
vec![
82+
(expr.span.until(block.span), String::new()),
83+
(block_expr.span.shrink_to_lo(), String::from("&mut ")),
84+
],
85+
Applicability::MaybeIncorrect,
86+
);
87+
},
88+
);
89+
}
90+
}
91+
}
92+
93+
/// Checks if `expr` denotes a mutable variable defined outside `block`. This peels away field
94+
/// accesses or indexing of such a variable first.
95+
fn is_copied_defined_outside_block(cx: &LateContext<'_>, mut expr: &Expr<'_>, block: &Block<'_>) -> bool {
96+
while let ExprKind::Field(base, _) | ExprKind::Index(base, _, _) = expr.kind {
97+
expr = base;
98+
}
99+
if let Some(mut current) = expr.res_local_id()
100+
&& let Node::Pat(pat) = cx.tcx.hir_node(current)
101+
&& matches!(pat.kind, PatKind::Binding(BindingMode::MUT, ..))
102+
{
103+
// Scan enclosing blocks until we find `block` (if so, the local is defined within it), or we loop
104+
// or can't find blocks anymore.
105+
loop {
106+
match get_enclosing_block(cx, current).map(|b| b.hir_id) {
107+
Some(parent) if parent == block.hir_id => return false,
108+
Some(parent) if parent != current => current = parent,
109+
_ => return true,
110+
}
111+
}
112+
}
113+
false
114+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
check-mutable-borrow-of-copy-in-tests = false
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#[test]
2+
fn in_test() {
3+
let mut a = [10; 2];
4+
let _ = &mut { a }; // Do not lint
5+
}
6+
7+
fn main() {
8+
let mut a = [10; 2];
9+
let _ = { &mut a }; //~ mutable_borrow_of_copy
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#[test]
2+
fn in_test() {
3+
let mut a = [10; 2];
4+
let _ = &mut { a }; // Do not lint
5+
}
6+
7+
fn main() {
8+
let mut a = [10; 2];
9+
let _ = &mut { a }; //~ mutable_borrow_of_copy
10+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error: mutable borrow of a value which was just copied
2+
--> tests/ui-toml/mutable_borrow_of_copy/mutable_borrow_of_copy.rs:9:13
3+
|
4+
LL | let _ = &mut { a };
5+
| ^^^^^^^^^^
6+
|
7+
= note: `-D clippy::mutable-borrow-of-copy` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::mutable_borrow_of_copy)]`
9+
help: try building the reference inside the block
10+
|
11+
LL - let _ = &mut { a };
12+
LL + let _ = { &mut a };
13+
|
14+
15+
error: aborting due to 1 previous error
16+

0 commit comments

Comments
 (0)