Skip to content

Emit if_let_mutex in presence of other mutexes #13174

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 29 additions & 37 deletions clippy_lints/src/if_let_mutex.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::is_type_diagnostic_item;
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 rustc_errors::Diag;
use rustc_hir::{Expr, ExprKind};
Expand Down Expand Up @@ -51,53 +51,45 @@ impl<'tcx> LateLintPass<'tcx> for IfLetMutex {
if_else: Some(if_else),
..
}) = 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>| {
if let Some(mutex) = is_mutex_lock_call(cx, e) {
ControlFlow::Break(mutex)
} else {
ControlFlow::Continue(())
}
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");
};

let op_mutex = for_each_expr_without_closures(let_expr, is_mutex_lock);
if let Some(op_mutex) = op_mutex {
let arm_mutex = for_each_expr_without_closures((if_then, if_else), is_mutex_lock);
if let Some(arm_mutex) = arm_mutex
&& SpanlessEq::new(cx).eq_expr(op_mutex, arm_mutex)
{
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,
);
}
}
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
&& path.ident.as_str() == "lock"
&& let ty = cx.typeck_results().expr_ty(self_arg).peel_refs()
&& 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 {
None
ControlFlow::Continue(())
}
}
9 changes: 9 additions & 0 deletions tests/ui/if_let_mutex.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![warn(clippy::if_let_mutex)]
#![allow(clippy::redundant_pattern_matching)]

use std::ops::Deref;
use std::sync::Mutex;
Expand Down Expand Up @@ -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() {}
24 changes: 20 additions & 4 deletions tests/ui/if_let_mutex.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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() {
| ^ - this Mutex will remain locked for the entire `if let`-block...
Expand All @@ -19,7 +19,7 @@ LL | | };
= 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
--> 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() {
| ^ - this Mutex will remain locked for the entire `if let`-block...
Expand All @@ -37,7 +37,7 @@ LL | | };
= 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
--> tests/ui/if_let_mutex.rs:45:5
--> tests/ui/if_let_mutex.rs:46:5
|
LL | if let Ok(i) = mutex.lock() {
| ^ ----- this Mutex will remain locked for the entire `if let`-block...
Expand All @@ -53,5 +53,21 @@ LL | | };
|
= 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