Skip to content

Commit c04c189

Browse files
committed
progress work on suggestion for auto fix
1 parent 23549a8 commit c04c189

File tree

5 files changed

+164
-0
lines changed

5 files changed

+164
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,7 @@ Released 2018-09-13
12541254
[`get_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#get_unwrap
12551255
[`identity_conversion`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_conversion
12561256
[`identity_op`]: https://rust-lang.github.io/rust-clippy/master/index.html#identity_op
1257+
[`if_let_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_mutex
12571258
[`if_let_redundant_pattern_matching`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_redundant_pattern_matching
12581259
[`if_let_some_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_let_some_result
12591260
[`if_not_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#if_not_else

clippy_lints/src/if_let_mutex.rs

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
use crate::utils::{
2+
match_type, method_calls, method_chain_args, paths, snippet, snippet_with_applicability, span_lint_and_sugg,
3+
};
4+
use if_chain::if_chain;
5+
use rustc::ty;
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};
10+
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.
14+
///
15+
/// **Why is this bad?** The Mutex lock remains held for the whole
16+
/// `if let ... else` block and deadlocks.
17+
///
18+
/// **Known problems:** None.
19+
///
20+
/// **Example:**
21+
///
22+
/// ```rust
23+
/// # use std::sync::Mutex;
24+
/// let mutex = Mutex::new(10);
25+
/// if let Ok(thing) = mutex.lock() {
26+
/// do_thing();
27+
/// } else {
28+
/// mutex.lock();
29+
/// }
30+
/// ```
31+
pub IF_LET_MUTEX,
32+
correctness,
33+
"locking a `Mutex` in an `if let` block can cause deadlock"
34+
}
35+
36+
declare_lint_pass!(IfLetMutex => [IF_LET_MUTEX]);
37+
38+
impl LateLintPass<'_, '_> for IfLetMutex {
39+
fn check_expr(&mut self, cx: &LateContext<'_, '_>, ex: &'_ Expr<'_>) {
40+
if_chain! {
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
49+
50+
let mut suggestion = String::from(&format!("if let _ = {} {{\n", snippet(cx, op.span, "_")));
51+
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
59+
then {
60+
let ty = cx.tables.expr_ty(&args[0]);
61+
// // make sure receiver is Result<MutexGuard<...>>
62+
match_type(cx, ty, &paths::RESULT)
63+
} else {
64+
suggestion.push_str(&format!(" {}\n", snippet(cx, l.span, "_")));
65+
false
66+
}
67+
},
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
71+
then {
72+
let ty = cx.tables.expr_ty(&args[0]);
73+
// // make sure receiver is Result<MutexGuard<...>>
74+
match_type(cx, ty, &paths::RESULT)
75+
} else {
76+
suggestion.push_str(&format!(" {}\n", snippet(cx, e.span, "_")));
77+
false
78+
}
79+
},
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
83+
then {
84+
let ty = cx.tables.expr_ty(&args[0]);
85+
// // make sure receiver is Result<MutexGuard<...>>
86+
match_type(cx, ty, &paths::RESULT)
87+
} else {
88+
suggestion.push_str(&format!(" {}\n", snippet(cx, e.span, "_")));
89+
false
90+
}
91+
},
92+
_ => { suggestion.push_str(&format!(" {}\n", snippet(cx, stmt.span, "_"))); false },
93+
});
94+
then {
95+
true
96+
} else {
97+
suggestion.push_str(&format!("else {}\n", snippet(cx, arm.span, "_")));
98+
false
99+
}
100+
});
101+
then {
102+
println!("{}", suggestion);
103+
span_lint_and_sugg(
104+
cx,
105+
IF_LET_MUTEX,
106+
ex.span,
107+
"using a `Mutex` in inner scope of `.lock` call",
108+
"try",
109+
format!("{:?}", "hello"),
110+
Applicability::MachineApplicable,
111+
);
112+
}
113+
}
114+
}
115+
}
116+
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);
119+
120+
let mut current = expr;
121+
for _ in 0..max_depth {
122+
if let ExprKind::MethodCall(path, span, args) = &current.kind {
123+
if args.iter().any(|e| e.span.from_expansion()) {
124+
break;
125+
}
126+
method_names.push(path.ident.to_string());
127+
println!("{:?}", method_names);
128+
current = &args[0];
129+
} else {
130+
break;
131+
}
132+
}
133+
134+
method_names
135+
}

