Skip to content

Commit 07f43a1

Browse files
Rollup merge of #97739 - a2aaron:let_underscore, r=estebank
Uplift the `let_underscore` lints from clippy into rustc. This PR resolves #97241. This PR adds three lints from clippy--`let_underscore_drop`, `let_underscore_lock`, and `let_underscore_must_use`, which are meant to capture likely-incorrect uses of `let _ = ...` bindings (in particular, doing this on a type with a non-trivial `Drop` causes the `Drop` to occur immediately, instead of at the end of the scope. For a type like `MutexGuard`, this effectively releases the lock immediately, which is almost certainly the wrong behavior) In porting the lints from clippy I had to copy over a bunch of utility functions from `clippy_util` that these lints also relied upon. Is that the right approach? Note that I've set the `must_use` and `drop` lints to Allow by default and set `lock` to Deny by default (this matches the same settings that clippy has). In talking with `@estebank` he informed me to do a Crater run (I am not sure what type of Crater run to request here--I think it's just "check only"?) On the linked issue, there's some discussion about using `must_use` and `Drop` together as a heuristic for when to warn--I did not implement this yet. r? `@estebank`
2 parents b10aed0 + 76c90c3 commit 07f43a1

File tree

10 files changed

+250
-0
lines changed

10 files changed

+250
-0
lines changed
+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
use crate::{LateContext, LateLintPass, LintContext};
2+
use rustc_errors::{Applicability, LintDiagnosticBuilder, MultiSpan};
3+
use rustc_hir as hir;
4+
use rustc_middle::ty;
5+
use rustc_span::Symbol;
6+
7+
declare_lint! {
8+
/// The `let_underscore_drop` lint checks for statements which don't bind
9+
/// an expression which has a non-trivial Drop implementation to anything,
10+
/// causing the expression to be dropped immediately instead of at end of
11+
/// scope.
12+
///
13+
/// ### Example
14+
/// ```
15+
/// struct SomeStruct;
16+
/// impl Drop for SomeStruct {
17+
/// fn drop(&mut self) {
18+
/// println!("Dropping SomeStruct");
19+
/// }
20+
/// }
21+
///
22+
/// fn main() {
23+
/// #[warn(let_underscore_drop)]
24+
/// // SomeStuct is dropped immediately instead of at end of scope,
25+
/// // so "Dropping SomeStruct" is printed before "end of main".
26+
/// // The order of prints would be reversed if SomeStruct was bound to
27+
/// // a name (such as "_foo").
28+
/// let _ = SomeStruct;
29+
/// println!("end of main");
30+
/// }
31+
/// ```
32+
///
33+
/// {{produces}}
34+
///
35+
/// ### Explanation
36+
///
37+
/// Statements which assign an expression to an underscore causes the
38+
/// expression to immediately drop instead of extending the expression's
39+
/// lifetime to the end of the scope. This is usually unintended,
40+
/// especially for types like `MutexGuard`, which are typically used to
41+
/// lock a mutex for the duration of an entire scope.
42+
///
43+
/// If you want to extend the expression's lifetime to the end of the scope,
44+
/// assign an underscore-prefixed name (such as `_foo`) to the expression.
45+
/// If you do actually want to drop the expression immediately, then
46+
/// calling `std::mem::drop` on the expression is clearer and helps convey
47+
/// intent.
48+
pub LET_UNDERSCORE_DROP,
49+
Allow,
50+
"non-binding let on a type that implements `Drop`"
51+
}
52+
53+
declare_lint! {
54+
/// The `let_underscore_lock` lint checks for statements which don't bind
55+
/// a mutex to anything, causing the lock to be released immediately instead
56+
/// of at end of scope, which is typically incorrect.
57+
///
58+
/// ### Example
59+
/// ```compile_fail
60+
/// use std::sync::{Arc, Mutex};
61+
/// use std::thread;
62+
/// let data = Arc::new(Mutex::new(0));
63+
///
64+
/// thread::spawn(move || {
65+
/// // The lock is immediately released instead of at the end of the
66+
/// // scope, which is probably not intended.
67+
/// let _ = data.lock().unwrap();
68+
/// println!("doing some work");
69+
/// let mut lock = data.lock().unwrap();
70+
/// *lock += 1;
71+
/// });
72+
/// ```
73+
///
74+
/// {{produces}}
75+
///
76+
/// ### Explanation
77+
///
78+
/// Statements which assign an expression to an underscore causes the
79+
/// expression to immediately drop instead of extending the expression's
80+
/// lifetime to the end of the scope. This is usually unintended,
81+
/// especially for types like `MutexGuard`, which are typically used to
82+
/// lock a mutex for the duration of an entire scope.
83+
///
84+
/// If you want to extend the expression's lifetime to the end of the scope,
85+
/// assign an underscore-prefixed name (such as `_foo`) to the expression.
86+
/// If you do actually want to drop the expression immediately, then
87+
/// calling `std::mem::drop` on the expression is clearer and helps convey
88+
/// intent.
89+
pub LET_UNDERSCORE_LOCK,
90+
Deny,
91+
"non-binding let on a synchronization lock"
92+
}
93+
94+
declare_lint_pass!(LetUnderscore => [LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK]);
95+
96+
const SYNC_GUARD_SYMBOLS: [Symbol; 3] = [
97+
rustc_span::sym::MutexGuard,
98+
rustc_span::sym::RwLockReadGuard,
99+
rustc_span::sym::RwLockWriteGuard,
100+
];
101+
102+
impl<'tcx> LateLintPass<'tcx> for LetUnderscore {
103+
fn check_local(&mut self, cx: &LateContext<'_>, local: &hir::Local<'_>) {
104+
if !matches!(local.pat.kind, hir::PatKind::Wild) {
105+
return;
106+
}
107+
if let Some(init) = local.init {
108+
let init_ty = cx.typeck_results().expr_ty(init);
109+
// If the type has a trivial Drop implementation, then it doesn't
110+
// matter that we drop the value immediately.
111+
if !init_ty.needs_drop(cx.tcx, cx.param_env) {
112+
return;
113+
}
114+
let is_sync_lock = match init_ty.kind() {
115+
ty::Adt(adt, _) => SYNC_GUARD_SYMBOLS
116+
.iter()
117+
.any(|guard_symbol| cx.tcx.is_diagnostic_item(*guard_symbol, adt.did())),
118+
_ => false,
119+
};
120+
121+
if is_sync_lock {
122+
let mut span = MultiSpan::from_spans(vec![local.pat.span, init.span]);
123+
span.push_span_label(
124+
local.pat.span,
125+
"this lock is not assigned to a binding and is immediately dropped".to_string(),
126+
);
127+
span.push_span_label(
128+
init.span,
129+
"this binding will immediately drop the value assigned to it".to_string(),
130+
);
131+
cx.struct_span_lint(LET_UNDERSCORE_LOCK, span, |lint| {
132+
build_and_emit_lint(
133+
lint,
134+
local,
135+
init.span,
136+
"non-binding let on a synchronization lock",
137+
)
138+
})
139+
} else {
140+
cx.struct_span_lint(LET_UNDERSCORE_DROP, local.span, |lint| {
141+
build_and_emit_lint(
142+
lint,
143+
local,
144+
init.span,
145+
"non-binding let on a type that implements `Drop`",
146+
);
147+
})
148+
}
149+
}
150+
}
151+
}
152+
153+
fn build_and_emit_lint(
154+
lint: LintDiagnosticBuilder<'_, ()>,
155+
local: &hir::Local<'_>,
156+
init_span: rustc_span::Span,
157+
msg: &str,
158+
) {
159+
lint.build(msg)
160+
.span_suggestion_verbose(
161+
local.pat.span,
162+
"consider binding to an unused variable to avoid immediately dropping the value",
163+
"_unused",
164+
Applicability::MachineApplicable,
165+
)
166+
.multipart_suggestion(
167+
"consider immediately dropping the value",
168+
vec![
169+
(local.span.until(init_span), "drop(".to_string()),
170+
(init_span.shrink_to_hi(), ")".to_string()),
171+
],
172+
Applicability::MachineApplicable,
173+
)
174+
.emit();
175+
}

