Skip to content

Commit 834b691

Browse files
committed
Auto merge of #13174 - y21:if_let_mutex_multi_mutex, r=llogiq
Emit `if_let_mutex` in presence of other mutexes Currently (master, not nightly nor stable) `if_let_mutex` does not emit a warning here: ```rs let m1 = Mutex::new(10); let m2 = Mutex::new(()); if let 100..=200 = *m1.lock().unwrap() { m2.lock(); } else { m1.lock(); } ``` It currently looks for the first call to `.lock()` on *any* mutex receiver inside of the if/else body, and only later (outside of the visitor) checks that the receiver matches the mutex in the scrutinee. That means that in cases like the above, it finds the `m2.lock()` expression, stops the visitor, fails the check that it's the same mutex (`m2` != `m1`) and then does not look for any other `.lock()` calls. So, just make the receiver check also part of the visitor so that we only stop the visitor when we also find the right receiver. The first commit has the actual changes described here. The sceond one just unnests all the `if let`s ---- changelog: none
2 parents 5342092 + 61dcf6c commit 834b691

File tree

3 files changed

+58
-41
lines changed

3 files changed

+58
-41
lines changed

clippy_lints/src/if_let_mutex.rs

Lines changed: 29 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::ty::is_type_diagnostic_item;
33
use clippy_utils::visitors::for_each_expr_without_closures;
4-
use clippy_utils::{higher, SpanlessEq};
4+
use clippy_utils::{eq_expr_value, higher};
55
use core::ops::ControlFlow;
66
use rustc_errors::Diag;
77
use rustc_hir::{Expr, ExprKind};
@@ -51,53 +51,45 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex {
5151
if_else: Some(if_else),
5252
..
5353
}) = higher::IfLet::hir(cx, expr)
54+
&& let Some(op_mutex) = for_each_expr_without_closures(let_expr, |e| mutex_lock_call(cx, e, None))
55+
&& let Some(arm_mutex) =
56+
for_each_expr_without_closures((if_then, if_else), |e| mutex_lock_call(cx, e, Some(op_mutex)))
5457
{
55-
let is_mutex_lock = |e: &'tcx Expr<'tcx>| {
56-
if let Some(mutex) = is_mutex_lock_call(cx, e) {
57-
ControlFlow::Break(mutex)
58-
} else {
59-
ControlFlow::Continue(())
60-
}
58+
let diag = |diag: &mut Diag<'_, ()>| {
59+
diag.span_label(
60+
op_mutex.span,
61+
"this Mutex will remain locked for the entire `if let`-block...",
62+
);
63+
diag.span_label(
64+
arm_mutex.span,
65+
"... and is tried to lock again here, which will always deadlock.",
66+
);
67+
diag.help("move the lock call outside of the `if let ...` expression");
6168
};
62-
63-
let op_mutex = for_each_expr_without_closures(let_expr, is_mutex_lock);
64-
if let Some(op_mutex) = op_mutex {
65-
let arm_mutex = for_each_expr_without_closures((if_then, if_else), is_mutex_lock);
66-
if let Some(arm_mutex) = arm_mutex
67-
&& SpanlessEq::new(cx).eq_expr(op_mutex, arm_mutex)
68-
{
69-
let diag = |diag: &mut Diag<'_, ()>| {
70-
diag.span_label(
71-
op_mutex.span,
72-
"this Mutex will remain locked for the entire `if let`-block...",
73-
);
74-
diag.span_label(
75-
arm_mutex.span,
76-
"... and is tried to lock again here, which will always deadlock.",
77-
);
78-
diag.help("move the lock call outside of the `if let ...` expression");
79-
};
80-
span_lint_and_then(
81-
cx,
82-
IF_LET_MUTEX,
83-
expr.span,
84-
"calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock",
85-
diag,
86-
);
87-
}
88-
}
69+
span_lint_and_then(
70+
cx,
71+
IF_LET_MUTEX,
72+
expr.span,
73+
"calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock",
74+
diag,
75+
);
8976
}
9077
}
9178
}
9279

93-
fn is_mutex_lock_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
80+
fn mutex_lock_call<'tcx>(
81+
cx: &LateContext<'tcx>,
82+
expr: &'tcx Expr<'_>,
83+
op_mutex: Option<&'tcx Expr<'_>>,
84+
) -> ControlFlow<&'tcx Expr<'tcx>> {
9485
if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind
9586
&& path.ident.as_str() == "lock"
9687
&& let ty = cx.typeck_results().expr_ty(self_arg).peel_refs()
9788
&& is_type_diagnostic_item(cx, ty, sym::Mutex)
89+
&& op_mutex.map_or(true, |op| eq_expr_value(cx, self_arg, op))
9890
{
99-
Some(self_arg)
91+
ControlFlow::Break(self_arg)
10092
} else {
101-
None
93+
ControlFlow::Continue(())
10294
}
10395
}

tests/ui/if_let_mutex.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![warn(clippy::if_let_mutex)]
2+
#![allow(clippy::redundant_pattern_matching)]
23

34
use std::ops::Deref;
45
use std::sync::Mutex;
@@ -50,4 +51,12 @@ fn mutex_ref(mutex: &Mutex<i32>) {
5051
};
5152
}
5253

54+
fn multiple_mutexes(m1: &Mutex<()>, m2: &Mutex<()>) {
55+
if let Ok(_) = m1.lock() {
56+
m2.lock();
57+
} else {
58+
m1.lock();
59+
}
60+
}
61+
5362
fn main() {}

tests/ui/if_let_mutex.stderr

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock
2-
--> tests/ui/if_let_mutex.rs:10:5
2+
--> tests/ui/if_let_mutex.rs:11:5
33
|
44
LL | if let Err(locked) = m.lock() {
55
| ^ - this Mutex will remain locked for the entire `if let`-block...
@@ -19,7 +19,7 @@ LL | | };
1919
= help: to override `-D warnings` add `#[allow(clippy::if_let_mutex)]`
2020

2121
error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock
22-
--> tests/ui/if_let_mutex.rs:23:5
22+
--> tests/ui/if_let_mutex.rs:24:5
2323
|
2424
LL | if let Some(locked) = m.lock().unwrap().deref() {
2525
| ^ - this Mutex will remain locked for the entire `if let`-block...
@@ -37,7 +37,7 @@ LL | | };
3737
= help: move the lock call outside of the `if let ...` expression
3838

3939
error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock
40-
--> tests/ui/if_let_mutex.rs:45:5
40+
--> tests/ui/if_let_mutex.rs:46:5
4141
|
4242
LL | if let Ok(i) = mutex.lock() {
4343
| ^ ----- this Mutex will remain locked for the entire `if let`-block...
@@ -53,5 +53,21 @@ LL | | };
5353
|
5454
= help: move the lock call outside of the `if let ...` expression
5555

56-
error: aborting due to 3 previous errors
56+
error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock
57+
--> tests/ui/if_let_mutex.rs:55:5
58+
|
59+
LL | if let Ok(_) = m1.lock() {
60+
| ^ -- this Mutex will remain locked for the entire `if let`-block...
61+
| _____|
62+
| |
63+
LL | | m2.lock();
64+
LL | | } else {
65+
LL | | m1.lock();
66+
| | -- ... and is tried to lock again here, which will always deadlock.
67+
LL | | }
68+
| |_____^
69+
|
70+
= help: move the lock call outside of the `if let ...` expression
71+
72+
error: aborting due to 4 previous errors
5773

0 commit comments

Comments
 (0)