use span_lint_and_help, cargo dev fmt

This commit is contained in:
Devin R 2020-03-17 21:51:43 -04:00
parent 139e2c6227
commit 40bbdffc89
4 changed files with 64 additions and 45 deletions

View file

@ -1,10 +1,6 @@
use crate::utils::{ use crate::utils::{match_type, paths, span_lint_and_help};
match_type, method_calls, method_chain_args, paths, snippet, snippet_with_applicability, span_lint_and_sugg,
};
use if_chain::if_chain; use if_chain::if_chain;
use rustc::ty; use rustc_hir::{Expr, ExprKind, MatchSource, StmtKind};
use rustc_errors::Applicability;
use rustc_hir::{print, Expr, ExprKind, MatchSource, PatKind, QPath, StmtKind};
use rustc_lint::{LateContext, LateLintPass}; use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_session::{declare_lint_pass, declare_tool_lint};
@ -15,19 +11,26 @@ declare_clippy_lint! {
/// **Why is this bad?** The Mutex lock remains held for the whole /// **Why is this bad?** The Mutex lock remains held for the whole
/// `if let ... else` block and deadlocks. /// `if let ... else` block and deadlocks.
/// ///
/// **Known problems:** None. /// **Known problems:** This lint does not generate an auto-applicable suggestion.
/// ///
/// **Example:** /// **Example:**
/// ///
/// ```rust /// ```rust,ignore
/// # use std::sync::Mutex;
/// let mutex = Mutex::new(10);
/// if let Ok(thing) = mutex.lock() { /// if let Ok(thing) = mutex.lock() {
/// do_thing(); /// do_thing();
/// } else { /// } else {
/// mutex.lock(); /// mutex.lock();
/// } /// }
/// ``` /// ```
/// Should be written
/// ```rust,ignore
/// let locked = mutex.lock();
/// if let Ok(thing) = locked {
/// do_thing(thing);
/// } else {
/// use_locked(locked);
/// }
/// ```
pub IF_LET_MUTEX, pub IF_LET_MUTEX,
correctness, correctness,
"locking a `Mutex` in an `if let` block can cause deadlocks" "locking a `Mutex` in an `if let` block can cause deadlocks"
@ -43,93 +46,92 @@ impl LateLintPass<'_, '_> for IfLetMutex {
}) = ex.kind; // if let ... {} else {} }) = ex.kind; // if let ... {} else {}
if let ExprKind::MethodCall(_, _, ref args) = op.kind; if let ExprKind::MethodCall(_, _, ref args) = op.kind;
let ty = cx.tables.expr_ty(&args[0]); let ty = cx.tables.expr_ty(&args[0]);
if let ty::Adt(_, subst) = ty.kind;
if match_type(cx, ty, &paths::MUTEX); // make sure receiver is Mutex 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 if method_chain_names(op, 10).iter().any(|s| s == "lock"); // and lock is called
let mut suggestion = String::from(&format!("if let _ = {} {{\n", snippet(cx, op.span, "_")));
if arms.iter().any(|arm| if_chain! { if arms.iter().any(|arm| if_chain! {
if let ExprKind::Block(ref block, _l) = arm.body.kind; if let ExprKind::Block(ref block, _l) = arm.body.kind;
if block.stmts.iter().any(|stmt| match stmt.kind { if block.stmts.iter().any(|stmt| match stmt.kind {
StmtKind::Local(l) => if_chain! { StmtKind::Local(l) => if_chain! {
if let Some(ex) = l.init; if let Some(ex) = l.init;
if let ExprKind::MethodCall(_, _, ref args) = op.kind; if let ExprKind::MethodCall(_, _, _) = op.kind;
if method_chain_names(ex, 10).iter().any(|s| s == "lock"); // and lock is called if method_chain_names(ex, 10).iter().any(|s| s == "lock"); // and lock is called
then { then {
let ty = cx.tables.expr_ty(&args[0]); match_type_method_chain(cx, ex, 5)
// // make sure receiver is Result<MutexGuard<...>>
match_type(cx, ty, &paths::RESULT)
} else { } else {
suggestion.push_str(&format!(" {}\n", snippet(cx, l.span, "_")));
false false
} }
}, },
StmtKind::Expr(e) => if_chain! { StmtKind::Expr(e) => if_chain! {
if let ExprKind::MethodCall(_, _, ref args) = e.kind; if let ExprKind::MethodCall(_, _, _) = e.kind;
if method_chain_names(e, 10).iter().any(|s| s == "lock"); // and lock is called if method_chain_names(e, 10).iter().any(|s| s == "lock"); // and lock is called
then { then {
let ty = cx.tables.expr_ty(&args[0]); match_type_method_chain(cx, ex, 5)
// // make sure receiver is Result<MutexGuard<...>>
match_type(cx, ty, &paths::RESULT)
} else { } else {
suggestion.push_str(&format!(" {}\n", snippet(cx, e.span, "_")));
false false
} }
}, },
StmtKind::Semi(e) => if_chain! { StmtKind::Semi(e) => if_chain! {
if let ExprKind::MethodCall(_, _, ref args) = e.kind; if let ExprKind::MethodCall(_, _, _) = e.kind;
if method_chain_names(e, 10).iter().any(|s| s == "lock"); // and lock is called if method_chain_names(e, 10).iter().any(|s| s == "lock"); // and lock is called
then { then {
let ty = cx.tables.expr_ty(&args[0]); match_type_method_chain(cx, ex, 5)
// // make sure receiver is Result<MutexGuard<...>>
match_type(cx, ty, &paths::RESULT)
} else { } else {
suggestion.push_str(&format!(" {}\n", snippet(cx, e.span, "_")));
false false
} }
}, },
_ => { suggestion.push_str(&format!(" {}\n", snippet(cx, stmt.span, "_"))); false }, _ => {
false
},
}); });
then { then {
true true
} else { } else {
suggestion.push_str(&format!("else {}\n", snippet(cx, arm.span, "_")));
false false
} }
}); });
then { then {
println!("{}", suggestion); span_lint_and_help(
span_lint_and_sugg(
cx, cx,
IF_LET_MUTEX, IF_LET_MUTEX,
ex.span, ex.span,
"using a `Mutex` in inner scope of `.lock` call", "calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock",
"try", "move the lock call outside of the `if let ...` expression",
format!("{:?}", "hello"),
Applicability::MachineApplicable,
); );
} }
} }
} }
} }
/// Return the names of `max_depth` number of methods called in the chain.
fn method_chain_names<'tcx>(expr: &'tcx Expr<'tcx>, max_depth: usize) -> Vec<String> { 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 method_names = Vec::with_capacity(max_depth);
let mut current = expr; let mut current = expr;
for _ in 0..max_depth { for _ in 0..max_depth {
if let ExprKind::MethodCall(path, span, args) = &current.kind { if let ExprKind::MethodCall(path, _, args) = &current.kind {
if args.iter().any(|e| e.span.from_expansion()) { if args.iter().any(|e| e.span.from_expansion()) {
break; break;
} }
method_names.push(path.ident.to_string()); method_names.push(path.ident.to_string());
println!("{:?}", method_names);
current = &args[0]; current = &args[0];
} else { } else {
break; break;
} }
} }
method_names method_names
} }
/// 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) = &current.kind {
let ty = cx.tables.expr_ty(&args[0]);
if match_type(cx, ty, &paths::MUTEX) {
return true;
}
current = &args[0];
}
}
false
}

