1use std::cell::RefCell;
2use std::collections::hash_map;
3use std::rc::Rc;
4
5use rustc_data_structures::fx::{FxHashMap, FxHashSet};
6use rustc_data_structures::unord::{UnordMap, UnordSet};
7use rustc_errors::Subdiagnostic;
8use rustc_hir::CRATE_HIR_ID;
9use rustc_hir::def_id::{DefId, LocalDefId};
10use rustc_index::bit_set::MixedBitSet;
11use rustc_index::{IndexSlice, IndexVec};
12use rustc_macros::{LintDiagnostic, Subdiagnostic};
13use rustc_middle::bug;
14use rustc_middle::mir::{
15 self, BasicBlock, Body, ClearCrossCrate, Local, Location, Place, StatementKind, TerminatorKind,
16 dump_mir,
17};
18use rustc_middle::ty::{self, Ty, TyCtxt};
19use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
20use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex};
21use rustc_mir_dataflow::{Analysis, MaybeReachable, ResultsCursor};
22use rustc_session::lint::builtin::TAIL_EXPR_DROP_ORDER;
23use rustc_session::lint::{self};
24use rustc_span::{DUMMY_SP, Span, Symbol};
25use rustc_type_ir::data_structures::IndexMap;
26use smallvec::{SmallVec, smallvec};
27use tracing::{debug, instrument};
28
29fn place_has_common_prefix<'tcx>(left: &Place<'tcx>, right: &Place<'tcx>) -> bool {
30 left.local == right.local
31 && left.projection.iter().zip(right.projection).all(|(left, right)| left == right)
32}
33
34#[derive(Debug, Clone, Copy)]
36enum MovePathIndexAtBlock {
37 Unknown,
39 None,
41 Some(MovePathIndex),
43}
44
45struct DropsReachable<'a, 'mir, 'tcx> {
46 body: &'a Body<'tcx>,
47 place: &'a Place<'tcx>,
48 drop_span: &'a mut Option<Span>,
49 move_data: &'a MoveData<'tcx>,
50 maybe_init: &'a mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>,
51 block_drop_value_info: &'a mut IndexSlice<BasicBlock, MovePathIndexAtBlock>,
52 collected_drops: &'a mut MixedBitSet<MovePathIndex>,
53 visited: FxHashMap<BasicBlock, Rc<RefCell<MixedBitSet<MovePathIndex>>>>,
54}
55
56impl<'a, 'mir, 'tcx> DropsReachable<'a, 'mir, 'tcx> {
57 fn visit(&mut self, block: BasicBlock) {
58 let move_set_size = self.move_data.move_paths.len();
59 let make_new_path_set = || Rc::new(RefCell::new(MixedBitSet::new_empty(move_set_size)));
60
61 let data = &self.body.basic_blocks[block];
62 let Some(terminator) = &data.terminator else { return };
63 let dropped_local_here =
67 Rc::clone(self.visited.entry(block).or_insert_with(make_new_path_set));
68 match self.block_drop_value_info[block] {
71 MovePathIndexAtBlock::Some(dropped) => {
72 dropped_local_here.borrow_mut().insert(dropped);
73 }
74 MovePathIndexAtBlock::Unknown => {
75 if let TerminatorKind::Drop { place, .. } = &terminator.kind
76 && let LookupResult::Exact(idx) | LookupResult::Parent(Some(idx)) =
77 self.move_data.rev_lookup.find(place.as_ref())
78 {
79 self.maybe_init.seek_before_primary_effect(Location {
84 block,
85 statement_index: data.statements.len(),
86 });
87
88 if let MaybeReachable::Reachable(maybe_init) = self.maybe_init.get()
93 && maybe_init.contains(idx)
94 {
95 self.block_drop_value_info[block] = MovePathIndexAtBlock::Some(idx);
98 dropped_local_here.borrow_mut().insert(idx);
99 } else {
100 self.block_drop_value_info[block] = MovePathIndexAtBlock::None;
101 }
102 }
103 }
104 MovePathIndexAtBlock::None => {}
105 }
106
107 for succ in terminator.successors() {
108 let target = &self.body.basic_blocks[succ];
109 if target.is_cleanup {
110 continue;
111 }
112
113 let dropped_local_there = match self.visited.entry(succ) {
116 hash_map::Entry::Occupied(occupied_entry) => {
117 if succ == block
118 || !occupied_entry.get().borrow_mut().union(&*dropped_local_here.borrow())
119 {
120 continue;
123 }
124 Rc::clone(occupied_entry.get())
125 }
126 hash_map::Entry::Vacant(vacant_entry) => Rc::clone(
127 vacant_entry.insert(Rc::new(RefCell::new(dropped_local_here.borrow().clone()))),
128 ),
129 };
130 if let Some(terminator) = &target.terminator
131 && let TerminatorKind::Drop {
132 place: dropped_place,
133 target: _,
134 unwind: _,
135 replace: _,
136 } = &terminator.kind
137 && place_has_common_prefix(dropped_place, self.place)
138 {
139 self.collected_drops.union(&*dropped_local_there.borrow());
142 if self.drop_span.is_none() {
143 *self.drop_span = Some(terminator.source_info.span);
147 }
148 } else {
152 self.visit(succ)
153 }
154 }
155 }
156}
157
158fn true_significant_drop_ty<'tcx>(
163 tcx: TyCtxt<'tcx>,
164 ty: Ty<'tcx>,
165) -> Option<SmallVec<[Ty<'tcx>; 2]>> {
166 if let ty::Adt(def, args) = ty.kind() {
167 let mut did = def.did();
168 let mut name_rev = vec![];
169 loop {
170 let key = tcx.def_key(did);
171
172 match key.disambiguated_data.data {
173 rustc_hir::definitions::DefPathData::CrateRoot => {
174 name_rev.push(tcx.crate_name(did.krate))
175 }
176 rustc_hir::definitions::DefPathData::TypeNs(symbol) => name_rev.push(symbol),
177 _ => return None,
178 }
179 if let Some(parent) = key.parent {
180 did = DefId { krate: did.krate, index: parent };
181 } else {
182 break;
183 }
184 }
185 let name_str: Vec<_> = name_rev.iter().rev().map(|x| x.as_str()).collect();
186 debug!(?name_str);
187 match name_str[..] {
188 ["sym" | "proc_macro2", ..]
190 | ["core" | "std", "task", "LocalWaker" | "Waker"]
191 | ["core" | "std", "task", "wake", "LocalWaker" | "Waker"] => Some(smallvec![]),
192 ["tracing", "instrument", "Instrumented"] | ["bytes", "Bytes"] => Some(smallvec![]),
194 ["hashbrown", "raw", "RawTable" | "RawIntoIter"] => {
195 if let [ty, ..] = &***args
196 && let Some(ty) = ty.as_type()
197 {
198 Some(smallvec![ty])
199 } else {
200 None
201 }
202 }
203 ["hashbrown", "raw", "RawDrain"] => {
204 if let [_, ty, ..] = &***args
205 && let Some(ty) = ty.as_type()
206 {
207 Some(smallvec![ty])
208 } else {
209 None
210 }
211 }
212 _ => None,
213 }
214 } else {
215 None
216 }
217}
218
219#[instrument(level = "debug", skip(tcx, typing_env))]
222fn extract_component_raw<'tcx>(
223 tcx: TyCtxt<'tcx>,
224 typing_env: ty::TypingEnv<'tcx>,
225 ty: Ty<'tcx>,
226 ty_seen: &mut UnordSet<Ty<'tcx>>,
227) -> SmallVec<[Ty<'tcx>; 4]> {
228 let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty);
230
231 let tys = tcx.list_significant_drop_tys(typing_env.as_query_input(ty));
232 debug!(?ty, "components");
233 let mut out_tys = smallvec![];
234 for ty in tys {
235 if let Some(tys) = true_significant_drop_ty(tcx, ty) {
236 for ty in tys {
238 if ty_seen.insert(ty) {
239 out_tys.extend(extract_component_raw(tcx, typing_env, ty, ty_seen));
240 }
241 }
242 } else {
243 if ty_seen.insert(ty) {
244 out_tys.push(ty);
245 }
246 }
247 }
248 out_tys
249}
250
251#[instrument(level = "debug", skip(tcx, typing_env))]
252fn extract_component_with_significant_dtor<'tcx>(
253 tcx: TyCtxt<'tcx>,
254 typing_env: ty::TypingEnv<'tcx>,
255 ty: Ty<'tcx>,
256) -> SmallVec<[Ty<'tcx>; 4]> {
257 let mut tys = extract_component_raw(tcx, typing_env, ty, &mut Default::default());
258 let mut deduplicate = FxHashSet::default();
259 tys.retain(|oty| deduplicate.insert(*oty));
260 tys.into_iter().collect()
261}
262
263#[instrument(level = "debug", skip(tcx))]
267fn ty_dtor_span<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Span> {
268 match ty.kind() {
269 ty::Bool
270 | ty::Char
271 | ty::Int(_)
272 | ty::Uint(_)
273 | ty::Float(_)
274 | ty::Error(_)
275 | ty::Str
276 | ty::Never
277 | ty::RawPtr(_, _)
278 | ty::Ref(_, _, _)
279 | ty::FnPtr(_, _)
280 | ty::Tuple(_)
281 | ty::Dynamic(_, _, _)
282 | ty::Alias(_, _)
283 | ty::Bound(_, _)
284 | ty::Pat(_, _)
285 | ty::Placeholder(_)
286 | ty::Infer(_)
287 | ty::Slice(_)
288 | ty::Array(_, _)
289 | ty::UnsafeBinder(_) => None,
290
291 ty::Adt(adt_def, _) => {
292 let did = adt_def.did();
293 let try_local_did_span = |did: DefId| {
294 if let Some(local) = did.as_local() {
295 tcx.source_span(local)
296 } else {
297 tcx.def_span(did)
298 }
299 };
300 let dtor = if let Some(dtor) = tcx.adt_destructor(did) {
301 dtor.did
302 } else if let Some(dtor) = tcx.adt_async_destructor(did) {
303 dtor.future
304 } else {
305 return Some(try_local_did_span(did));
306 };
307 let def_key = tcx.def_key(dtor);
308 let Some(parent_index) = def_key.parent else { return Some(try_local_did_span(dtor)) };
309 let parent_did = DefId { index: parent_index, krate: dtor.krate };
310 Some(try_local_did_span(parent_did))
311 }
312 ty::Coroutine(did, _)
313 | ty::CoroutineWitness(did, _)
314 | ty::CoroutineClosure(did, _)
315 | ty::Closure(did, _)
316 | ty::FnDef(did, _)
317 | ty::Foreign(did) => Some(tcx.def_span(did)),
318 ty::Param(_) => None,
319 }
320}
321
322fn place_descendent_of_bids<'tcx>(
326 mut idx: MovePathIndex,
327 move_data: &MoveData<'tcx>,
328 bids: &UnordSet<&Place<'tcx>>,
329) -> bool {
330 loop {
331 let path = &move_data.move_paths[idx];
332 if bids.contains(&path.place) {
333 return true;
334 }
335 if let Some(parent) = path.parent {
336 idx = parent;
337 } else {
338 return false;
339 }
340 }
341}
342
343pub(crate) fn run_lint<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &Body<'tcx>) {
345 if matches!(tcx.def_kind(def_id), rustc_hir::def::DefKind::SyntheticCoroutineBody) {
346 return;
348 }
349 if body.span.edition().at_least_rust_2024()
350 || tcx.lints_that_dont_need_to_run(()).contains(&lint::LintId::of(TAIL_EXPR_DROP_ORDER))
351 {
352 return;
353 }
354
355 let typing_env = ty::TypingEnv::non_body_analysis(tcx, def_id);
358
359 let mut bid_per_block = IndexMap::default();
365 let mut bid_places = UnordSet::new();
366
367 let mut ty_dropped_components = UnordMap::default();
368 for (block, data) in body.basic_blocks.iter_enumerated() {
369 for (statement_index, stmt) in data.statements.iter().enumerate() {
370 if let StatementKind::BackwardIncompatibleDropHint { place, reason: _ } = &stmt.kind {
371 let ty = place.ty(body, tcx).ty;
372 if ty_dropped_components
373 .entry(ty)
374 .or_insert_with(|| extract_component_with_significant_dtor(tcx, typing_env, ty))
375 .is_empty()
376 {
377 continue;
378 }
379 bid_per_block
380 .entry(block)
381 .or_insert(vec![])
382 .push((Location { block, statement_index }, &**place));
383 bid_places.insert(&**place);
384 }
385 }
386 }
387 if bid_per_block.is_empty() {
388 return;
389 }
390
391 dump_mir(tcx, false, "lint_tail_expr_drop_order", &0 as _, body, |_, _| Ok(()));
392 let locals_with_user_names = collect_user_names(body);
393 let is_closure_like = tcx.is_closure_like(def_id.to_def_id());
394
395 let move_data = MoveData::gather_moves(body, tcx, |_| true);
399 let maybe_init = MaybeInitializedPlaces::new(tcx, body, &move_data);
400 let mut maybe_init = maybe_init.iterate_to_fixpoint(tcx, body, None).into_results_cursor(body);
401 let mut block_drop_value_info =
402 IndexVec::from_elem_n(MovePathIndexAtBlock::Unknown, body.basic_blocks.len());
403 for (&block, candidates) in &bid_per_block {
404 let mut all_locals_dropped = MixedBitSet::new_empty(move_data.move_paths.len());
407 let mut drop_span = None;
408 for &(_, place) in candidates.iter() {
409 let mut collected_drops = MixedBitSet::new_empty(move_data.move_paths.len());
410 DropsReachable {
417 body,
418 place,
419 drop_span: &mut drop_span,
420 move_data: &move_data,
421 maybe_init: &mut maybe_init,
422 block_drop_value_info: &mut block_drop_value_info,
423 collected_drops: &mut collected_drops,
424 visited: Default::default(),
425 }
426 .visit(block);
427 all_locals_dropped.union(&collected_drops);
433 }
434
435 {
437 let mut to_exclude = MixedBitSet::new_empty(all_locals_dropped.domain_size());
438 for path_idx in all_locals_dropped.iter() {
441 let move_path = &move_data.move_paths[path_idx];
442 let dropped_local = move_path.place.local;
443 if dropped_local == Local::ZERO {
451 debug!(?dropped_local, "skip return value");
452 to_exclude.insert(path_idx);
453 continue;
454 }
455 if is_closure_like && matches!(dropped_local, ty::CAPTURE_STRUCT_LOCAL) {
459 debug!(?dropped_local, "skip closure captures");
460 to_exclude.insert(path_idx);
461 continue;
462 }
463 if place_descendent_of_bids(path_idx, &move_data, &bid_places) {
478 debug!(?dropped_local, "skip descendent of bids");
479 to_exclude.insert(path_idx);
480 continue;
481 }
482 let observer_ty = move_path.place.ty(body, tcx).ty;
483 if ty_dropped_components
485 .entry(observer_ty)
486 .or_insert_with(|| {
487 extract_component_with_significant_dtor(tcx, typing_env, observer_ty)
488 })
489 .is_empty()
490 {
491 debug!(?dropped_local, "skip non-droppy types");
492 to_exclude.insert(path_idx);
493 continue;
494 }
495 }
496 if candidates.iter().all(|&(_, place)| candidates[0].1.local == place.local) {
500 for path_idx in all_locals_dropped.iter() {
501 if move_data.move_paths[path_idx].place.local == candidates[0].1.local {
502 to_exclude.insert(path_idx);
503 }
504 }
505 }
506 all_locals_dropped.subtract(&to_exclude);
507 }
508 if all_locals_dropped.is_empty() {
509 continue;
511 }
512
513 let local_names = assign_observables_names(
516 all_locals_dropped
517 .iter()
518 .map(|path_idx| move_data.move_paths[path_idx].place.local)
519 .chain(candidates.iter().map(|(_, place)| place.local)),
520 &locals_with_user_names,
521 );
522
523 let mut lint_root = None;
524 let mut local_labels = vec![];
525 for &(_, place) in candidates {
527 let linted_local_decl = &body.local_decls[place.local];
528 let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
529 bug!("a name should have been assigned")
530 };
531 let name = name.as_str();
532
533 if lint_root.is_none()
534 && let ClearCrossCrate::Set(data) =
535 &body.source_scopes[linted_local_decl.source_info.scope].local_data
536 {
537 lint_root = Some(data.lint_root);
538 }
539
540 let mut seen_dyn = false;
542 let destructors = ty_dropped_components
543 .get(&linted_local_decl.ty)
544 .unwrap()
545 .iter()
546 .filter_map(|&ty| {
547 if let Some(span) = ty_dtor_span(tcx, ty) {
548 Some(DestructorLabel { span, name, dtor_kind: "concrete" })
549 } else if matches!(ty.kind(), ty::Dynamic(..)) {
550 if seen_dyn {
551 None
552 } else {
553 seen_dyn = true;
554 Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
555 }
556 } else {
557 None
558 }
559 })
560 .collect();
561 local_labels.push(LocalLabel {
562 span: linted_local_decl.source_info.span,
563 destructors,
564 name,
565 is_generated_name,
566 is_dropped_first_edition_2024: true,
567 });
568 }
569
570 for path_idx in all_locals_dropped.iter() {
572 let place = &move_data.move_paths[path_idx].place;
573 let observer_ty = place.ty(body, tcx).ty;
575
576 let observer_local_decl = &body.local_decls[place.local];
577 let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
578 bug!("a name should have been assigned")
579 };
580 let name = name.as_str();
581
582 let mut seen_dyn = false;
583 let destructors = extract_component_with_significant_dtor(tcx, typing_env, observer_ty)
584 .into_iter()
585 .filter_map(|ty| {
586 if let Some(span) = ty_dtor_span(tcx, ty) {
587 Some(DestructorLabel { span, name, dtor_kind: "concrete" })
588 } else if matches!(ty.kind(), ty::Dynamic(..)) {
589 if seen_dyn {
590 None
591 } else {
592 seen_dyn = true;
593 Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
594 }
595 } else {
596 None
597 }
598 })
599 .collect();
600 local_labels.push(LocalLabel {
601 span: observer_local_decl.source_info.span,
602 destructors,
603 name,
604 is_generated_name,
605 is_dropped_first_edition_2024: false,
606 });
607 }
608
609 let span = local_labels[0].span;
610 tcx.emit_node_span_lint(
611 lint::builtin::TAIL_EXPR_DROP_ORDER,
612 lint_root.unwrap_or(CRATE_HIR_ID),
613 span,
614 TailExprDropOrderLint { local_labels, drop_span, _epilogue: () },
615 );
616 }
617}
618
619fn collect_user_names(body: &Body<'_>) -> IndexMap<Local, Symbol> {
621 let mut names = IndexMap::default();
622 for var_debug_info in &body.var_debug_info {
623 if let mir::VarDebugInfoContents::Place(place) = &var_debug_info.value
624 && let Some(local) = place.local_or_deref_local()
625 {
626 names.entry(local).or_insert(var_debug_info.name);
627 }
628 }
629 names
630}
631
632fn assign_observables_names(
634 locals: impl IntoIterator<Item = Local>,
635 user_names: &IndexMap<Local, Symbol>,
636) -> IndexMap<Local, (String, bool)> {
637 let mut names = IndexMap::default();
638 let mut assigned_names = FxHashSet::default();
639 let mut idx = 0u64;
640 let mut fresh_name = || {
641 idx += 1;
642 (format!("#{idx}"), true)
643 };
644 for local in locals {
645 let name = if let Some(name) = user_names.get(&local) {
646 let name = name.as_str();
647 if assigned_names.contains(name) { fresh_name() } else { (name.to_owned(), false) }
648 } else {
649 fresh_name()
650 };
651 assigned_names.insert(name.0.clone());
652 names.insert(local, name);
653 }
654 names
655}
656
657#[derive(LintDiagnostic)]
658#[diag(mir_transform_tail_expr_drop_order)]
659struct TailExprDropOrderLint<'a> {
660 #[subdiagnostic]
661 local_labels: Vec<LocalLabel<'a>>,
662 #[label(mir_transform_drop_location)]
663 drop_span: Option<Span>,
664 #[note(mir_transform_note_epilogue)]
665 _epilogue: (),
666}
667
668struct LocalLabel<'a> {
669 span: Span,
670 name: &'a str,
671 is_generated_name: bool,
672 is_dropped_first_edition_2024: bool,
673 destructors: Vec<DestructorLabel<'a>>,
674}
675
676impl Subdiagnostic for LocalLabel<'_> {
678 fn add_to_diag_with<
679 G: rustc_errors::EmissionGuarantee,
680 F: rustc_errors::SubdiagMessageOp<G>,
681 >(
682 self,
683 diag: &mut rustc_errors::Diag<'_, G>,
684 f: &F,
685 ) {
686 diag.arg("name", self.name);
687 diag.arg("is_generated_name", self.is_generated_name);
688 diag.arg("is_dropped_first_edition_2024", self.is_dropped_first_edition_2024);
689 let msg = f(diag, crate::fluent_generated::mir_transform_tail_expr_local.into());
690 diag.span_label(self.span, msg);
691 for dtor in self.destructors {
692 dtor.add_to_diag_with(diag, f);
693 }
694 let msg = f(diag, crate::fluent_generated::mir_transform_label_local_epilogue);
695 diag.span_label(self.span, msg);
696 }
697}
698
699#[derive(Subdiagnostic)]
700#[note(mir_transform_tail_expr_dtor)]
701struct DestructorLabel<'a> {
702 #[primary_span]
703 span: Span,
704 dtor_kind: &'static str,
705 name: &'a str,
706}