Skip to content

Commit 0fc95e8

Browse files
committed
Auto merge of rust-lang#9318 - lukaslueg:ifletmutexref, r=xFrednet
Fix if_let_mutex not checking Mutexes behind refs Fixes rust-lang#9193 We can always peel references because we are looking for a method-call, for which autoderef applies. --- changelog: [`if_let_mutex`]: detect calls to `Mutex::lock()` if mutex is behind a ref changelog: [`if_let_mutex`]: Add labels to the two instances of the same Mutex that will deadlock
2 parents 8c9040c + 6de4bdf commit 0fc95e8

File tree

3 files changed

+61
-27
lines changed

3 files changed

+61
-27
lines changed

clippy_lints/src/if_let_mutex.rs

+26-24
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
use clippy_utils::diagnostics::span_lint_and_help;
1+
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::higher;
33
use clippy_utils::ty::is_type_diagnostic_item;
44
use clippy_utils::SpanlessEq;
55
use if_chain::if_chain;
6+
use rustc_errors::Diagnostic;
67
use rustc_hir::intravisit::{self as visit, Visitor};
78
use rustc_hir::{Expr, ExprKind};
89
use rustc_lint::{LateContext, LateLintPass};
@@ -45,16 +46,8 @@ declare_lint_pass!(IfLetMutex => [IF_LET_MUTEX]);
4546

