Skip to content

Commit 7bca046

Browse files
lint against possible change in drop order from tail expr
1 parent 668b659 commit 7bca046

File tree

6 files changed

+279
-0
lines changed

6 files changed

+279
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5903,6 +5903,7 @@ Released 2018-09-13
59035903
[`suspicious_xor_used_as_pow`]: https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_xor_used_as_pow
59045904
[`swap_ptr_to_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#swap_ptr_to_ref
59055905
[`tabs_in_doc_comments`]: https://rust-lang.github.io/rust-clippy/master/index.html#tabs_in_doc_comments
5906+
[`tail_expr_drop_order`]: https://rust-lang.github.io/rust-clippy/master/index.html#tail_expr_drop_order
59065907
[`temporary_assignment`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_assignment
59075908
[`temporary_cstring_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#temporary_cstring_as_ptr
59085909
[`test_attr_in_doctest`]: https://rust-lang.github.io/rust-clippy/master/index.html#test_attr_in_doctest

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
681681
crate::swap::MANUAL_SWAP_INFO,
682682
crate::swap_ptr_to_ref::SWAP_PTR_TO_REF_INFO,
683683
crate::tabs_in_doc_comments::TABS_IN_DOC_COMMENTS_INFO,
684+
crate::tail_expr_drop_order::TAIL_EXPR_DROP_ORDER_INFO,
684685
crate::temporary_assignment::TEMPORARY_ASSIGNMENT_INFO,
685686
crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO,
686687
crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO,

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ mod suspicious_xor_used_as_pow;
345345
mod swap;
346346
mod swap_ptr_to_ref;
347347
mod tabs_in_doc_comments;
348+
mod tail_expr_drop_order;
348349
mod temporary_assignment;
349350
mod tests_outside_test_module;
350351
mod to_digit_is_some;
@@ -911,6 +912,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
911912
store.register_late_pass(|_| Box::new(set_contains_or_insert::SetContainsOrInsert));
912913
store.register_early_pass(|| Box::new(byte_char_slices::ByteCharSlice));
913914
store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest));
915+
store.register_late_pass(|_| Box::new(tail_expr_drop_order::TailExprDropOrder));
914916
// add lints here, do not remove this comment, it's used in `new_lint`
915917
}
916918

