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
This commit is contained in:
commit
834b691a9f
@ -1,7 +1,7 @@
|
|||||||
use clippy_utils::diagnostics::span_lint_and_then;
|
use clippy_utils::diagnostics::span_lint_and_then;
|
||||||
use clippy_utils::ty::is_type_diagnostic_item;
|
use clippy_utils::ty::is_type_diagnostic_item;
|
||||||
use clippy_utils::visitors::for_each_expr_without_closures;
|
use clippy_utils::visitors::for_each_expr_without_closures;
|
||||||
use clippy_utils::{higher, SpanlessEq};
|
use clippy_utils::{eq_expr_value, higher};
|
||||||
use core::ops::ControlFlow;
|
use core::ops::ControlFlow;
|
||||||
use rustc_errors::Diag;
|
use rustc_errors::Diag;
|
||||||
use rustc_hir::{Expr, ExprKind};
|
use rustc_hir::{Expr, ExprKind};
|
||||||
@ -51,53 +51,45 @@ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
|
|||||||
if_else: Some(if_else),
|
if_else: Some(if_else),
|
||||||
..
|
..
|
||||||
}) = higher::IfLet::hir(cx, expr)
|
}) = higher::IfLet::hir(cx, expr)
|
||||||
|
&& let Some(op_mutex) = for_each_expr_without_closures(let_expr, |e| mutex_lock_call(cx, e, None))
|
||||||
|
&& let Some(arm_mutex) =
|
||||||
|
for_each_expr_without_closures((if_then, if_else), |e| mutex_lock_call(cx, e, Some(op_mutex)))
|
||||||
{
|
{
|
||||||
let is_mutex_lock = |e: &'tcx Expr<'tcx>| {
|
let diag = |diag: &mut Diag<'_, ()>| {
|
||||||
if let Some(mutex) = is_mutex_lock_call(cx, e) {
|
diag.span_label(
|
||||||
ControlFlow::Break(mutex)
|
op_mutex.span,
|
||||||
} else {
|
"this Mutex will remain locked for the entire `if let`-block...",
|
||||||
ControlFlow::Continue(())
|
);
|
||||||
}
|
diag.span_label(
|
||||||
|
arm_mutex.span,
|
||||||
|
"... and is tried to lock again here, which will always deadlock.",
|
||||||
|
);
|
||||||
|
diag.help("move the lock call outside of the `if let ...` expression");
|
||||||
};
|
};
|
||||||
|
span_lint_and_then(
|
||||||
let op_mutex = for_each_expr_without_closures(let_expr, is_mutex_lock);
|
cx,
|
||||||
if let Some(op_mutex) = op_mutex {
|
IF_LET_MUTEX,
|
||||||
let arm_mutex = for_each_expr_without_closures((if_then, if_else), is_mutex_lock);
|
expr.span,
|
||||||
if let Some(arm_mutex) = arm_mutex
|
"calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock",
|
||||||
&& SpanlessEq::new(cx).eq_expr(op_mutex, arm_mutex)
|
diag,
|
||||||
{
|
);
|
||||||
let diag = |diag: &mut Diag<'_, ()>| {
|
|
||||||
diag.span_label(
|
|
||||||
op_mutex.span,
|
|
||||||
"this Mutex will remain locked for the entire `if let`-block...",
|
|
||||||
);
|
|
||||||
diag.span_label(
|
|
||||||
arm_mutex.span,
|
|
||||||
"... and is tried to lock again here, which will always deadlock.",
|
|
||||||
);
|
|
||||||
diag.help("move the lock call outside of the `if let ...` expression");
|
|
||||||
};
|
|
||||||
span_lint_and_then(
|
|
||||||
cx,
|
|
||||||
IF_LET_MUTEX,
|
|
||||||
expr.span,
|
|
||||||
"calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock",
|
|
||||||
diag,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_mutex_lock_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
|
fn mutex_lock_call<'tcx>(
|
||||||
|
cx: &LateContext<'tcx>,
|
||||||
|
expr: &'tcx Expr<'_>,
|
||||||
|
op_mutex: Option<&'tcx Expr<'_>>,
|
||||||
|
) -> ControlFlow<&'tcx Expr<'tcx>> {
|
||||||
if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind
|
if let ExprKind::MethodCall(path, self_arg, ..) = &expr.kind
|
||||||
&& path.ident.as_str() == "lock"
|
&& path.ident.as_str() == "lock"
|
||||||
&& let ty = cx.typeck_results().expr_ty(self_arg).peel_refs()
|
&& let ty = cx.typeck_results().expr_ty(self_arg).peel_refs()
|
||||||
&& is_type_diagnostic_item(cx, ty, sym::Mutex)
|
&& is_type_diagnostic_item(cx, ty, sym::Mutex)
|
||||||
|
&& op_mutex.map_or(true, |op| eq_expr_value(cx, self_arg, op))
|
||||||
{
|
{
|
||||||
Some(self_arg)
|
ControlFlow::Break(self_arg)
|
||||||
} else {
|
} else {
|
||||||
None
|
ControlFlow::Continue(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#![warn(clippy::if_let_mutex)]
|
#![warn(clippy::if_let_mutex)]
|
||||||
|
#![allow(clippy::redundant_pattern_matching)]
|
||||||
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
@ -50,4 +51,12 @@ fn mutex_ref(mutex: &Mutex<i32>) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn multiple_mutexes(m1: &Mutex<()>, m2: &Mutex<()>) {
|
||||||
|
if let Ok(_) = m1.lock() {
|
||||||
|
m2.lock();
|
||||||
|
} else {
|
||||||
|
m1.lock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock
|
error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock
|
||||||
--> tests/ui/if_let_mutex.rs:10:5
|
--> tests/ui/if_let_mutex.rs:11:5
|
||||||
|
|
|
|
||||||
LL | if let Err(locked) = m.lock() {
|
LL | if let Err(locked) = m.lock() {
|
||||||
| ^ - this Mutex will remain locked for the entire `if let`-block...
|
| ^ - this Mutex will remain locked for the entire `if let`-block...
|
||||||
@ -19,7 +19,7 @@ LL | | };
|
|||||||
= help: to override `-D warnings` add `#[allow(clippy::if_let_mutex)]`
|
= help: to override `-D warnings` add `#[allow(clippy::if_let_mutex)]`
|
||||||
|
|
||||||
error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock
|
error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock
|
||||||
--> tests/ui/if_let_mutex.rs:23:5
|
--> tests/ui/if_let_mutex.rs:24:5
|
||||||
|
|
|
|
||||||
LL | if let Some(locked) = m.lock().unwrap().deref() {
|
LL | if let Some(locked) = m.lock().unwrap().deref() {
|
||||||
| ^ - this Mutex will remain locked for the entire `if let`-block...
|
| ^ - this Mutex will remain locked for the entire `if let`-block...
|
||||||
@ -37,7 +37,7 @@ LL | | };
|
|||||||
= help: move the lock call outside of the `if let ...` expression
|
= help: move the lock call outside of the `if let ...` expression
|
||||||
|
|
||||||
error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock
|
error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock
|
||||||
--> tests/ui/if_let_mutex.rs:45:5
|
--> tests/ui/if_let_mutex.rs:46:5
|
||||||
|
|
|
|
||||||
LL | if let Ok(i) = mutex.lock() {
|
LL | if let Ok(i) = mutex.lock() {
|
||||||
| ^ ----- this Mutex will remain locked for the entire `if let`-block...
|
| ^ ----- this Mutex will remain locked for the entire `if let`-block...
|
||||||
@ -53,5 +53,21 @@ LL | | };
|
|||||||
|
|
|
|
||||||
= help: move the lock call outside of the `if let ...` expression
|
= help: move the lock call outside of the `if let ...` expression
|
||||||
|
|
||||||
error: aborting due to 3 previous errors
|
error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock
|
||||||
|
--> tests/ui/if_let_mutex.rs:55:5
|
||||||
|
|
|
||||||
|
LL | if let Ok(_) = m1.lock() {
|
||||||
|
| ^ -- this Mutex will remain locked for the entire `if let`-block...
|
||||||
|
| _____|
|
||||||
|
| |
|
||||||
|
LL | | m2.lock();
|
||||||
|
LL | | } else {
|
||||||
|
LL | | m1.lock();
|
||||||
|
| | -- ... and is tried to lock again here, which will always deadlock.
|
||||||
|
LL | | }
|
||||||
|
| |_____^
|
||||||
|
|
|
||||||
|
= help: move the lock call outside of the `if let ...` expression
|
||||||
|
|
||||||
|
error: aborting due to 4 previous errors
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user