clippy_lints/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ pub mod functions;
218218
pub mod get_last_with_len;
219219
pub mod identity_conversion;
220220
pub mod identity_op;
221+
pub mod if_let_mutex;
221222
pub mod if_let_some_result;
222223
pub mod if_not_else;
223224
pub mod implicit_return;
@@ -559,6 +560,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
559560
&get_last_with_len::GET_LAST_WITH_LEN,
560561
&identity_conversion::IDENTITY_CONVERSION,
561562
&identity_op::IDENTITY_OP,
563+
&if_let_mutex::IF_LET_MUTEX,
562564
&if_let_some_result::IF_LET_SOME_RESULT,
563565
&if_not_else::IF_NOT_ELSE,
564566
&implicit_return::IMPLICIT_RETURN,
@@ -1021,6 +1023,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10211023
store.register_late_pass(|| box wildcard_imports::WildcardImports);
10221024
store.register_early_pass(|| box macro_use::MacroUseImports);
10231025
store.register_late_pass(|| box verbose_file_reads::VerboseFileReads);
1026+
store.register_late_pass(|| box if_let_mutex::IfLetMutex);
10241027

10251028
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
10261029
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@@ -1191,6 +1194,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
11911194
LintId::of(&get_last_with_len::GET_LAST_WITH_LEN),
11921195
LintId::of(&identity_conversion::IDENTITY_CONVERSION),
11931196
LintId::of(&identity_op::IDENTITY_OP),
1197+
LintId::of(&if_let_mutex::IF_LET_MUTEX),
11941198
LintId::of(&if_let_some_result::IF_LET_SOME_RESULT),
11951199
LintId::of(&indexing_slicing::OUT_OF_BOUNDS_INDEXING),
11961200
LintId::of(&infinite_iter::INFINITE_ITER),
@@ -1587,6 +1591,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
15871591
LintId::of(&erasing_op::ERASING_OP),
15881592
LintId::of(&formatting::POSSIBLE_MISSING_COMMA),
15891593
LintId::of(&functions::NOT_UNSAFE_PTR_ARG_DEREF),
1594+
LintId::of(&if_let_mutex::IF_LET_MUTEX),
15901595
LintId::of(&indexing_slicing::OUT_OF_BOUNDS_INDEXING),
15911596
LintId::of(&infinite_iter::INFINITE_ITER),
15921597
LintId::of(&inherent_to_string::INHERENT_TO_STRING_SHADOW_DISPLAY),

src/lintlist/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,13 @@ pub const ALL_LINTS: [Lint; 361] = [
707707
deprecation: None,
708708
module: "identity_op",
709709
},
710+
Lint {
711+
name: "if_let_mutex",
712+
group: "correctness",
713+
desc: "default lint description",
714+
deprecation: None,
715+
module: "if_let_mutex",
716+
},
710717
Lint {
711718
name: "if_let_some_result",
712719
group: "style",

tests/ui/if_let_mutex.rs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#![warn(clippy::if_let_mutex)]
2+
3+
use std::sync::Mutex;
4+
5+
fn do_stuff() {}
6+
fn foo() {
7+
let m = Mutex::new(1u8);
8+
9+
if let Ok(locked) = m.lock() {
10+
do_stuff();
11+
} else {
12+
m.lock().unwrap();
13+
};
14+
}
15+
16+
fn main() {}

0 commit comments

Comments
 (0)