]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/if_let_mutex.rs
Merge commit '27afd6ade4bb1123a8bf82001629b69d23d62aff' into clippyup
[rust.git] / clippy_lints / src / if_let_mutex.rs
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};
11
12 declare_clippy_lint! {
13     /// ### What it does
14     /// Checks for `Mutex::lock` calls in `if let` expression
15     /// with lock calls in any of the else blocks.
16     ///
17     /// ### Why is this bad?
18     /// The Mutex lock remains held for the whole
19     /// `if let ... else` block and deadlocks.
20     ///
21     /// ### Example
22     /// ```rust,ignore
23     /// if let Ok(thing) = mutex.lock() {
24     ///     do_thing();
25     /// } else {
26     ///     mutex.lock();
27     /// }
28     /// ```
29     /// Should be written
30     /// ```rust,ignore
31     /// let locked = mutex.lock();
32     /// if let Ok(thing) = locked {
33     ///     do_thing(thing);
34     /// } else {
35     ///     use_locked(locked);
36     /// }
37     /// ```
38     pub IF_LET_MUTEX,
39     correctness,
40     "locking a `Mutex` in an `if let` block can cause deadlocks"
41 }
42
43 declare_lint_pass!(IfLetMutex => [IF_LET_MUTEX]);
44
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,
49             found_mutex: None,
50             cx,
51         };
52         let mut op_visit = OppVisitor {
53             mutex_lock_called: false,
54             found_mutex: None,
55             cx,
56         };
57         if let Some(higher::IfLet {
58             let_expr,
59             if_then,
60             if_else: Some(if_else),
61             ..
62         }) = higher::IfLet::hir(cx, expr)
63         {
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);
68
69                 if arm_visit.mutex_lock_called && arm_visit.same_mutex(cx, op_visit.found_mutex.unwrap()) {
70                     span_lint_and_help(
71                         cx,
72                         IF_LET_MUTEX,
73                         expr.span,
74                         "calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock",
75                         None,
76                         "move the lock call outside of the `if let ...` expression",
77                     );
78                 }
79             }
80         }
81     }
82 }
83
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>,
89 }
90
91 impl<'tcx> Visitor<'tcx> for OppVisitor<'_, 'tcx> {
92     type Map = Map<'tcx>;
93
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;
98             return;
99         }
100         visit::walk_expr(self, expr);
101     }
102
103     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
104         NestedVisitorMap::None
105     }
106 }
107
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>,
113 }
114
115 impl<'tcx> Visitor<'tcx> for ArmVisitor<'_, 'tcx> {
116     type Map = Map<'tcx>;
117
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;
122             return;
123         }
124         visit::walk_expr(self, expr);
125     }
126
127     fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
128         NestedVisitorMap::None
129     }
130 }
131
132 impl<'tcx, 'l> ArmVisitor<'tcx, 'l> {
133     fn same_mutex(&self, cx: &LateContext<'_>, op_mutex: &Expr<'_>) -> bool {
134         self.found_mutex
135             .map_or(false, |arm_mutex| SpanlessEq::new(cx).eq_expr(op_mutex, arm_mutex))
136     }
137 }
138
139 fn is_mutex_lock_call<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
140     if_chain! {
141         if let ExprKind::MethodCall(path, _span, [self_arg, ..], _) = &expr.kind;
142         if path.ident.as_str() == "lock";
143         let ty = cx.typeck_results().expr_ty(self_arg);
144         if is_type_diagnostic_item(cx, ty, sym!(mutex_type));
145         then {
146             Some(self_arg)
147         } else {
148             None
149         }
150     }
151 }