View file

@ -734,7 +734,7 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
Lint { Lint {
name: "if_let_mutex", name: "if_let_mutex",
group: "correctness", group: "correctness",
desc: "default lint description", desc: "locking a `Mutex` in an `if let` block can cause deadlocks",
deprecation: None, deprecation: None,
module: "if_let_mutex", module: "if_let_mutex",
}, },

View file

@ -2,14 +2,15 @@
use std::sync::Mutex; use std::sync::Mutex;
fn do_stuff() {} fn do_stuff<T>(_: T) {}
fn foo() { fn foo() {
let m = Mutex::new(1u8); let m = Mutex::new(1u8);
if let Ok(locked) = m.lock() { if let Err(locked) = m.lock() {
do_stuff(); do_stuff(locked);
} else { } else {
m.lock().unwrap(); let lock = m.lock().unwrap();
do_stuff(lock);
}; };
} }

View file

@ -0,0 +1,16 @@
error: calling `Mutex::lock` inside the scope of another `Mutex::lock` causes a deadlock
--> $DIR/if_let_mutex.rs:9:5
|
LL | / if let Err(locked) = m.lock() {
LL | | do_stuff(locked);
LL | | } else {
LL | | let lock = m.lock().unwrap();
LL | | do_stuff(lock);
LL | | };
| |_____^
|
= note: `-D clippy::if-let-mutex` implied by `-D warnings`
= help: move the lock call outside of the `if let ...` expression
error: aborting due to previous error