1use std::cell::RefCell;
2use std::collections::hash_map;
3use std::rc::Rc;
4
5use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
6use rustc_data_structures::unord::{UnordMap, UnordSet};
7use rustc_errors::Subdiagnostic;
8use rustc_hir::CRATE_HIR_ID;
9use rustc_hir::def_id::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::significant_drop_order::{
19 extract_component_with_significant_dtor, ty_dtor_span,
20};
21use rustc_middle::ty::{self, TyCtxt};
22use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
23use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex};
24use rustc_mir_dataflow::{Analysis, MaybeReachable, ResultsCursor};
25use rustc_session::lint::builtin::TAIL_EXPR_DROP_ORDER;
26use rustc_session::lint::{self};
27use rustc_span::{DUMMY_SP, Span, Symbol};
28use tracing::debug;
29
30fn place_has_common_prefix<'tcx>(left: &Place<'tcx>, right: &Place<'tcx>) -> bool {
31 left.local == right.local
32 && left.projection.iter().zip(right.projection).all(|(left, right)| left == right)
33}
34
35#[derive(Debug, Clone, Copy)]
37enum MovePathIndexAtBlock {
38 Unknown,
40 None,
42 Some(MovePathIndex),
44}
45
46struct DropsReachable<'a, 'mir, 'tcx> {
47 body: &'a Body<'tcx>,
48 place: &'a Place<'tcx>,
49 drop_span: &'a mut Option<Span>,
50 move_data: &'a MoveData<'tcx>,
51 maybe_init: &'a mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>,
52 block_drop_value_info: &'a mut IndexSlice<BasicBlock, MovePathIndexAtBlock>,
53 collected_drops: &'a mut MixedBitSet<MovePathIndex>,
54 visited: FxHashMap<BasicBlock, Rc<RefCell<MixedBitSet<MovePathIndex>>>>,
55}
56
57impl<'a, 'mir, 'tcx> DropsReachable<'a, 'mir, 'tcx> {
58 fn visit(&mut self, block: BasicBlock) {
59 let move_set_size = self.move_data.move_paths.len();
60 let make_new_path_set = || Rc::new(RefCell::new(MixedBitSet::new_empty(move_set_size)));
61
62 let data = &self.body.basic_blocks[block];
63 let Some(terminator) = &data.terminator else { return };
64 let dropped_local_here =
68 Rc::clone(self.visited.entry(block).or_insert_with(make_new_path_set));
69 match self.block_drop_value_info[block] {
72 MovePathIndexAtBlock::Some(dropped) => {
73 dropped_local_here.borrow_mut().insert(dropped);
74 }
75 MovePathIndexAtBlock::Unknown => {
76 if let TerminatorKind::Drop { place, .. } = &terminator.kind
77 && let LookupResult::Exact(idx) | LookupResult::Parent(Some(idx)) =
78 self.move_data.rev_lookup.find(place.as_ref())
79 {
80 self.maybe_init.seek_before_primary_effect(Location {
85 block,
86 statement_index: data.statements.len(),
87 });
88
89 if let MaybeReachable::Reachable(maybe_init) = self.maybe_init.get()
94 && maybe_init.contains(idx)
95 {
96 self.block_drop_value_info[block] = MovePathIndexAtBlock::Some(idx);
99 dropped_local_here.borrow_mut().insert(idx);
100 } else {
101 self.block_drop_value_info[block] = MovePathIndexAtBlock::None;
102 }
103 }
104 }
105 MovePathIndexAtBlock::None => {}
106 }
107
108 for succ in terminator.successors() {
109 let target = &self.body.basic_blocks[succ];
110 if target.is_cleanup {
111 continue;
112 }
113
114 let dropped_local_there = match self.visited.entry(succ) {
117 hash_map::Entry::Occupied(occupied_entry) => {
118 if succ == block
119 || !occupied_entry.get().borrow_mut().union(&*dropped_local_here.borrow())
120 {
121 continue;
124 }
125 Rc::clone(occupied_entry.get())
126 }
127 hash_map::Entry::Vacant(vacant_entry) => Rc::clone(
128 vacant_entry.insert(Rc::new(RefCell::new(dropped_local_here.borrow().clone()))),
129 ),
130 };
131 if let Some(terminator) = &target.terminator
132 && let TerminatorKind::Drop {
133 place: dropped_place,
134 target: _,
135 unwind: _,
136 replace: _,
137 } = &terminator.kind
138 && place_has_common_prefix(dropped_place, self.place)
139 {
140 self.collected_drops.union(&*dropped_local_there.borrow());
143 if self.drop_span.is_none() {
144 *self.drop_span = Some(terminator.source_info.span);
148 }
149 } else {
153 self.visit(succ)
154 }
155 }
156 }
157}
158
159fn place_descendent_of_bids<'tcx>(
163 mut idx: MovePathIndex,
164 move_data: &MoveData<'tcx>,
165 bids: &UnordSet<&Place<'tcx>>,
166) -> bool {
167 loop {
168 let path = &move_data.move_paths[idx];
169 if bids.contains(&path.place) {
170 return true;
171 }
172 if let Some(parent) = path.parent {
173 idx = parent;
174 } else {
175 return false;
176 }
177 }
178}
179
180pub(crate) fn run_lint<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &Body<'tcx>) {
182 if matches!(tcx.def_kind(def_id), rustc_hir::def::DefKind::SyntheticCoroutineBody) {
183 return;
185 }
186 if body.span.edition().at_least_rust_2024()
187 || tcx.lints_that_dont_need_to_run(()).contains(&lint::LintId::of(TAIL_EXPR_DROP_ORDER))
188 {
189 return;
190 }
191
192 let typing_env = ty::TypingEnv::non_body_analysis(tcx, def_id);
195
196 let mut bid_per_block = FxIndexMap::default();
202 let mut bid_places = UnordSet::new();
203
204 let mut ty_dropped_components = UnordMap::default();
205 for (block, data) in body.basic_blocks.iter_enumerated() {
206 for (statement_index, stmt) in data.statements.iter().enumerate() {
207 if let StatementKind::BackwardIncompatibleDropHint { place, reason: _ } = &stmt.kind {
208 let ty = place.ty(body, tcx).ty;
209 if ty_dropped_components
210 .entry(ty)
211 .or_insert_with(|| extract_component_with_significant_dtor(tcx, typing_env, ty))
212 .is_empty()
213 {
214 continue;
215 }
216 bid_per_block
217 .entry(block)
218 .or_insert(vec![])
219 .push((Location { block, statement_index }, &**place));
220 bid_places.insert(&**place);
221 }
222 }
223 }
224 if bid_per_block.is_empty() {
225 return;
226 }
227
228 dump_mir(tcx, false, "lint_tail_expr_drop_order", &0 as _, body, |_, _| Ok(()));
229 let locals_with_user_names = collect_user_names(body);
230 let is_closure_like = tcx.is_closure_like(def_id.to_def_id());
231
232 let move_data = MoveData::gather_moves(body, tcx, |_| true);
236 let maybe_init = MaybeInitializedPlaces::new(tcx, body, &move_data);
237 let mut maybe_init = maybe_init.iterate_to_fixpoint(tcx, body, None).into_results_cursor(body);
238 let mut block_drop_value_info =
239 IndexVec::from_elem_n(MovePathIndexAtBlock::Unknown, body.basic_blocks.len());
240 for (&block, candidates) in &bid_per_block {
241 let mut all_locals_dropped = MixedBitSet::new_empty(move_data.move_paths.len());
244 let mut drop_span = None;
245 for &(_, place) in candidates.iter() {
246 let mut collected_drops = MixedBitSet::new_empty(move_data.move_paths.len());
247 DropsReachable {
254 body,
255 place,
256 drop_span: &mut drop_span,
257 move_data: &move_data,
258 maybe_init: &mut maybe_init,
259 block_drop_value_info: &mut block_drop_value_info,
260 collected_drops: &mut collected_drops,
261 visited: Default::default(),
262 }
263 .visit(block);
264 all_locals_dropped.union(&collected_drops);
270 }
271
272 {
274 let mut to_exclude = MixedBitSet::new_empty(all_locals_dropped.domain_size());
275 for path_idx in all_locals_dropped.iter() {
278 let move_path = &move_data.move_paths[path_idx];
279 let dropped_local = move_path.place.local;
280 if dropped_local == Local::ZERO {
288 debug!(?dropped_local, "skip return value");
289 to_exclude.insert(path_idx);
290 continue;
291 }
292 if is_closure_like && matches!(dropped_local, ty::CAPTURE_STRUCT_LOCAL) {
296 debug!(?dropped_local, "skip closure captures");
297 to_exclude.insert(path_idx);
298 continue;
299 }
300 if place_descendent_of_bids(path_idx, &move_data, &bid_places) {
315 debug!(?dropped_local, "skip descendent of bids");
316 to_exclude.insert(path_idx);
317 continue;
318 }
319 let observer_ty = move_path.place.ty(body, tcx).ty;
320 if ty_dropped_components
322 .entry(observer_ty)
323 .or_insert_with(|| {
324 extract_component_with_significant_dtor(tcx, typing_env, observer_ty)
325 })
326 .is_empty()
327 {
328 debug!(?dropped_local, "skip non-droppy types");
329 to_exclude.insert(path_idx);
330 continue;
331 }
332 }
333 if candidates.iter().all(|&(_, place)| candidates[0].1.local == place.local) {
337 for path_idx in all_locals_dropped.iter() {
338 if move_data.move_paths[path_idx].place.local == candidates[0].1.local {
339 to_exclude.insert(path_idx);
340 }
341 }
342 }
343 all_locals_dropped.subtract(&to_exclude);
344 }
345 if all_locals_dropped.is_empty() {
346 continue;
348 }
349
350 let local_names = assign_observables_names(
353 all_locals_dropped
354 .iter()
355 .map(|path_idx| move_data.move_paths[path_idx].place.local)
356 .chain(candidates.iter().map(|(_, place)| place.local)),
357 &locals_with_user_names,
358 );
359
360 let mut lint_root = None;
361 let mut local_labels = vec![];
362 for &(_, place) in candidates {
364 let linted_local_decl = &body.local_decls[place.local];
365 let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
366 bug!("a name should have been assigned")
367 };
368 let name = name.as_str();
369
370 if lint_root.is_none()
371 && let ClearCrossCrate::Set(data) =
372 &body.source_scopes[linted_local_decl.source_info.scope].local_data
373 {
374 lint_root = Some(data.lint_root);
375 }
376
377 let mut seen_dyn = false;
379 let destructors = ty_dropped_components
380 .get(&linted_local_decl.ty)
381 .unwrap()
382 .iter()
383 .filter_map(|&ty| {
384 if let Some(span) = ty_dtor_span(tcx, ty) {
385 Some(DestructorLabel { span, name, dtor_kind: "concrete" })
386 } else if matches!(ty.kind(), ty::Dynamic(..)) {
387 if seen_dyn {
388 None
389 } else {
390 seen_dyn = true;
391 Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
392 }
393 } else {
394 None
395 }
396 })
397 .collect();
398 local_labels.push(LocalLabel {
399 span: linted_local_decl.source_info.span,
400 destructors,
401 name,
402 is_generated_name,
403 is_dropped_first_edition_2024: true,
404 });
405 }
406
407 for path_idx in all_locals_dropped.iter() {
409 let place = &move_data.move_paths[path_idx].place;
410 let observer_ty = place.ty(body, tcx).ty;
412
413 let observer_local_decl = &body.local_decls[place.local];
414 let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
415 bug!("a name should have been assigned")
416 };
417 let name = name.as_str();
418
419 let mut seen_dyn = false;
420 let destructors = extract_component_with_significant_dtor(tcx, typing_env, observer_ty)
421 .into_iter()
422 .filter_map(|ty| {
423 if let Some(span) = ty_dtor_span(tcx, ty) {
424 Some(DestructorLabel { span, name, dtor_kind: "concrete" })
425 } else if matches!(ty.kind(), ty::Dynamic(..)) {
426 if seen_dyn {
427 None
428 } else {
429 seen_dyn = true;
430 Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
431 }
432 } else {
433 None
434 }
435 })
436 .collect();
437 local_labels.push(LocalLabel {
438 span: observer_local_decl.source_info.span,
439 destructors,
440 name,
441 is_generated_name,
442 is_dropped_first_edition_2024: false,
443 });
444 }
445
446 let span = local_labels[0].span;
447 tcx.emit_node_span_lint(
448 lint::builtin::TAIL_EXPR_DROP_ORDER,
449 lint_root.unwrap_or(CRATE_HIR_ID),
450 span,
451 TailExprDropOrderLint { local_labels, drop_span, _epilogue: () },
452 );
453 }
454}
455
456fn collect_user_names(body: &Body<'_>) -> FxIndexMap<Local, Symbol> {
458 let mut names = FxIndexMap::default();
459 for var_debug_info in &body.var_debug_info {
460 if let mir::VarDebugInfoContents::Place(place) = &var_debug_info.value
461 && let Some(local) = place.local_or_deref_local()
462 {
463 names.entry(local).or_insert(var_debug_info.name);
464 }
465 }
466 names
467}
468
469fn assign_observables_names(
471 locals: impl IntoIterator<Item = Local>,
472 user_names: &FxIndexMap<Local, Symbol>,
473) -> FxIndexMap<Local, (String, bool)> {
474 let mut names = FxIndexMap::default();
475 let mut assigned_names = FxHashSet::default();
476 let mut idx = 0u64;
477 let mut fresh_name = || {
478 idx += 1;
479 (format!("#{idx}"), true)
480 };
481 for local in locals {
482 let name = if let Some(name) = user_names.get(&local) {
483 let name = name.as_str();
484 if assigned_names.contains(name) { fresh_name() } else { (name.to_owned(), false) }
485 } else {
486 fresh_name()
487 };
488 assigned_names.insert(name.0.clone());
489 names.insert(local, name);
490 }
491 names
492}
493
494#[derive(LintDiagnostic)]
495#[diag(mir_transform_tail_expr_drop_order)]
496struct TailExprDropOrderLint<'a> {
497 #[subdiagnostic]
498 local_labels: Vec<LocalLabel<'a>>,
499 #[label(mir_transform_drop_location)]
500 drop_span: Option<Span>,
501 #[note(mir_transform_note_epilogue)]
502 _epilogue: (),
503}
504
505struct LocalLabel<'a> {
506 span: Span,
507 name: &'a str,
508 is_generated_name: bool,
509 is_dropped_first_edition_2024: bool,
510 destructors: Vec<DestructorLabel<'a>>,
511}
512
513impl Subdiagnostic for LocalLabel<'_> {
515 fn add_to_diag_with<
516 G: rustc_errors::EmissionGuarantee,
517 F: rustc_errors::SubdiagMessageOp<G>,
518 >(
519 self,
520 diag: &mut rustc_errors::Diag<'_, G>,
521 f: &F,
522 ) {
523 diag.arg("name", self.name);
524 diag.arg("is_generated_name", self.is_generated_name);
525 diag.arg("is_dropped_first_edition_2024", self.is_dropped_first_edition_2024);
526 let msg = f(diag, crate::fluent_generated::mir_transform_tail_expr_local.into());
527 diag.span_label(self.span, msg);
528 for dtor in self.destructors {
529 dtor.add_to_diag_with(diag, f);
530 }
531 let msg = f(diag, crate::fluent_generated::mir_transform_label_local_epilogue);
532 diag.span_label(self.span, msg);
533 }
534}
535
536#[derive(Subdiagnostic)]
537#[note(mir_transform_tail_expr_dtor)]
538struct DestructorLabel<'a> {
539 #[primary_span]
540 span: Span,
541 dtor_kind: &'static str,
542 name: &'a str,
543}