1 use clippy_utils
::diagnostics
::span_lint_and_help
;
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_hir
::intravisit
::{self as visit, NestedVisitorMap, Visitor}
;
7 use rustc_hir
::{Expr, ExprKind}
;
8 use rustc_lint
::{LateContext, LateLintPass}
;
9 use rustc_middle
::hir
::map
::Map
;
10 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
12 declare_clippy_lint
! {
14 /// Checks for `Mutex::lock` calls in `if let` expression
15 /// with lock calls in any of the else blocks.
17 /// ### Why is this bad?
18 /// The Mutex lock remains held for the whole
19 /// `if let ... else` block and deadlocks.
23 /// if let Ok(thing) = mutex.lock() {
31 /// let locked = mutex.lock();
32 /// if let Ok(thing) = locked {
35 /// use_locked(locked);
40 "locking a `Mutex` in an `if let` block can cause deadlocks"
43 declare_lint_pass
!(IfLetMutex
=> [IF_LET_MUTEX
]);
45 impl<'tcx
> LateLintPass
<'tcx
> for IfLetMutex
{
46 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'tcx
>) {
47 let mut arm_visit
= ArmVisitor
{
48 mutex_lock_called
: false,
52 let mut op_visit
= OppVisitor
{
53 mutex_lock_called
: false,
57 if let Some(higher
::IfLet
{
60 if_else
: Some(if_else
),
62 }) = higher
::IfLet
::hir(cx
, expr
)
64 op_visit
.visit_expr(let_expr
);
65 if op_visit
.mutex_lock_called
{
66 arm_visit
.visit_expr(if_then
);
67 arm_visit
.visit_expr(if_else
);
69 if arm_visit
.mutex_lock_called
&& arm_visit
.same_mutex(cx
, op_visit
.found_mutex
.unwrap()) {
74 "calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock",
76 "move the lock call outside of the `if let ...` expression",
84 /// Checks if `Mutex::lock` is called in the `if let` expr.
85 pub struct OppVisitor
<'a
, 'tcx
> {
86 mutex_lock_called
: bool
,
87 found_mutex
: Option
<&'tcx Expr
<'tcx
>>,
88 cx
: &'a LateContext
<'tcx
>,
91 impl<'tcx
> Visitor
<'tcx
> for OppVisitor
<'_
, 'tcx
> {
94 fn visit_expr(&mut self, expr
: &'tcx Expr
<'_
>) {
95 if let Some(mutex
) = is_mutex_lock_call(self.cx
, expr
) {
96 self.found_mutex
= Some(mutex
);
97 self.mutex_lock_called
= true;
100 visit
::walk_expr(self, expr
);
103 fn nested_visit_map(&mut self) -> NestedVisitorMap
<Self::Map
> {
104 NestedVisitorMap
::None
108 /// Checks if `Mutex::lock` is called in any of the branches.
109 pub struct ArmVisitor
<'a
, 'tcx
> {
110 mutex_lock_called
: bool
,
111 found_mutex
: Option
<&'tcx Expr
<'tcx
>>,
112 cx
: &'a LateContext
<'tcx
>,
115 impl<'tcx
> Visitor
<'tcx
> for ArmVisitor
<'_
, 'tcx
> {
116 type Map
= Map
<'tcx
>;
118 fn visit_expr(&mut self, expr
: &'tcx Expr
<'tcx
>) {
119 if let Some(mutex
) = is_mutex_lock_call(self.cx
, expr
) {
120 self.found_mutex
= Some(mutex
);
121 self.mutex_lock_called
= true;
124 visit
::walk_expr(self, expr
);
127 fn nested_visit_map(&mut self) -> NestedVisitorMap
<Self::Map
> {
128 NestedVisitorMap
::None
132 impl<'tcx
, 'l
> ArmVisitor
<'tcx
, 'l
> {
133 fn same_mutex(&self, cx
: &LateContext
<'_
>, op_mutex
: &Expr
<'_
>) -> bool
{
135 .map_or(false, |arm_mutex
| SpanlessEq
::new(cx
).eq_expr(op_mutex
, arm_mutex
))
139 fn is_mutex_lock_call
<'tcx
>(cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) -> Option
<&'tcx Expr
<'tcx
>> {
141 if let ExprKind
::MethodCall(path
, _span
, args
, _
) = &expr
.kind
;
142 if path
.ident
.as_str() == "lock";
143 let ty
= cx
.typeck_results().expr_ty(&args
[0]);
144 if is_type_diagnostic_item(cx
, ty
, sym
!(mutex_type
));