Skip to content

Commit 90227c1

Browse files
committed
Auto merge of rust-lang#8981 - PrestonFrom:more_details_for_significant_drop_lint, r=flip1995
Add details about how significant drop in match scrutinees can cause deadlocks Adds more details about how a significant drop in a match scrutinee can cause a deadlock and include link to documentation. changelog: Add more details to significant drop lint to explicitly show how temporaries in match scrutinees can cause deadlocks.
2 parents 4995b4e + 7bc4096 commit 90227c1

File tree

4 files changed

+407
-117
lines changed

4 files changed

+407
-117
lines changed

clippy_lints/src/matches/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -963,7 +963,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
963963
return;
964964
}
965965
if matches!(source, MatchSource::Normal | MatchSource::ForLoopDesugar) {
966-
significant_drop_in_scrutinee::check(cx, expr, ex, source);
966+
significant_drop_in_scrutinee::check(cx, expr, ex, arms, source);
967967
}
968968

969969
collapsible_match::check_match(cx, arms);

clippy_lints/src/matches/significant_drop_in_scrutinee.rs

+113-49
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use crate::FxHashSet;
22
use clippy_utils::diagnostics::span_lint_and_then;
3-
use clippy_utils::get_attr;
43
use clippy_utils::source::{indent_of, snippet};
4+
use clippy_utils::{get_attr, is_lint_allowed};
55
use rustc_errors::{Applicability, Diagnostic};
66
use rustc_hir::intravisit::{walk_expr, Visitor};
7-
use rustc_hir::{Expr, ExprKind, MatchSource};
7+
use rustc_hir::{Arm, Expr, ExprKind, MatchSource};
88
use rustc_lint::{LateContext, LintContext};
99
use rustc_middle::ty::subst::GenericArgKind;
1010
use rustc_middle::ty::{Ty, TypeAndMut};
@@ -16,12 +16,23 @@ pub(super) fn check<'tcx>(
1616
cx: &LateContext<'tcx>,
1717
expr: &'tcx Expr<'tcx>,
1818
scrutinee: &'tcx Expr<'_>,
19+
arms: &'tcx [Arm<'_>],
1920
source: MatchSource,
2021
) {
22+
if is_lint_allowed(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, expr.hir_id) {
23+
return;
24+
}
25+
2126
if let Some((suggestions, message)) = has_significant_drop_in_scrutinee(cx, scrutinee, source) {
2227
for found in suggestions {
2328
span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| {
2429
set_diagnostic(diag, cx, expr, found);
30+
let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None);
31+
diag.span_label(s, "temporary lives until here");
32+
for span in has_significant_drop_in_arms(cx, arms) {
33+
diag.span_label(span, "another value with significant `Drop` created here");
34+
}
35+
diag.note("this might lead to deadlocks or other unexpected behavior");
2536
});
2637
}
2738
}
@@ -80,22 +91,77 @@ fn has_significant_drop_in_scrutinee<'tcx, 'a>(
8091
let mut helper = SigDropHelper::new(cx);
8192
helper.find_sig_drop(scrutinee).map(|drops| {
8293
let message = if source == MatchSource::Normal {
83-
"temporary with significant drop in match scrutinee"
94+
"temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression"
8495
} else {
85-
"temporary with significant drop in for loop"
96+
"temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression"
8697
};
8798
(drops, message)
8899
})
89100
}
90101