4647
impl<'tcx> LateLintPass<'tcx> for IfLetMutex {
4748
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
48-
let mut arm_visit = ArmVisitor {
49-
mutex_lock_called: false,
50-
found_mutex: None,
51-
cx,
52-
};
53-
let mut op_visit = OppVisitor {
54-
mutex_lock_called: false,
55-
found_mutex: None,
56-
cx,
57-
};
49+
let mut arm_visit = ArmVisitor { found_mutex: None, cx };
50+
let mut op_visit = OppVisitor { found_mutex: None, cx };
5851
if let Some(higher::IfLet {
5952
let_expr,
6053
if_then,
@@ -63,18 +56,28 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex {
6356
}) = higher::IfLet::hir(cx, expr)
6457
{
6558
op_visit.visit_expr(let_expr);
66-
if op_visit.mutex_lock_called {
59+
if let Some(op_mutex) = op_visit.found_mutex {
6760
arm_visit.visit_expr(if_then);
6861
arm_visit.visit_expr(if_else);
6962

70-
if arm_visit.mutex_lock_called && arm_visit.same_mutex(cx, op_visit.found_mutex.unwrap()) {
71-
span_lint_and_help(
63+
if let Some(arm_mutex) = arm_visit.found_mutex_if_same_as(op_mutex) {
64+
let diag = |diag: &mut Diagnostic| {
65+
diag.span_label(
66+
op_mutex.span,
67+
"this Mutex will remain locked for the entire `if let`-block...",
68+
);
69+
diag.span_label(
70+
arm_mutex.span,
71+
"... and is tried to lock again here, which will always deadlock.",
72+
);
73+
diag.help("move the lock call outside of the `if let ...` expression");
74+
};
75+
span_lint_and_then(
7276
cx,
7377
IF_LET_MUTEX,
7478
expr.span,
7579
"calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock",
76-
None,
77-
"move the lock call outside of the `if let ...` expression",
80+
diag,
7881
);
7982
}
8083
}
@@ -84,7 +87,6 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex {
8487

8588
/// Checks if `Mutex::lock` is called in the `if let` expr.
8689
pub struct OppVisitor<'a, 'tcx> {
87-
mutex_lock_called: bool,
8890
found_mutex: Option<&'tcx Expr<'tcx>>,
8991
cx: &'a LateContext<'tcx>,
9092
}
@@ -93,7 +95,6 @@ impl<'tcx> Visitor<'tcx> for OppVisitor<'_, 'tcx> {
9395
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
9496
if let Some(mutex) = is_mutex_lock_call(self.cx, expr) {
9597
self.found_mutex = Some(mutex);
96-
self.mutex_lock_called = true;
9798
return;
9899
}
99100
visit::walk_expr(self, expr);
@@ -102,7 +103,6 @@ impl<'tcx> Visitor<'tcx> for OppVisitor<'_, 'tcx> {
102103

103104
/// Checks if `Mutex::lock` is called in any of the branches.
104105
pub struct ArmVisitor<'a, 'tcx> {
105-
mutex_lock_called: bool,
106106
found_mutex: Option<&'tcx Expr<'tcx>>,
107107
cx: &'a LateContext<'tcx>,
108108
}
@@ -111,25 +111,27 @@ impl<'tcx> Visitor<'tcx> for ArmVisitor<'_, 'tcx> {
111111
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
112112
if let Some(mutex) = is_mutex_lock_call(self.cx, expr) {
113113
self.found_mutex = Some(mutex);
114-
self.mutex_lock_called = true;
115114
return;
116115
}
117116
visit::walk_expr(self, expr);
118117
}
119118
}
120119

121120
impl<'tcx, 'l> ArmVisitor<'tcx, 'l> {
122-
fn same_mutex(&self, cx: &LateContext<'_>, op_mutex: &Expr<'_>) -> bool {
123-
self.found_mutex
124-
.map_or(false, |arm_mutex| SpanlessEq::new(cx).eq_expr(op_mutex, arm_mutex))
121+
fn found_mutex_if_same_as(&self, op_mutex: &Expr<'_>) -> Option<&Expr<'_>> {
122+
self.found_mutex.and_then(|arm_mutex| {
123+
SpanlessEq::new(self.cx)
124+
.eq_expr(op_mutex, arm_mutex)
125+
.then_some(arm_mutex)
126+
})
125127
}
126128
}
127129

128130
fn is_mutex_lock_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
129131
if_chain! {
130132
if let ExprKind::MethodCall(path, [self_arg, ..], _) = &expr.kind;
131133
if path.ident.as_str() == "lock";
132-
let ty = cx.typeck_results().expr_ty(self_arg);
134+
let ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
133135
if is_type_diagnostic_item(cx, ty, sym::Mutex);
134136
then {
135137
Some(self_arg)

tests/ui/if_let_mutex.rs

+8
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,12 @@ fn if_let_different_mutex() {
3939
};
4040
}
4141

42+
fn mutex_ref(mutex: &Mutex<i32>) {
43+
if let Ok(i) = mutex.lock() {
44+
do_stuff(i);
45+
} else {
46+
let _x = mutex.lock();
47+
};
48+
}
49+
4250
fn main() {}

tests/ui/if_let_mutex.stderr

+27-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock
22
--> $DIR/if_let_mutex.rs:10:5
33
|
4-
LL | / if let Err(locked) = m.lock() {
4+
LL | if let Err(locked) = m.lock() {
5+
| ^ - this Mutex will remain locked for the entire `if let`-block...
6+
| _____|
7+
| |
58
LL | | do_stuff(locked);
69
LL | | } else {
710
LL | | let lock = m.lock().unwrap();
11+
| | - ... and is tried to lock again here, which will always deadlock.
812
LL | | do_stuff(lock);
913
LL | | };
1014
| |_____^
@@ -15,15 +19,35 @@ LL | | };
1519
error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock
1620
--> $DIR/if_let_mutex.rs:22:5
1721
|
18-
LL | / if let Some(locked) = m.lock().unwrap().deref() {
22+
LL | if let Some(locked) = m.lock().unwrap().deref() {
23+
| ^ - this Mutex will remain locked for the entire `if let`-block...
24+
| _____|
25+
| |
1926
LL | | do_stuff(locked);
2027
LL | | } else {
2128
LL | | let lock = m.lock().unwrap();
29+
| | - ... and is tried to lock again here, which will always deadlock.
2230
LL | | do_stuff(lock);
2331
LL | | };
2432
| |_____^
2533
|
2634
= help: move the lock call outside of the `if let ...` expression
2735

28-
error: aborting due to 2 previous errors
36+
error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock
37+
--> $DIR/if_let_mutex.rs:43:5
38+
|
39+
LL | if let Ok(i) = mutex.lock() {
40+
| ^ ----- this Mutex will remain locked for the entire `if let`-block...
41+
| _____|
42+
| |
43+
LL | | do_stuff(i);
44+
LL | | } else {
45+
LL | | let _x = mutex.lock();
46+
| | ----- ... and is tried to lock again here, which will always deadlock.
47+
LL | | };
48+
| |_____^
49+
|
50+
= help: move the lock call outside of the `if let ...` expression
51+
52+
error: aborting due to 3 previous errors
2953

0 commit comments

Comments
 (0)