2 match_type, method_calls, method_chain_args, paths, snippet, snippet_with_applicability, span_lint_and_sugg,
4 use if_chain::if_chain;
6 use rustc_errors::Applicability;
7 use rustc_hir::{print, Expr, ExprKind, MatchSource, PatKind, QPath, StmtKind};
8 use rustc_lint::{LateContext, LateLintPass};
9 use rustc_session::{declare_lint_pass, declare_tool_lint};
11 declare_clippy_lint! {
12 /// **What it does:** Checks for `Mutex::lock` calls in `if let` expression
13 /// with lock calls in any of the else blocks.
15 /// **Why is this bad?** The Mutex lock remains held for the whole
16 /// `if let ... else` block and deadlocks.
18 /// **Known problems:** None.
23 /// # use std::sync::Mutex;
24 /// let mutex = Mutex::new(10);
25 /// if let Ok(thing) = mutex.lock() {
33 "locking a `Mutex` in an `if let` block can cause deadlock"
36 declare_lint_pass!(IfLetMutex => [IF_LET_MUTEX]);
38 impl LateLintPass<'_, '_> for IfLetMutex {
39 fn check_expr(&mut self, cx: &LateContext<'_, '_>, ex: &'_ Expr<'_>) {
41 if let ExprKind::Match(ref op, ref arms, MatchSource::IfLetDesugar {
42 contains_else_clause: true,
43 }) = ex.kind; // if let ... {} else {}
44 if let ExprKind::MethodCall(_, _, ref args) = op.kind;
45 let ty = cx.tables.expr_ty(&args[0]);
46 if let ty::Adt(_, subst) = ty.kind;
47 if match_type(cx, ty, &paths::MUTEX); // make sure receiver is Mutex
48 if method_chain_names(op, 10).iter().any(|s| s == "lock"); // and lock is called
50 let mut suggestion = String::from(&format!("if let _ = {} {{\n", snippet(cx, op.span, "_")));
52 if arms.iter().any(|arm| if_chain! {
53 if let ExprKind::Block(ref block, _l) = arm.body.kind;
54 if block.stmts.iter().any(|stmt| match stmt.kind {
55 StmtKind::Local(l) => if_chain! {
56 if let Some(ex) = l.init;
57 if let ExprKind::MethodCall(_, _, ref args) = op.kind;
58 if method_chain_names(ex, 10).iter().any(|s| s == "lock"); // and lock is called
60 let ty = cx.tables.expr_ty(&args[0]);
61 // // make sure receiver is Result<MutexGuard<...>>
62 match_type(cx, ty, &paths::RESULT)
64 suggestion.push_str(&format!(" {}\n", snippet(cx, l.span, "_")));
68 StmtKind::Expr(e) => if_chain! {
69 if let ExprKind::MethodCall(_, _, ref args) = e.kind;
70 if method_chain_names(e, 10).iter().any(|s| s == "lock"); // and lock is called
72 let ty = cx.tables.expr_ty(&args[0]);
73 // // make sure receiver is Result<MutexGuard<...>>
74 match_type(cx, ty, &paths::RESULT)
76 suggestion.push_str(&format!(" {}\n", snippet(cx, e.span, "_")));
80 StmtKind::Semi(e) => if_chain! {
81 if let ExprKind::MethodCall(_, _, ref args) = e.kind;
82 if method_chain_names(e, 10).iter().any(|s| s == "lock"); // and lock is called
84 let ty = cx.tables.expr_ty(&args[0]);
85 // // make sure receiver is Result<MutexGuard<...>>
86 match_type(cx, ty, &paths::RESULT)
88 suggestion.push_str(&format!(" {}\n", snippet(cx, e.span, "_")));
92 _ => { suggestion.push_str(&format!(" {}\n", snippet(cx, stmt.span, "_"))); false },
97 suggestion.push_str(&format!("else {}\n", snippet(cx, arm.span, "_")));
102 println!("{}", suggestion);
107 "using a `Mutex` in inner scope of `.lock` call",
109 format!("{:?}", "hello"),
110 Applicability::MachineApplicable,
117 fn method_chain_names<'tcx>(expr: &'tcx Expr<'tcx>, max_depth: usize) -> Vec<String> {
118 let mut method_names = Vec::with_capacity(max_depth);
120 let mut current = expr;
121 for _ in 0..max_depth {
122 if let ExprKind::MethodCall(path, span, args) = ¤t.kind {
123 if args.iter().any(|e| e.span.from_expansion()) {
126 method_names.push(path.ident.to_string());
127 println!("{:?}", method_names);