Skip to content

Commit ad7587f

Browse files
committed
Add let_underscore_lock lint.
Similar to `let_underscore_drop`, this lint checks for statements similar to `let _ = foo`, where `foo` is a lock guard. These types of let statements are especially problematic because the lock gets released immediately, instead of at the end of the scope. This behavior is almost always the wrong thing.
1 parent 821b32b commit ad7587f

File tree

4 files changed

+91
-3
lines changed

4 files changed

+91
-3
lines changed

compiler/rustc_lint/src/let_underscore.rs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::{LateContext, LateLintPass, LintContext};
22
use rustc_hir as hir;
3+
use rustc_middle::ty::{self, subst::GenericArgKind};
4+
use rustc_span::Symbol;
35

46
declare_lint! {
57
/// The `let_underscore_drop` lint checks for statements which don't bind
@@ -43,7 +45,53 @@ declare_lint! {
4345
"non-binding let on a type that implements `Drop`"
4446
}
4547

46-
declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_DROP]);
48+
declare_lint! {
49+
/// The `let_underscore_lock` lint checks for statements which don't bind
50+
/// a mutex to anything, causing the lock to be released immediately instead
51+
/// of at end of scope, which is typically incorrect.
52+
///
53+
/// ### Example
54+
/// ```rust
55+
/// use std::sync::{Arc, Mutex};
56+
/// use std::thread;
57+
/// let data = Arc::new(Mutex::new(0));
58+
///
59+
/// thread::spawn(move || {
60+
/// // The lock is immediately released instead of at the end of the
61+
/// // scope, which is probably not intended.
62+
/// let _ = data.lock().unwrap();
63+
/// println!("doing some work");
64+
/// let mut lock = data.lock().unwrap();
65+
/// *lock += 1;
66+
/// });
67+
/// ```
68+
/// ### Explanation
69+
///
70+
/// Statements which assign an expression to an underscore causes the
71+
/// expression to immediately drop instead of extending the expression's
72+
/// lifetime to the end of the scope. This is usually unintended,
73+
/// especially for types like `MutexGuard`, which are typically used to
74+
/// lock a mutex for the duration of an entire scope.
75+
///
76+
/// If you want to extend the expression's lifetime to the end of the scope,
77+
/// assign an underscore-prefixed name (such as `_foo`) to the expression.
78+
/// If you do actually want to drop the expression immediately, then
79+
/// calling `std::mem::drop` on the expression is clearer and helps convey
80+
/// intent.
81+
pub LET_UNDERSCORE_LOCK,
82+
Warn,
83+
"non-binding let on a synchronization lock"
84+
}
85+
86+
declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK]);
87+
88+
const SYNC_GUARD_PATHS: [&[&str]; 5] = [
89+
&["std", "sync", "mutex", "MutexGuard"],
90+
&["std", "sync", "rwlock", "RwLockReadGuard"],
91+
&["std", "sync", "rwlock", "RwLockWriteGuard"],
92+
&["parking_lot", "raw_mutex", "RawMutex"],
93+
&["parking_lot", "raw_rwlock", "RawRwLock"],
94+
];
4795

4896
impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
4997
fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) {
@@ -53,7 +101,27 @@ impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
53101
if let Some(init) = local.init {
54102
let init_ty = cx.typeck_results().expr_ty(init);
55103
let needs_drop = init_ty.needs_drop(cx.tcx, cx.param_env);
56-
if needs_drop {
104+
let is_sync_lock = init_ty.walk().any(|inner| match inner.unpack() {
105+
GenericArgKind::Type(inner_ty) => {
106+
SYNC_GUARD_PATHS.iter().any(|guard_path| match inner_ty.kind() {
107+
ty::Adt(adt, _) => {
108+
let ty_path = cx.get_def_path(adt.did());
109+
guard_path.iter().map(|x| Symbol::intern(x)).eq(ty_path.iter().copied())
110+
}
111+
_ => false,
112+
})
113+
}
114+
115+
GenericArgKind::Lifetime(_) | GenericArgKind::Const(_) => false,
116+
});
117+
if is_sync_lock {
118+
cx.struct_span_lint(LET_UNDERSCORE_LOCK, local.span, |lint| {
119+
lint.build("non-binding let on a synchronization lock")
120+
.help("consider binding to an unused variable")
121+
.help("consider explicitly droping with `std::mem::drop`")
122+
.emit();
123+
})
124+
} else if needs_drop {
57125
cx.struct_span_lint(LET_UNDERSCORE_DROP, local.span, |lint| {
58126
lint.build("non-binding let on a type that implements `Drop`")
59127
.help("consider binding to an unused variable")

compiler/rustc_lint/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ fn register_builtins(store: &mut LintStore, no_interleave_lints: bool) {
317317
REDUNDANT_SEMICOLONS
318318
);
319319

320-
add_lint_group!("let_underscore", LET_UNDERSCORE_DROP);
320+
add_lint_group!("let_underscore", LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK);
321321

322322
add_lint_group!(
323323
"rust_2018_idioms",

src/test/ui/let_underscore_lock.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// run-pass
2+
3+
use std::sync::{Arc, Mutex};
4+
5+
fn main() {
6+
let data = Arc::new(Mutex::new(0));
7+
let _ = data.lock().unwrap(); //~WARNING non-binding let on a synchronization lock
8+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
warning: non-binding let on a synchronization lock
2+
--> $DIR/let_underscore_lock.rs:7:5
3+
|
4+
LL | let _ = data.lock().unwrap();
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `#[warn(let_underscore_lock)]` on by default
8+
= help: consider binding to an unused variable
9+
= help: consider explicitly droping with `std::mem::drop`
10+
11+
warning: 1 warning emitted
12+

0 commit comments

Comments
 (0)