Skip to content

Commit 2476100

Browse files
committed
Add details about significant drop in match scrutinees causing deadlocks
Adds more details about how a significant drop in a match scrutinee can cause a deadlock and include link to documentation. Emits messages indicating temporaries with significant drops in arms of matches and message about possible deadlocks/unexpected behavior. changelog: Add more details to significant drop lint to explicitly show how temporaries in match scrutinees can cause deadlocks/unexpected behavior.
1 parent 93c6f9e commit 2476100

File tree

4 files changed

+363
-92
lines changed

4 files changed

+363
-92
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

+112-48
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use clippy_utils::get_attr;
44
use clippy_utils::source::{indent_of, snippet};
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,21 @@ 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
) {
2122
if let Some((suggestions, message)) = has_significant_drop_in_scrutinee(cx, scrutinee, source) {
2223
for found in suggestions {
2324
span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| {
2425
set_diagnostic(diag, cx, expr, found);
26+
let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None);
27+
diag.span_label(s, "original temporary lives until here");
28+
if let Some(spans) = has_significant_drop_in_arms(cx, arms) {
29+
for span in spans {
30+
diag.span_label(span, "significant drop in arm here");
31+
}
32+
diag.note("this might lead to deadlocks or other unexpected behavior");
33+
}
2534
});
2635
}
2736
}
@@ -80,22 +89,77 @@ fn has_significant_drop_in_scrutinee<'tcx, 'a>(
8089
let mut helper = SigDropHelper::new(cx);
8190
helper.find_sig_drop(scrutinee).map(|drops| {
8291
let message = if source == MatchSource::Normal {
83-
"temporary with significant drop in match scrutinee"
92+
"temporary with drop impl with side effects in match scrutinee lives to end of block"
8493
} else {
85-
"temporary with significant drop in for loop"
94+
"temporary with drop impl with side effects in for loop condition lives to end of block"
8695
};
8796
(drops, message)
8897
})
8998
}
9099

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

101165
#[expect(clippy::enum_variant_names)]
@@ -118,11 +182,11 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
118182
SigDropHelper {
119183
cx,
120184
is_chain_end: true,
121-
seen_types: FxHashSet::default(),
122185
has_significant_drop: false,
123186
current_sig_drop: None,
124187
sig_drop_spans: None,
125188
special_handling_for_binary_op: false,
189+
sig_drop_checker: SigDropChecker::new(cx),
126190
}
127191
}
128192

@@ -163,7 +227,7 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
163227
if self.current_sig_drop.is_some() {
164228
return;
165229
}
166-
let ty = self.get_type(expr);
230+
let ty = self.sig_drop_checker.get_type(expr);
167231
if ty.is_ref() {
168232
// We checked that the type was ref, so builtin_deref will return Some TypeAndMut,
169233
// but let's avoid any chance of an ICE
@@ -187,14 +251,6 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
187251
}
188252
}
189253

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-
198254
fn visit_exprs_for_binary_ops(
199255
&mut self,
200256
left: &'tcx Expr<'_>,
@@ -214,44 +270,15 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
214270

215271
self.special_handling_for_binary_op = false;
216272
}
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-
}
250273
}
251274

252275
impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
253276
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)) {
277+
if !self.is_chain_end
278+
&& self
279+
.sig_drop_checker
280+
.has_sig_drop_attr(self.cx, self.sig_drop_checker.get_type(ex))
281+
{
255282
self.has_significant_drop = true;
256283
return;
257284
}
@@ -330,3 +357,40 @@ impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
330357
}
331358
}
332359
}
360+
361+
struct ArmSigDropHelper<'a, 'tcx> {
362+
sig_drop_checker: SigDropChecker<'a, 'tcx>,
363+
found_sig_drop_spans: Option<FxHashSet<Span>>,
364+
}
365+
366+
impl<'a, 'tcx> ArmSigDropHelper<'a, 'tcx> {
367+
fn new(cx: &'a LateContext<'tcx>) -> ArmSigDropHelper<'a, 'tcx> {
368+
ArmSigDropHelper {
369+
sig_drop_checker: SigDropChecker::new(cx),
370+
found_sig_drop_spans: None,
371+
}
372+
}
373+
}
374+
375+
fn has_significant_drop_in_arms<'tcx, 'a>(cx: &'a LateContext<'tcx>, arms: &'tcx [Arm<'_>]) -> Option<FxHashSet<Span>> {
376+
let mut helper = ArmSigDropHelper::new(cx);
377+
for arm in arms {
378+
helper.visit_expr(arm.body);
379+
}
380+
helper.found_sig_drop_spans
381+
}
382+
383+
impl<'a, 'tcx> Visitor<'tcx> for ArmSigDropHelper<'a, 'tcx> {
384+
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
385+
if self
386+
.sig_drop_checker
387+
.has_sig_drop_attr(self.sig_drop_checker.cx, self.sig_drop_checker.get_type(ex))
388+
{
389+
self.found_sig_drop_spans
390+
.get_or_insert_with(FxHashSet::default)
391+
.insert(ex.span);
392+
return;
393+
}
394+
walk_expr(self, ex);
395+
}
396+
}

tests/ui/significant_drop_in_scrutinee.rs

+16
Original file line numberDiff line numberDiff line change
@@ -591,4 +591,20 @@ fn should_trigger_lint_for_read_write_lock_for_loop() {
591591
}
592592
}
593593

594+
fn do_bar(mutex: &Mutex<State>) {
595+
mutex.lock().unwrap().bar();
596+
}
597+
598+
fn should_trigger_lint_without_significant_drop_in_arm() {
599+
let mutex = Mutex::new(State {});
600+
601+
// Should trigger lint because the lifetime of the temporary MutexGuard is surprising because it
602+
// is preserved until the end of the match, but there is no clear indication that this is the
603+
// case.
604+
match mutex.lock().unwrap().foo() {
605+
true => do_bar(&mutex),
606+
false => {},
607+
};
608+
}
609+
594610
fn main() {}

0 commit comments

Comments
 (0)