+229
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
use std::mem::swap;
2+
3+
use clippy_utils::diagnostics::span_lint_and_then;
4+
use rustc_ast::UnOp;
5+
use rustc_hir::def::Res;
6+
use rustc_hir::intravisit::{self, Visitor};
7+
use rustc_hir::{Block, Expr, ExprKind, LetStmt, Pat, PatKind, QPath, StmtKind};
8+
use rustc_lint::{LateContext, LateLintPass};
9+
use rustc_session::declare_lint_pass;
10+
use rustc_span::Span;
11+
12+
declare_clippy_lint! {
13+
/// ### What it does
14+
/// Edition 2024 introduces a new rule with drop orders for values generated in tail expressions
15+
/// of blocks.
16+
/// Now the values will be dropped first, before the local variable bindings were dropped.
17+
///
18+
/// This lint looks for those values generated at the tail expression location, that of type
19+
/// with a significant `Drop` implementation, such as locks.
20+
/// In case there are also local variables of type with significant `Drop` implementation as well,
21+
/// this lint warns you of a potential transposition in the drop order.
22+
/// Your discretion on the new drop order introduced by Edition 2024 is required.
23+
///
24+
/// ### Why is this bad?
25+
/// Values of type with significant `Drop` implementation has an ill-specified drop order that
26+
/// come after those of local variables.
27+
/// Edition 2024 moves them, so that they are dropped first before dropping local variables.
28+
///
29+
/// ### Example
30+
/// ```ignore
31+
/// fn edition_2024() -> i32 {
32+
/// let mutex = std::sync::Mutex::new(vec![0]);
33+
/// mutex.lock().unwrap()[0]
34+
/// }
35+
/// ```
36+
/// This lint only points out the issue with `mutex.lock()`, which will be dropped before `mutex` does.
37+
/// No fix will be proposed.
38+
/// However, the most probable fix is to hoist `mutex.lock()` into its own local variable binding.
39+
/// ```no_run
40+
/// fn edition_2024() -> i32 {
41+
/// let mutex = std::sync::Mutex::new(vec![0]);
42+
/// let guard = mutex.lock().unwrap();
43+
/// guard[0]
44+
/// }
45+
/// ```
46+
#[clippy::version = "1.82.0"]
47+
pub TAIL_EXPR_DROP_ORDER,
48+
nursery,
49+
"Detect and warn on significant change in drop order in tail expression location"
50+
}
51+
52+
declare_lint_pass!(TailExprDropOrder => [TAIL_EXPR_DROP_ORDER]);
53+
54+
impl<'tcx> LateLintPass<'tcx> for TailExprDropOrder {
55+
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
56+
LintVisitor {
57+
cx,
58+
locals: <_>::default(),
59+
}
60+
.check_block_inner(block);
61+
}
62+
}
63+
64+
struct LintVisitor<'tcx, 'a> {
65+
cx: &'a LateContext<'tcx>,
66+
locals: Vec<Span>,
67+
}
68+
69+
struct LocalCollector<'tcx, 'a> {
70+
cx: &'a LateContext<'tcx>,
71+
locals: &'a mut Vec<Span>,
72+
}
73+
74+
impl<'tcx, 'a> Visitor<'tcx> for LocalCollector<'tcx, 'a> {
75+
type Result = ();
76+
fn visit_pat(&mut self, pat: &'tcx Pat<'tcx>) {
77+
if let PatKind::Binding(_binding_mode, id, ident, pat) = pat.kind {
78+
let ty = self.cx.typeck_results().node_type(id);
79+
if ty.has_significant_drop(self.cx.tcx, self.cx.param_env) {
80+
self.locals.push(ident.span);
81+
}
82+
if let Some(pat) = pat {
83+
self.visit_pat(pat);
84+
}
85+
} else {
86+
intravisit::walk_pat(self, pat);
87+
}
88+
}
89+
}
90+
91+
impl<'tcx, 'a> Visitor<'tcx> for LintVisitor<'tcx, 'a> {
92+
fn visit_block(&mut self, block: &'tcx Block<'tcx>) {
93+
let mut locals = <_>::default();
94+
swap(&mut locals, &mut self.locals);
95+
self.check_block_inner(block);
96+
swap(&mut locals, &mut self.locals);
97+
}
98+
fn visit_local(&mut self, local: &'tcx LetStmt<'tcx>) {
99+
LocalCollector {
100+
cx: self.cx,
101+
locals: &mut self.locals,
102+
}
103+
.visit_local(local);
104+
}
105+
}
106+
107+
impl<'tcx, 'a> LintVisitor<'tcx, 'a> {
108+
fn check_block_inner(&mut self, block: &Block<'tcx>) {
109+
if !block.span.at_least_rust_2024() {
110+
// We only lint for Edition 2024 onwards
111+
return;
112+
}
113+
let Some(tail_expr) = block.expr else { return };
114+
for stmt in block.stmts {
115+
match stmt.kind {
116+
StmtKind::Let(let_stmt) => self.visit_local(let_stmt),
117+
StmtKind::Item(_) => {},
118+
StmtKind::Expr(e) | StmtKind::Semi(e) => self.visit_expr(e),
119+
}
120+
}
121+
if self.locals.is_empty() {
122+
return;
123+
}
124+
LintTailExpr {
125+
cx: self.cx,
126+
locals: &self.locals,
127+
}
128+
.visit_expr(tail_expr);
129+
}
130+
}
131+
132+
struct LintTailExpr<'tcx, 'a> {
133+
cx: &'a LateContext<'tcx>,
134+
locals: &'a [Span],
135+
}
136+
137+
impl<'tcx, 'a> LintTailExpr<'tcx, 'a> {
138+
fn expr_eventually_point_into_local(mut expr: &Expr<'tcx>) -> bool {
139+
loop {
140+
match expr.kind {
141+
ExprKind::Index(access, _, _) | ExprKind::Field(access, _) => expr = access,
142+
ExprKind::AddrOf(_, _, referee) | ExprKind::Unary(UnOp::Deref, referee) => expr = referee,
143+
ExprKind::Path(_)
144+
if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind
145+
&& let [local, ..] = path.segments
146+
&& let Res::Local(_) = local.res =>
147+
{
148+
return true;
149+
},
150+
_ => return false,
151+
}
152+
}
153+
}
154+
155+
fn expr_generates_nonlocal_droppy_value(&self, expr: &Expr<'tcx>) -> bool {
156+
if Self::expr_eventually_point_into_local(expr) {
157+
return false;
158+
}
159+
self.cx
160+
.typeck_results()
161+
.expr_ty(expr)
162+
.has_significant_drop(self.cx.tcx, self.cx.param_env)
163+
}
164+
}
165+
166+
impl<'tcx, 'a> Visitor<'tcx> for LintTailExpr<'tcx, 'a> {
167+
fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
168+
if self.expr_generates_nonlocal_droppy_value(expr) {
169+
span_lint_and_then(
170+
self.cx,
171+
TAIL_EXPR_DROP_ORDER,
172+
expr.span,
173+
"discretion required on this expression which generates a value with a significant drop implementation",
174+
|diag| {
175+
diag.span_help(self.locals.to_vec(), "one or more locals with a significant drop implementation will observe a visible change in drop order");
176+
},
177+
);
178+
return;
179+
}
180+
match expr.kind {
181+
ExprKind::ConstBlock(_)
182+
| ExprKind::Array(_)
183+
| ExprKind::Break(_, _)
184+
| ExprKind::Continue(_)
185+
| ExprKind::Ret(_)
186+
| ExprKind::Become(_)
187+
| ExprKind::Yield(_, _)
188+
| ExprKind::InlineAsm(_)
189+
| ExprKind::If(_, _, _)
190+
| ExprKind::Loop(_, _, _, _)
191+
| ExprKind::Match(_, _, _)
192+
| ExprKind::Closure(_)
193+
| ExprKind::DropTemps(_)
194+
| ExprKind::OffsetOf(_, _)
195+
| ExprKind::Assign(_, _, _)
196+
| ExprKind::AssignOp(_, _, _)
197+
| ExprKind::Lit(_)
198+
| ExprKind::Err(_) => {},
199+
200+
ExprKind::MethodCall(_, _, _, _)
201+
| ExprKind::Call(_, _)
202+
| ExprKind::Type(_, _)
203+
| ExprKind::Tup(_)
204+
| ExprKind::Binary(_, _, _)
205+
| ExprKind::Unary(_, _)
206+
| ExprKind::Path(_)
207+
| ExprKind::Let(_)
208+
| ExprKind::Cast(_, _)
209+
| ExprKind::Field(_, _)
210+
| ExprKind::Index(_, _, _)
211+
| ExprKind::AddrOf(_, _, _)
212+
| ExprKind::Struct(_, _, _)
213+
| ExprKind::Repeat(_, _) => intravisit::walk_expr(self, expr),
214+
215+
ExprKind::Block(block, _) => LintVisitor {
216+
cx: self.cx,
217+
locals: <_>::default(),
218+
}
219+
.check_block_inner(block),
220+
}
221+
}
222+
fn visit_block(&mut self, block: &'tcx Block<'tcx>) {
223+
LintVisitor {
224+
cx: self.cx,
225+
locals: <_>::default(),
226+
}
227+
.check_block_inner(block);
228+
}
229+
}

