Skip to content

Commit ada08e3

Browse files
committed
Check arms for significant drops
1 parent a97fe06 commit ada08e3

File tree

4 files changed

+270
-79
lines changed

4 files changed

+270
-79
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

+105-46
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@ 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};
11-
use rustc_span::Span;
11+
use rustc_span::{Span};
1212

1313
use super::SIGNIFICANT_DROP_IN_SCRUTINEE;
1414

1515
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) {
@@ -25,8 +26,14 @@ pub(super) fn check<'tcx>(
2526
let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None);
2627
diag.span_label(
2728
s,
28-
"temporary lives until here. If mutex is locked within block, deadlocks can occur.",
29+
"original temporary lives until here",
2930
);
31+
if let Some(spans) = has_significant_drop_in_arms(cx, arms) {
32+
for span in spans {
33+
diag.span_label(span, "significant drop in arm here");
34+
}
35+
diag.note("this might lead to deadlocks or other unexpected behavior");
36+
}
3037
});
3138
}
3239
}
@@ -93,14 +100,69 @@ fn has_significant_drop_in_scrutinee<'tcx, 'a>(
93100
})
94101
}
95102

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

106168
#[expect(clippy::enum_variant_names)]
@@ -123,11 +185,11 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
123185
SigDropHelper {
124186
cx,
125187
is_chain_end: true,
126-
seen_types: FxHashSet::default(),
127188
has_significant_drop: false,
128189
current_sig_drop: None,
129190
sig_drop_spans: None,
130191
special_handling_for_binary_op: false,
192+
sig_drop_checker: SigDropChecker::new(cx)
131193
}
132194
}
133195

@@ -168,7 +230,7 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
168230
if self.current_sig_drop.is_some() {
169231
return;
170232
}
171-
let ty = self.get_type(expr);
233+
let ty = self.sig_drop_checker.get_type(expr);
172234
if ty.is_ref() {
173235
// We checked that the type was ref, so builtin_deref will return Some TypeAndMut,
174236
// but let's avoid any chance of an ICE
@@ -192,14 +254,6 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
192254
}
193255
}
194256

195-
fn get_type(&self, ex: &'tcx Expr<'_>) -> Ty<'tcx> {
196-
self.cx.typeck_results().expr_ty(ex)
197-
}
198-
199-
fn has_seen_type(&mut self, ty: Ty<'tcx>) -> bool {
200-
!self.seen_types.insert(ty)
201-
}
202-
203257
fn visit_exprs_for_binary_ops(
204258
&mut self,
205259
left: &'tcx Expr<'_>,
@@ -220,43 +274,12 @@ impl<'a, 'tcx> SigDropHelper<'a, 'tcx> {
220274
self.special_handling_for_binary_op = false;
221275
}
222276

223-
fn has_sig_drop_attr(&mut self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
224-
if let Some(adt) = ty.ty_adt_def() {
225-
if get_attr(cx.sess(), cx.tcx.get_attrs_unchecked(adt.did()), "has_significant_drop").count() > 0 {
226-
return true;
227-
}
228-
}
229277

230-
match ty.kind() {
231-
rustc_middle::ty::Adt(a, b) => {
232-
for f in a.all_fields() {
233-
let ty = f.ty(cx.tcx, b);
234-
if !self.has_seen_type(ty) && self.has_sig_drop_attr(cx, ty) {
235-
return true;
236-
}
237-
}
238-
239-
for generic_arg in b.iter() {
240-
if let GenericArgKind::Type(ty) = generic_arg.unpack() {
241-
if self.has_sig_drop_attr(cx, ty) {
242-
return true;
243-
}
244-
}
245-
}
246-
false
247-
},
248-
rustc_middle::ty::Array(ty, _)
249-
| rustc_middle::ty::RawPtr(TypeAndMut { ty, .. })
250-
| rustc_middle::ty::Ref(_, ty, _)
251-
| rustc_middle::ty::Slice(ty) => self.has_sig_drop_attr(cx, *ty),
252-
_ => false,
253-
}
254-
}
255278
}
256279

257280
impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
258281
fn visit_expr(&mut self, ex: &'tcx Expr<'_>) {
259-
if !self.is_chain_end && self.has_sig_drop_attr(self.cx, self.get_type(ex)) {
282+
if !self.is_chain_end && self.sig_drop_checker.has_sig_drop_attr(self.cx, self.sig_drop_checker.get_type(ex)) {
260283
self.has_significant_drop = true;
261284
return;
262285
}
@@ -334,4 +357,40 @@ impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
334357
self.try_setting_current_suggestion(ex, false);
335358
}
336359
}
360+
361+
}
362+
363+
struct ArmSigDropHelper<'a, 'tcx> {
364+
sig_drop_checker: SigDropChecker<'a, 'tcx>,
365+
found_sig_drop_spans: Option<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: None
373+
}
374+
}
375+
}
376+
377+
fn has_significant_drop_in_arms<'tcx, 'a>(
378+
cx: &'a LateContext<'tcx>,
379+
arms: &'tcx [Arm<'_>],
380+
) -> Option<FxHashSet<Span>> {
381+
let mut helper = ArmSigDropHelper::new(cx);
382+
for arm in arms {
383+
helper.visit_expr(arm.body);
384+
}
385+
helper.found_sig_drop_spans
386+
}
387+
388+
impl<'a, 'tcx> Visitor<'tcx> for ArmSigDropHelper<'a, 'tcx> {
389+
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
390+
if self.sig_drop_checker.has_sig_drop_attr(self.sig_drop_checker.cx, self.sig_drop_checker.get_type(ex)) {
391+
self.found_sig_drop_spans.get_or_insert_with(FxHashSet::default).insert(ex.span);
392+
return;
393+
}
394+
walk_expr(self, ex);
395+
}
337396
}

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)