compiler/rustc_lint/src/lib.rs

+5
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ mod expect;
5555
pub mod hidden_unicode_codepoints;
5656
mod internal;
5757
mod late;
58+
mod let_underscore;
5859
mod levels;
5960
mod methods;
6061
mod non_ascii_idents;
@@ -86,6 +87,7 @@ use builtin::*;
8687
use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
8788
use hidden_unicode_codepoints::*;
8889
use internal::*;
90+
use let_underscore::*;
8991
use methods::*;
9092
use non_ascii_idents::*;
9193
use non_fmt_panic::NonPanicFmt;
@@ -189,6 +191,7 @@ macro_rules! late_lint_mod_passes {
189191
VariantSizeDifferences: VariantSizeDifferences,
190192
BoxPointers: BoxPointers,
191193
PathStatements: PathStatements,
194+
LetUnderscore: LetUnderscore,
192195
// Depends on referenced function signatures in expressions
193196
UnusedResults: UnusedResults,
194197
NonUpperCaseGlobals: NonUpperCaseGlobals,
@@ -315,6 +318,8 @@ fn register_builtins(store: &mut LintStore, no_interleave_lints: bool) {
315318
REDUNDANT_SEMICOLONS
316319
);
317320

321+
add_lint_group!("let_underscore", LET_UNDERSCORE_DROP, LET_UNDERSCORE_LOCK);
322+
318323
add_lint_group!(
319324
"rust_2018_idioms",
320325
BARE_TRAIT_OBJECTS,

compiler/rustc_span/src/symbol.rs

+3
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ symbols! {
223223
LinkedList,
224224
LintPass,
225225
Mutex,
226+
MutexGuard,
226227
N,
227228
NonZeroI128,
228229
NonZeroI16,
@@ -271,6 +272,8 @@ symbols! {
271272
Rust,
272273
RustcDecodable,
273274
RustcEncodable,
275+
RwLockReadGuard,
276+
RwLockWriteGuard,
274277
Send,
275278
SeqCst,
276279
SessionDiagnostic,

library/std/src/sync/mutex.rs

+1
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
192192
and cause Futures to not implement `Send`"]
193193
#[stable(feature = "rust1", since = "1.0.0")]
194194
#[clippy::has_significant_drop]
195+
#[cfg_attr(not(test), rustc_diagnostic_item = "MutexGuard")]
195196
pub struct MutexGuard<'a, T: ?Sized + 'a> {
196197
lock: &'a Mutex<T>,
197198
poison: poison::Guard,

library/std/src/sync/rwlock.rs

+2
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ unsafe impl<T: ?Sized + Send + Sync> Sync for RwLock<T> {}
101101
and cause Futures to not implement `Send`"]
102102
#[stable(feature = "rust1", since = "1.0.0")]
103103
#[clippy::has_significant_drop]
104+
#[cfg_attr(not(test), rustc_diagnostic_item = "RwLockReadGuard")]
104105
pub struct RwLockReadGuard<'a, T: ?Sized + 'a> {
105106
// NB: we use a pointer instead of `&'a T` to avoid `noalias` violations, because a
106107
// `Ref` argument doesn't hold immutability for its whole scope, only until it drops.
@@ -130,6 +131,7 @@ unsafe impl<T: ?Sized + Sync> Sync for RwLockReadGuard<'_, T> {}
130131
and cause Future's to not implement `Send`"]
131132
#[stable(feature = "rust1", since = "1.0.0")]
132133
#[clippy::has_significant_drop]
134+
#[cfg_attr(not(test), rustc_diagnostic_item = "RwLockWriteGuard")]
133135
pub struct RwLockWriteGuard<'a, T: ?Sized + 'a> {
134136
lock: &'a RwLock<T>,
135137
poison: poison::Guard,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// check-pass
2+
#![warn(let_underscore_drop)]
3+
4+
struct NontrivialDrop;
5+
6+
impl Drop for NontrivialDrop {
7+
fn drop(&mut self) {
8+
println!("Dropping!");
9+
}
10+
}
11+
12+
fn main() {
13+
let _ = NontrivialDrop; //~WARNING non-binding let on a type that implements `Drop`
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
warning: non-binding let on a type that implements `Drop`
2+
--> $DIR/let_underscore_drop.rs:13:5
3+
|
4+
LL | let _ = NontrivialDrop;
5+
| ^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/let_underscore_drop.rs:2:9
9+
|
10+
LL | #![warn(let_underscore_drop)]
11+
| ^^^^^^^^^^^^^^^^^^^
12+
help: consider binding to an unused variable to avoid immediately dropping the value
13+
|
14+
LL | let _unused = NontrivialDrop;
15+
| ~~~~~~~
16+
help: consider immediately dropping the value
17+
|
18+
LL | drop(NontrivialDrop);
19+
| ~~~~~ +
20+
21+
warning: 1 warning emitted
22+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// check-fail
2+
use std::sync::{Arc, Mutex};
3+
4+
fn main() {
5+
let data = Arc::new(Mutex::new(0));
6+
let _ = data.lock().unwrap(); //~ERROR non-binding let on a synchronization lock
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: non-binding let on a synchronization lock
2+
--> $DIR/let_underscore_lock.rs:6:9
3+
|
4+
LL | let _ = data.lock().unwrap();
5+
| ^ ^^^^^^^^^^^^^^^^^^^^ this binding will immediately drop the value assigned to it
6+
| |
7+
| this lock is not assigned to a binding and is immediately dropped
8+
|
9+
= note: `#[deny(let_underscore_lock)]` on by default
10+
help: consider binding to an unused variable to avoid immediately dropping the value
11+
|
12+
LL | let _unused = data.lock().unwrap();
13+
| ~~~~~~~
14+
help: consider immediately dropping the value
15+
|
16+
LL | drop(data.lock().unwrap());
17+
| ~~~~~ +
18+
19+
error: aborting due to previous error
20+

src/tools/lint-docs/src/groups.rs

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::process::Command;
88
/// Descriptions of rustc lint groups.
99
static GROUP_DESCRIPTIONS: &[(&str, &str)] = &[
1010
("unused", "Lints that detect things being declared but not used, or excess syntax"),
11+
("let-underscore", "Lints that detect wildcard let bindings that are likely to be invalid"),
1112
("rustdoc", "Rustdoc-specific lints"),
1213
("rust-2018-idioms", "Lints to nudge you toward idiomatic features of Rust 2018"),
1314
("nonstandard-style", "Violation of standard naming conventions"),

0 commit comments

Comments
 (0)