1 use clippy_utils::diagnostics::span_lint_and_then;
2 use clippy_utils::higher;
3 use clippy_utils::ty::is_type_diagnostic_item;
4 use clippy_utils::SpanlessEq;
5 use if_chain::if_chain;
6 use rustc_errors::Diagnostic;
7 use rustc_hir::intravisit::{self as visit, Visitor};
8 use rustc_hir::{Expr, ExprKind};
9 use rustc_lint::{LateContext, LateLintPass};
10 use rustc_session::{declare_lint_pass, declare_tool_lint};
13 declare_clippy_lint! {
15 /// Checks for `Mutex::lock` calls in `if let` expression
16 /// with lock calls in any of the else blocks.
18 /// ### Why is this bad?
19 /// The Mutex lock remains held for the whole
20 /// `if let ... else` block and deadlocks.
24 /// if let Ok(thing) = mutex.lock() {
32 /// let locked = mutex.lock();
33 /// if let Ok(thing) = locked {
36 /// use_locked(locked);
39 #[clippy::version = "1.45.0"]
42 "locking a `Mutex` in an `if let` block can cause deadlocks"
45 declare_lint_pass!(IfLetMutex => [IF_LET_MUTEX]);
47 impl<'tcx> LateLintPass<'tcx> for IfLetMutex {
48 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
49 let mut arm_visit = ArmVisitor { found_mutex: None, cx };
50 let mut op_visit = OppVisitor { found_mutex: None, cx };
51 if let Some(higher::IfLet {
54 if_else: Some(if_else),
56 }) = higher::IfLet::hir(cx, expr)
58 op_visit.visit_expr(let_expr);
59 if let Some(op_mutex) = op_visit.found_mutex {
60 arm_visit.visit_expr(if_then);
61 arm_visit.visit_expr(if_else);
63 if let Some(arm_mutex) = arm_visit.found_mutex_if_same_as(op_mutex) {
64 let diag = |diag: &mut Diagnostic| {
67 "this Mutex will remain locked for the entire `if let`-block...",
71 "... and is tried to lock again here, which will always deadlock.",
73 diag.help("move the lock call outside of the `if let ...` expression");
79 "calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock",
88 /// Checks if `Mutex::lock` is called in the `if let` expr.
89 pub struct OppVisitor<'a, 'tcx> {
90 found_mutex: Option<&'tcx Expr<'tcx>>,
91 cx: &'a LateContext<'tcx>,
94 impl<'tcx> Visitor<'tcx> for OppVisitor<'_, 'tcx> {
95 fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
96 if let Some(mutex) = is_mutex_lock_call(self.cx, expr) {
97 self.found_mutex = Some(mutex);
100 visit::walk_expr(self, expr);
104 /// Checks if `Mutex::lock` is called in any of the branches.
105 pub struct ArmVisitor<'a, 'tcx> {
106 found_mutex: Option<&'tcx Expr<'tcx>>,
107 cx: &'a LateContext<'tcx>,
110 impl<'tcx> Visitor<'tcx> for ArmVisitor<'_, 'tcx> {
111 fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
112 if let Some(mutex) = is_mutex_lock_call(self.cx, expr) {
113 self.found_mutex = Some(mutex);
116 visit::walk_expr(self, expr);
120 impl<'tcx, 'l> ArmVisitor<'tcx, 'l> {
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)
130 fn is_mutex_lock_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
132 if let ExprKind::MethodCall(path, [self_arg, ..], _) = &expr.kind;
133 if path.ident.as_str() == "lock";
134 let ty = cx.typeck_results().expr_ty(self_arg).peel_refs();
135 if is_type_diagnostic_item(cx, ty, sym::Mutex);