2020-03-17 20:51:43 -05:00
|
|
|
use crate::utils::{match_type, paths, span_lint_and_help};
|
2020-03-05 07:36:19 -06:00
|
|
|
use if_chain::if_chain;
|
2020-03-18 14:20:01 -05:00
|
|
|
use rustc_hir::{Arm, Expr, ExprKind, MatchSource, Stmt, StmtKind};
|
2020-03-05 07:36:19 -06:00
|
|
|
use rustc_lint::{LateContext, LateLintPass};
|
|
|
|
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
|
|
|
|
|
|
|
declare_clippy_lint! {
|
|
|
|
/// **What it does:** Checks for `Mutex::lock` calls in `if let` expression
|
|
|
|
/// with lock calls in any of the else blocks.
|
|
|
|
///
|
|
|
|
/// **Why is this bad?** The Mutex lock remains held for the whole
|
|
|
|
/// `if let ... else` block and deadlocks.
|
|
|
|
///
|
2020-03-18 14:20:01 -05:00
|
|
|
/// **Known problems:** None.
|
2020-03-05 07:36:19 -06:00
|
|
|
///
|
|
|
|
/// **Example:**
|
|
|
|
///
|
2020-03-17 20:51:43 -05:00
|
|
|
/// ```rust,ignore
|
2020-03-05 07:36:19 -06:00
|
|
|
/// if let Ok(thing) = mutex.lock() {
|
|
|
|
/// do_thing();
|
|
|
|
/// } else {
|
|
|
|
/// mutex.lock();
|
|
|
|
/// }
|
|
|
|
/// ```
|
2020-03-17 20:51:43 -05:00
|
|
|
/// Should be written
|
|
|
|
/// ```rust,ignore
|
|
|
|
/// let locked = mutex.lock();
|
|
|
|
/// if let Ok(thing) = locked {
|
|
|
|
/// do_thing(thing);
|
|
|
|
/// } else {
|
|
|
|
/// use_locked(locked);
|
|
|
|
/// }
|
|
|
|
/// ```
|
2020-03-05 07:36:19 -06:00
|
|
|
pub IF_LET_MUTEX,
|
|
|
|
correctness,
|
2020-03-05 17:02:22 -06:00
|
|
|
"locking a `Mutex` in an `if let` block can cause deadlocks"
|
2020-03-05 07:36:19 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
declare_lint_pass!(IfLetMutex => [IF_LET_MUTEX]);
|
|
|
|
|
|
|
|
impl LateLintPass<'_, '_> for IfLetMutex {
|
|
|
|
fn check_expr(&mut self, cx: &LateContext<'_, '_>, ex: &'_ Expr<'_>) {
|
|
|
|
if_chain! {
|
|
|
|
if let ExprKind::Match(ref op, ref arms, MatchSource::IfLetDesugar {
|
|
|
|
contains_else_clause: true,
|
|
|
|
}) = ex.kind; // if let ... {} else {}
|
|
|
|
if let ExprKind::MethodCall(_, _, ref args) = op.kind;
|
|
|
|
let ty = cx.tables.expr_ty(&args[0]);
|
|
|
|
if match_type(cx, ty, &paths::MUTEX); // make sure receiver is Mutex
|
|
|
|
if method_chain_names(op, 10).iter().any(|s| s == "lock"); // and lock is called
|
|
|
|
|
2020-03-18 14:20:01 -05:00
|
|
|
if arms.iter().any(|arm| matching_arm(arm, op, ex, cx));
|
2020-03-05 07:36:19 -06:00
|
|
|
then {
|
2020-03-17 20:51:43 -05:00
|
|
|
span_lint_and_help(
|
2020-03-05 07:36:19 -06:00
|
|
|
cx,
|
|
|
|
IF_LET_MUTEX,
|
|
|
|
ex.span,
|
2020-03-17 20:51:43 -05:00
|
|
|
"calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock",
|
|
|
|
"move the lock call outside of the `if let ...` expression",
|
2020-03-05 07:36:19 -06:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-18 14:20:01 -05:00
|
|
|
fn matching_arm(arm: &Arm<'_>, op: &Expr<'_>, ex: &Expr<'_>, cx: &LateContext<'_, '_>) -> bool {
|
2020-03-18 14:28:57 -05:00
|
|
|
if let ExprKind::Block(ref block, _l) = arm.body.kind {
|
|
|
|
block.stmts.iter().any(|stmt| matching_stmt(stmt, op, ex, cx))
|
|
|
|
} else {
|
|
|
|
false
|
2020-03-18 14:20:01 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn matching_stmt(stmt: &Stmt<'_>, op: &Expr<'_>, ex: &Expr<'_>, cx: &LateContext<'_, '_>) -> bool {
|
|
|
|
match stmt.kind {
|
|
|
|
StmtKind::Local(l) => if_chain! {
|
|
|
|
if let Some(ex) = l.init;
|
|
|
|
if let ExprKind::MethodCall(_, _, _) = op.kind;
|
|
|
|
if method_chain_names(ex, 10).iter().any(|s| s == "lock"); // and lock is called
|
|
|
|
then {
|
|
|
|
match_type_method_chain(cx, ex, 5)
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
StmtKind::Expr(e) => if_chain! {
|
|
|
|
if let ExprKind::MethodCall(_, _, _) = e.kind;
|
|
|
|
if method_chain_names(e, 10).iter().any(|s| s == "lock"); // and lock is called
|
|
|
|
then {
|
|
|
|
match_type_method_chain(cx, ex, 5)
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
StmtKind::Semi(e) => if_chain! {
|
|
|
|
if let ExprKind::MethodCall(_, _, _) = e.kind;
|
|
|
|
if method_chain_names(e, 10).iter().any(|s| s == "lock"); // and lock is called
|
|
|
|
then {
|
|
|
|
match_type_method_chain(cx, ex, 5)
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-17 20:51:43 -05:00
|
|
|
/// Return the names of `max_depth` number of methods called in the chain.
|
2020-03-05 07:36:19 -06:00
|
|
|
fn method_chain_names<'tcx>(expr: &'tcx Expr<'tcx>, max_depth: usize) -> Vec<String> {
|
|
|
|
let mut method_names = Vec::with_capacity(max_depth);
|
|
|
|
let mut current = expr;
|
|
|
|
for _ in 0..max_depth {
|
2020-03-17 20:51:43 -05:00
|
|
|
if let ExprKind::MethodCall(path, _, args) = ¤t.kind {
|
2020-03-05 07:36:19 -06:00
|
|
|
if args.iter().any(|e| e.span.from_expansion()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
method_names.push(path.ident.to_string());
|
|
|
|
current = &args[0];
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
method_names
|
|
|
|
}
|
2020-03-17 20:51:43 -05:00
|
|
|
|
|
|
|
/// Check that lock is called on a `Mutex`.
|
|
|
|
fn match_type_method_chain<'tcx>(cx: &LateContext<'_, '_>, expr: &'tcx Expr<'tcx>, max_depth: usize) -> bool {
|
|
|
|
let mut current = expr;
|
|
|
|
for _ in 0..max_depth {
|
|
|
|
if let ExprKind::MethodCall(_, _, args) = ¤t.kind {
|
|
|
|
let ty = cx.tables.expr_ty(&args[0]);
|
|
|
|
if match_type(cx, ty, &paths::MUTEX) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
current = &args[0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|