Skip to content

Commit d047b47

Browse files
committed
fix(mut_mutex_lock): don't lint on indexing and field accesses
1 parent 116b5e0 commit d047b47

4 files changed

Lines changed: 176 additions & 5 deletions

File tree

clippy_lints/src/methods/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2519,6 +2519,10 @@ declare_clippy_lint! {
25192519
/// guarantee that the mutex isn't locked, instead of just a runtime
25202520
/// guarantee.
25212521
///
2522+
/// ### Known problems
2523+
///
2524+
/// This lint doesn't take lifetimes into account.
2525+
///
25222526
/// ### Example
25232527
/// ```no_run
25242528
/// use std::sync::{Arc, Mutex};

clippy_lints/src/methods/mut_mutex_lock.rs

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,102 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
22
use clippy_utils::expr_custom_deref_adjustment;
33
use clippy_utils::res::MaybeDef;
4-
use clippy_utils::ty::peel_and_count_ty_refs;
4+
use clippy_utils::ty::{implements_trait, peel_and_count_ty_refs};
55
use rustc_errors::Applicability;
6-
use rustc_hir::{Expr, Mutability};
6+
use rustc_hir::{Expr, ExprKind, Mutability, UnOp};
77
use rustc_lint::LateContext;
8+
use rustc_middle::ty::adjustment::{Adjust, DerefAdjustKind};
89
use rustc_span::{Span, sym};
910

1011
use super::MUT_MUTEX_LOCK;
1112

1213
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, recv: &'tcx Expr<'tcx>, name_span: Span) {
14+
let typeck = cx.typeck_results();
15+
16+
// Make sure that we have a mutable access to `Mutex`:
17+
//
18+
// 1. Check that there are no custom deref adjustments turning the access immutable (e.g.
19+
// `Arc<Mutex>` derefs to `&Mutex`)
1320
if matches!(expr_custom_deref_adjustment(cx, recv), None | Some(Mutability::Mut))
14-
// NOTE: the reason we don't use `expr_ty_adjusted` here is that a call to `Mutex::lock` by itself
15-
// adjusts the receiver to be `&Mutex`
16-
&& let (recv_ty, _, Some(Mutability::Mut)) = peel_and_count_ty_refs(cx.typeck_results().expr_ty(recv))
21+
// 2. Check that the receiver is behind one or more `&mut`s -- we want to have mutable
22+
// access, while not outright owning it -- if the latter were the case, then changing
23+
// `.lock()` to `.get_mut()`, could easily result in a conflict with other existing
24+
// immutable borrows.
25+
//
26+
// NOTE: `mutbl` being `Some` is enough to determine that there was at least one layer of
27+
// references, so no need to check `n`
28+
// NOTE: the reason we don't use `expr_ty_adjusted` here is that a call
29+
// to `Mutex::lock` by itself adjusts the receiver to be `&Mutex`
30+
&& let (recv_ty, _, Some(Mutability::Mut)) = peel_and_count_ty_refs(typeck.expr_ty(recv))
1731
&& recv_ty.is_diag_item(cx, sym::Mutex)
1832
{
33+
let deref_mut_trait = cx.tcx.lang_items().deref_mut_trait();
34+
let impls_deref_mut = |ty| deref_mut_trait.is_some_and(|trait_id| implements_trait(cx, ty, trait_id, &[]));
35+
36+
// The mutex was accessed either directly (`mutex.lock()`), or through a series of
37+
// deref/field/indexing projections. Since the final `.lock()` call only requires `&Mutex`,
38+
// those might be immutable, and so we need to manually check whether mutable projections
39+
// would've been possible. For that, we'll repeatedly peel off projections and check each
40+
// intermediary receiver.
41+
let mut r = recv;
42+
loop {
43+
// Check that the (deref) adjustments could be made mutable
44+
if (typeck.expr_adjustments(r))
45+
.iter()
46+
.map_while(|a| match a.kind {
47+
Adjust::Deref(x) => Some((a.target, x)),
48+
_ => None,
49+
})
50+
.try_fold(typeck.expr_ty(r), |ty, (target, deref)| match deref {
51+
// There has been an overloaded deref, most likely an immutable one, as `.lock()` didn't require a
52+
// mutable one -- we need to check if a mutable deref would've been possible, i.e. if
53+
// `ty: DerefMut<Target = target>` (we don't need to check the `Target` part, as `Deref` and
54+
// `DerefMut` impls necessarily have the same one)
55+
DerefAdjustKind::Overloaded(_) => impls_deref_mut(ty).then_some(target),
56+
// There has been a simple deref; if it happened on a `&T`, then we know it will can't be changed to
57+
// provide mutable access
58+
DerefAdjustKind::Builtin => (ty.ref_mutability() != Some(Mutability::Not)).then_some(target),
59+
DerefAdjustKind::Pin => Some(target),
60+
})
61+
.is_none()
62+
{
63+
return;
64+
}
65+
66+
// Peel off one projection
67+
match r.kind {
68+
ExprKind::Unary(UnOp::Deref, base) => {
69+
if impls_deref_mut(typeck.expr_ty_adjusted(base)) {
70+
r = base;
71+
} else {
72+
return;
73+
}
74+
},
75+
ExprKind::Index(..) | ExprKind::Field(..) => {
76+
// We don't want to lint on indexing and field accesses, as both of those would take exclusive
77+
// access to only part of a value -- which would conflict with any immutable reborrow over
78+
// the whole value
79+
return;
80+
},
81+
_ => {
82+
// We arrived at the innermost receiver
83+
if let ExprKind::Path(ref p) = r.kind
84+
&& cx
85+
.qpath_res(p, r.hir_id)
86+
.opt_def_id()
87+
.and_then(|id| cx.tcx.static_mutability(id))
88+
== Some(Mutability::Not)
89+
{
90+
// The mutex is stored in a `static`, and we don't want to suggest making that
91+
// mutable
92+
return;
93+
}
94+
// No more projections to check
95+
break;
96+
},
97+
}
98+
}
99+
19100
span_lint_and_sugg(
20101
cx,
21102
MUT_MUTEX_LOCK,

tests/ui/mut_mutex_lock.fixed

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,47 @@ fn mut_ref_ref_mutex_lock() {
3737
*guard += 1;
3838
}
3939

40+
mod issue16253 {
41+
use std::sync::{Arc, Mutex};
42+
43+
// Do not lint on an owned value since it has a good chance of already having another active borrow.
44+
fn dont_lint_owned(m: Mutex<i32>) {
45+
m.lock();
46+
}
47+
48+
struct Wrapper {
49+
arc: Arc<Mutex<i32>>,
50+
ref_: &'static Mutex<i32>,
51+
ref_mut: &'static mut Mutex<i32>,
52+
owned: Mutex<i32>,
53+
}
54+
55+
// Do not lint, even if the projection chain would theoretically allow mutable access
56+
fn field(w: Wrapper, ref_w: &Wrapper, refmut_w: &mut Wrapper) {
57+
w.arc.lock();
58+
w.ref_.lock();
59+
w.ref_mut.lock();
60+
w.owned.lock();
61+
ref_w.arc.lock();
62+
ref_w.ref_.lock();
63+
ref_w.ref_mut.lock();
64+
ref_w.owned.lock();
65+
refmut_w.arc.lock();
66+
refmut_w.ref_.lock();
67+
refmut_w.ref_mut.lock();
68+
refmut_w.owned.lock();
69+
}
70+
71+
// Do not lint, even if the `.index()` could be turned into `.index_mut()`
72+
fn index(mutexes: &mut [Mutex<u32>]) {
73+
// even though `[0]` is _currently_ an `.index(0)`, it can be turned into `.index_mut()` to
74+
// enable mutable access: `&mut [Mutex] -> &mut Mutex`
75+
mutexes[0].lock().unwrap();
76+
77+
// `exes` is `&[Mutex] = &Mutex`, so we can't get to `&mut Mutex` no matter what
78+
let exes: &_ = mutexes;
79+
exes[0].lock().unwrap();
80+
}
81+
}
82+
4083
fn main() {}

tests/ui/mut_mutex_lock.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,47 @@ fn mut_ref_ref_mutex_lock() {
3737
*guard += 1;
3838
}
3939

40+
mod issue16253 {
41+
use std::sync::{Arc, Mutex};
42+
43+
// Do not lint on an owned value since it has a good chance of already having another active borrow.
44+
fn dont_lint_owned(m: Mutex<i32>) {
45+
m.lock();
46+
}
47+
48+
struct Wrapper {
49+
arc: Arc<Mutex<i32>>,
50+
ref_: &'static Mutex<i32>,
51+
ref_mut: &'static mut Mutex<i32>,
52+
owned: Mutex<i32>,
53+
}
54+
55+
// Do not lint, even if the projection chain would theoretically allow mutable access
56+
fn field(w: Wrapper, ref_w: &Wrapper, refmut_w: &mut Wrapper) {
57+
w.arc.lock();
58+
w.ref_.lock();
59+
w.ref_mut.lock();
60+
w.owned.lock();
61+
ref_w.arc.lock();
62+
ref_w.ref_.lock();
63+
ref_w.ref_mut.lock();
64+
ref_w.owned.lock();
65+
refmut_w.arc.lock();
66+
refmut_w.ref_.lock();
67+
refmut_w.ref_mut.lock();
68+
refmut_w.owned.lock();
69+
}
70+
71+
// Do not lint, even if the `.index()` could be turned into `.index_mut()`
72+
fn index(mutexes: &mut [Mutex<u32>]) {
73+
// even though `[0]` is _currently_ an `.index(0)`, it can be turned into `.index_mut()` to
74+
// enable mutable access: `&mut [Mutex] -> &mut Mutex`
75+
mutexes[0].lock().unwrap();
76+
77+
// `exes` is `&[Mutex] = &Mutex`, so we can't get to `&mut Mutex` no matter what
78+
let exes: &_ = mutexes;
79+
exes[0].lock().unwrap();
80+
}
81+
}
82+
4083
fn main() {}

0 commit comments

Comments
 (0)