102+
struct SigDropChecker<'a, 'tcx> {
103+
seen_types: FxHashSet<Ty<'tcx>>,
104+
cx: &'a LateContext<'tcx>,
105+
}
106+
107+
impl<'a, 'tcx> SigDropChecker<'a, 'tcx> {
108+
fn new(cx: &'a LateContext<'tcx>) -> SigDropChecker<'a, 'tcx> {
109+
SigDropChecker {
110+
seen_types: FxHashSet::default(),
111+
cx,
112+
}
113+
}
114+
115+
fn get_type(&self, ex: &'tcx Expr<'_>) -> Ty<'tcx> {
116+
self.cx.typeck_results().expr_ty(ex)
117+
}
118+
119+
fn has_seen_type(&mut self, ty: Ty<'tcx>) -> bool {
120+
!self.seen_types.insert(ty)
121+
}
122+
123+
fn has_sig_drop_attr(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
124+
if let Some(adt) = ty.ty_adt_def() {
125+
if get_attr(cx.sess(), cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop").count() > 0 {
126+
return true;
127+
}
128+
}
129+
130+
match ty.kind() {
131+
rustc_middle::ty::Adt(a, b) => {
132+
for f in a.all_fields() {
133+
let ty = f.ty(cx.tcx, b);
134+
if !self.has_seen_type(ty) && self.has_sig_drop_attr(cx, ty) {
135+
return true;
136+
}
137+
}
138+
139+
for generic_arg in b.iter() {
140+
if let GenericArgKind::Type(ty) = generic_arg.unpack() {
141+
if self.has_sig_drop_attr(cx, ty) {
142+
return true;
143+
}
144+
}
145+
}
146+
false
147+
},
148+
rustc_middle::ty::Array(ty, _)
149+
| rustc_middle::ty::RawPtr(TypeAndMut { ty, .. })
150+
| rustc_middle::ty::Ref(_, ty, _)
151+
| rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(cx, *ty),
152+
_ => false,
153+
}
154+
}
155+
}
156+
91157
struct SigDropHelper<'a, 'tcx> {
92158
cx: &'a LateContext<'tcx>,
93159
is_chain_end: bool,
94-
seen_types: FxHashSet<Ty<'tcx>>,
95160
has_significant_drop: bool,
96161
current_sig_drop: Option<FoundSigDrop>,
97162
sig_drop_spans: Option<Vec<FoundSigDrop>>,
98163
special_handling_for_binary_op: bool,
164+
sig_drop_checker: SigDropChecker<'a, 'tcx>,
99165
}
100166

101167
#[expect(clippy::enum_variant_names)]
@@ -118,11 +184,11 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
118184
SigDropHelper {
119185
cx,
120186
is_chain_end: true,
121-
seen_types: FxHashSet::default(),
122187
has_significant_drop: false,
123188
current_sig_drop: None,
124189
sig_drop_spans: None,
125190
special_handling_for_binary_op: false,
191+
sig_drop_checker: SigDropChecker::new(cx),
126192
}
127193
}
128194

@@ -163,7 +229,7 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
163229
if self.current_sig_drop.is_some() {
164230
return;
165231
}
166-
let ty = self.get_type(expr);
232+
let ty = self.sig_drop_checker.get_type(expr);
167233
if ty.is_ref() {
168234
// We checked that the type was ref, so builtin_deref will return Some TypeAndMut,
169235
// but let's avoid any chance of an ICE
@@ -187,14 +253,6 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
187253
}
188254
}
189255

190-
fn get_type(&self, ex: &'tcx Expr<'_>) -> Ty<'tcx> {
191-
self.cx.typeck_results().expr_ty(ex)
192-
}
193-
194-
fn has_seen_type(&mut self, ty: Ty<'tcx>) -> bool {
195-
!self.seen_types.insert(ty)
196-
}
197-
198256
fn visit_exprs_for_binary_ops(
199257
&mut self,
200258
left: &'tcx Expr<'_>,
@@ -214,44 +272,15 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
214272

215273
self.special_handling_for_binary_op = false;
216274
}
217-
218-
fn has_sig_drop_attr(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
219-
if let Some(adt) = ty.ty_adt_def() {
220-
if get_attr(cx.sess(), cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop").count() > 0 {
221-
return true;
222-
}
223-
}
224-
225-
match ty.kind() {
226-
rustc_middle::ty::Adt(a, b) => {
227-
for f in a.all_fields() {
228-
let ty = f.ty(cx.tcx, b);
229-
if !self.has_seen_type(ty) && self.has_sig_drop_attr(cx, ty) {
230-
return true;
231-
}
232-
}
233-
234-
for generic_arg in b.iter() {
235-
if let GenericArgKind::Type(ty) = generic_arg.unpack() {
236-
if self.has_sig_drop_attr(cx, ty) {
237-
return true;
238-
}
239-
}
240-
}
241-
false
242-
},
243-
rustc_middle::ty::Array(ty, _)
244-
| rustc_middle::ty::RawPtr(TypeAndMut { ty, .. })
245-
| rustc_middle::ty::Ref(_, ty, _)
246-
| rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(cx, *ty),
247-
_ => false,
248-
}
249-
}
250275
}
251276