tests/ui/tail_expr_drop_order.rs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//@compile-flags: -Z unstable-options
2+
//@edition:2024
3+
4+
#![warn(clippy::tail_expr_drop_order)]
5+
6+
struct LoudDropper;
7+
impl Drop for LoudDropper {
8+
fn drop(&mut self) {
9+
println!("loud drop")
10+
}
11+
}
12+
impl LoudDropper {
13+
fn get(&self) -> i32 {
14+
0
15+
}
16+
}
17+
18+
fn should_lint() -> i32 {
19+
let x = LoudDropper;
20+
// Should lint
21+
x.get() + LoudDropper.get() //~ ERROR: discretion required on this expression which generates a value with a significant drop implementation
22+
}
23+
24+
fn should_not_lint() -> i32 {
25+
let x = LoudDropper;
26+
// Should not lint
27+
x.get()
28+
}
29+
30+
fn main() {}

tests/ui/tail_expr_drop_order.stderr

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error: discretion required on this expression which generates a value with a significant drop implementation
2+
--> tests/ui/tail_expr_drop_order.rs:21:15
3+
|
4+
LL | x.get() + LoudDropper.get()
5+
| ^^^^^^^^^^^
6+
|
7+
help: one or more locals with a significant drop implementation will observe a visible change in drop order
8+
--> tests/ui/tail_expr_drop_order.rs:19:9
9+
|
10+
LL | let x = LoudDropper;
11+
| ^
12+
= note: `-D clippy::tail-expr-drop-order` implied by `-D warnings`
13+
= help: to override `-D warnings` add `#[allow(clippy::tail_expr_drop_order)]`
14+
15+
error: aborting due to 1 previous error
16+

0 commit comments

Comments
 (0)