252277
impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
253278
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
254-
if !self.is_chain_end && self.has_sig_drop_attr(self.cx, self.get_type(ex)) {
279+
if !self.is_chain_end
280+
&& self
281+
.sig_drop_checker
282+
.has_sig_drop_attr(self.cx, self.sig_drop_checker.get_type(ex))
283+
{
255284
self.has_significant_drop = true;
256285
return;
257286
}
@@ -330,3 +359,38 @@ impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
330359
}
331360
}
332361
}
362+
363+
struct ArmSigDropHelper<'a, 'tcx> {
364+
sig_drop_checker: SigDropChecker<'a, 'tcx>,
365+
found_sig_drop_spans: FxHashSet<Span>,
366+
}
367+
368+
impl<'a, 'tcx> ArmSigDropHelper<'a, 'tcx> {
369+
fn new(cx: &'a LateContext<'tcx>) -> ArmSigDropHelper<'a, 'tcx> {
370+
ArmSigDropHelper {
371+
sig_drop_checker: SigDropChecker::new(cx),
372+
found_sig_drop_spans: FxHashSet::<Span>::default(),
373+
}
374+
}
375+
}
376+
377+
fn has_significant_drop_in_arms<'tcx, 'a>(cx: &'a LateContext<'tcx>, arms: &'tcx [Arm<'_>]) -> FxHashSet<Span> {
378+
let mut helper = ArmSigDropHelper::new(cx);
379+
for arm in arms {
380+
helper.visit_expr(arm.body);
381+
}
382+
helper.found_sig_drop_spans
383+
}
384+
385+
impl<'a, 'tcx> Visitor<'tcx> for ArmSigDropHelper<'a, 'tcx> {
386+
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
387+
if self
388+
.sig_drop_checker
389+
.has_sig_drop_attr(self.sig_drop_checker.cx, self.sig_drop_checker.get_type(ex))
390+
{
391+
self.found_sig_drop_spans.insert(ex.span);
392+
return;
393+
}
394+
walk_expr(self, ex);
395+
}
396+
}

tests/ui/significant_drop_in_scrutinee.rs

+29
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,19 @@ fn should_trigger_lint_with_mutex_guard_in_match_scrutinee() {
6464
};
6565
}
6666

67+
fn should_not_trigger_lint_with_mutex_guard_in_match_scrutinee_when_lint_allowed() {
68+
let mutex = Mutex::new(State {});
69+
70+
// Lint should not be triggered because it is "allowed" below.
71+
#[allow(clippy::significant_drop_in_scrutinee)]
72+
match mutex.lock().unwrap().foo() {
73+
true => {
74+
mutex.lock().unwrap().bar();
75+
},
76+
false => {},
77+
};
78+
}
79+
6780
fn should_not_trigger_lint_for_insignificant_drop() {
6881
// Should not trigger lint because there are no temporaries whose drops have a significant
6982
// side effect.
@@ -591,4 +604,20 @@ fn should_trigger_lint_for_read_write_lock_for_loop() {
591604
}
592605
}
593606

607+
fn do_bar(mutex: &Mutex<State>) {
608+
mutex.lock().unwrap().bar();
609+
}
610+
611+
fn should_trigger_lint_without_significant_drop_in_arm() {
612+
let mutex = Mutex::new(State {});
613+
614+
// Should trigger lint because the lifetime of the temporary MutexGuard is surprising because it
615+
// is preserved until the end of the match, but there is no clear indication that this is the
616+
// case.
617+
match mutex.lock().unwrap().foo() {
618+
true => do_bar(&mutex),
619+
false => {},
620+
};
621+
}
622+
594623
fn main() {}

0 commit comments

Comments
 (0)