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, MirDumper, Place, StatementKind,
16 TerminatorKind,
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 drop: _,
138 async_fut: _,
139 } = &terminator.kind
140 && place_has_common_prefix(dropped_place, self.place)
141 {
142 self.collected_drops.union(&*dropped_local_there.borrow());
145 if self.drop_span.is_none() {
146 *self.drop_span = Some(terminator.source_info.span);
150 }
151 } else {
155 self.visit(succ)
156 }
157 }
158 }
159}
160
161fn place_descendent_of_bids<'tcx>(
165 mut idx: MovePathIndex,
166 move_data: &MoveData<'tcx>,
167 bids: &UnordSet<&Place<'tcx>>,
168) -> bool {
169 loop {
170 let path = &move_data.move_paths[idx];
171 if bids.contains(&path.place) {
172 return true;
173 }
174 if let Some(parent) = path.parent {
175 idx = parent;
176 } else {
177 return false;
178 }
179 }
180}
181
182pub(crate) fn run_lint<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &Body<'tcx>) {
184 if matches!(tcx.def_kind(def_id), rustc_hir::def::DefKind::SyntheticCoroutineBody) {
185 return;
187 }
188 if body.span.edition().at_least_rust_2024()
189 || tcx.lints_that_dont_need_to_run(()).contains(&lint::LintId::of(TAIL_EXPR_DROP_ORDER))
190 {
191 return;
192 }
193
194 let typing_env = ty::TypingEnv::non_body_analysis(tcx, def_id);
197
198 let mut bid_per_block = FxIndexMap::default();
204 let mut bid_places = UnordSet::new();
205
206 let mut ty_dropped_components = UnordMap::default();
207 for (block, data) in body.basic_blocks.iter_enumerated() {
208 for (statement_index, stmt) in data.statements.iter().enumerate() {
209 if let StatementKind::BackwardIncompatibleDropHint { place, reason: _ } = &stmt.kind {
210 let ty = place.ty(body, tcx).ty;
211 if ty_dropped_components
212 .entry(ty)
213 .or_insert_with(|| extract_component_with_significant_dtor(tcx, typing_env, ty))
214 .is_empty()
215 {
216 continue;
217 }
218 bid_per_block
219 .entry(block)
220 .or_insert(vec![])
221 .push((Location { block, statement_index }, &**place));
222 bid_places.insert(&**place);
223 }
224 }
225 }
226 if bid_per_block.is_empty() {
227 return;
228 }
229
230 if let Some(dumper) = MirDumper::new(tcx, "lint_tail_expr_drop_order", body) {
231 dumper.dump_mir(body);
232 }
233
234 let locals_with_user_names = collect_user_names(body);
235 let is_closure_like = tcx.is_closure_like(def_id.to_def_id());
236
237 let move_data = MoveData::gather_moves(body, tcx, |_| true);
241 let mut maybe_init = MaybeInitializedPlaces::new(tcx, body, &move_data)
242 .iterate_to_fixpoint(tcx, body, None)
243 .into_results_cursor(body);
244 let mut block_drop_value_info =
245 IndexVec::from_elem_n(MovePathIndexAtBlock::Unknown, body.basic_blocks.len());
246 for (&block, candidates) in &bid_per_block {
247 let mut all_locals_dropped = MixedBitSet::new_empty(move_data.move_paths.len());
250 let mut drop_span = None;
251 for &(_, place) in candidates.iter() {
252 let mut collected_drops = MixedBitSet::new_empty(move_data.move_paths.len());
253 DropsReachable {
260 body,
261 place,
262 drop_span: &mut drop_span,
263 move_data: &move_data,
264 maybe_init: &mut maybe_init,
265 block_drop_value_info: &mut block_drop_value_info,
266 collected_drops: &mut collected_drops,
267 visited: Default::default(),
268 }
269 .visit(block);
270 all_locals_dropped.union(&collected_drops);
276 }
277
278 {
280 let mut to_exclude = MixedBitSet::new_empty(all_locals_dropped.domain_size());
281 for path_idx in all_locals_dropped.iter() {
284 let move_path = &move_data.move_paths[path_idx];
285 let dropped_local = move_path.place.local;
286 if dropped_local == Local::ZERO {
294 debug!(?dropped_local, "skip return value");
295 to_exclude.insert(path_idx);
296 continue;
297 }
298 if is_closure_like && matches!(dropped_local, ty::CAPTURE_STRUCT_LOCAL) {
302 debug!(?dropped_local, "skip closure captures");
303 to_exclude.insert(path_idx);
304 continue;
305 }
306 if place_descendent_of_bids(path_idx, &move_data, &bid_places) {
321 debug!(?dropped_local, "skip descendent of bids");
322 to_exclude.insert(path_idx);
323 continue;
324 }
325 let observer_ty = move_path.place.ty(body, tcx).ty;
326 if ty_dropped_components
328 .entry(observer_ty)
329 .or_insert_with(|| {
330 extract_component_with_significant_dtor(tcx, typing_env, observer_ty)
331 })
332 .is_empty()
333 {
334 debug!(?dropped_local, "skip non-droppy types");
335 to_exclude.insert(path_idx);
336 continue;
337 }
338 }
339 if candidates.iter().all(|&(_, place)| candidates[0].1.local == place.local) {
343 for path_idx in all_locals_dropped.iter() {
344 if move_data.move_paths[path_idx].place.local == candidates[0].1.local {
345 to_exclude.insert(path_idx);
346 }
347 }
348 }
349 all_locals_dropped.subtract(&to_exclude);
350 }
351 if all_locals_dropped.is_empty() {
352 continue;
354 }
355
356 let local_names = assign_observables_names(
359 all_locals_dropped
360 .iter()
361 .map(|path_idx| move_data.move_paths[path_idx].place.local)
362 .chain(candidates.iter().map(|(_, place)| place.local)),
363 &locals_with_user_names,
364 );
365
366 let mut lint_root = None;
367 let mut local_labels = vec![];
368 for &(_, place) in candidates {
370 let linted_local_decl = &body.local_decls[place.local];
371 let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
372 bug!("a name should have been assigned")
373 };
374 let name = name.as_str();
375
376 if lint_root.is_none()
377 && let ClearCrossCrate::Set(data) =
378 &body.source_scopes[linted_local_decl.source_info.scope].local_data
379 {
380 lint_root = Some(data.lint_root);
381 }
382
383 let mut seen_dyn = false;
385 let destructors = ty_dropped_components
386 .get(&linted_local_decl.ty)
387 .unwrap()
388 .iter()
389 .filter_map(|&ty| {
390 if let Some(span) = ty_dtor_span(tcx, ty) {
391 Some(DestructorLabel { span, name, dtor_kind: "concrete" })
392 } else if matches!(ty.kind(), ty::Dynamic(..)) {
393 if seen_dyn {
394 None
395 } else {
396 seen_dyn = true;
397 Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
398 }
399 } else {
400 None
401 }
402 })
403 .collect();
404 local_labels.push(LocalLabel {
405 span: linted_local_decl.source_info.span,
406 destructors,
407 name,
408 is_generated_name,
409 is_dropped_first_edition_2024: true,
410 });
411 }
412
413 for path_idx in all_locals_dropped.iter() {
415 let place = &move_data.move_paths[path_idx].place;
416 let observer_ty = place.ty(body, tcx).ty;
418
419 let observer_local_decl = &body.local_decls[place.local];
420 let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
421 bug!("a name should have been assigned")
422 };
423 let name = name.as_str();
424
425 let mut seen_dyn = false;
426 let destructors = extract_component_with_significant_dtor(tcx, typing_env, observer_ty)
427 .into_iter()
428 .filter_map(|ty| {
429 if let Some(span) = ty_dtor_span(tcx, ty) {
430 Some(DestructorLabel { span, name, dtor_kind: "concrete" })
431 } else if matches!(ty.kind(), ty::Dynamic(..)) {
432 if seen_dyn {
433 None
434 } else {
435 seen_dyn = true;
436 Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
437 }
438 } else {
439 None
440 }
441 })
442 .collect();
443 local_labels.push(LocalLabel {
444 span: observer_local_decl.source_info.span,
445 destructors,
446 name,
447 is_generated_name,
448 is_dropped_first_edition_2024: false,
449 });
450 }
451
452 let span = local_labels[0].span;
453 tcx.emit_node_span_lint(
454 lint::builtin::TAIL_EXPR_DROP_ORDER,
455 lint_root.unwrap_or(CRATE_HIR_ID),
456 span,
457 TailExprDropOrderLint { local_labels, drop_span, _epilogue: () },
458 );
459 }
460}
461
462fn collect_user_names(body: &Body<'_>) -> FxIndexMap<Local, Symbol> {
464 let mut names = FxIndexMap::default();
465 for var_debug_info in &body.var_debug_info {
466 if let mir::VarDebugInfoContents::Place(place) = &var_debug_info.value
467 && let Some(local) = place.local_or_deref_local()
468 {
469 names.entry(local).or_insert(var_debug_info.name);
470 }
471 }
472 names
473}
474
475fn assign_observables_names(
477 locals: impl IntoIterator<Item = Local>,
478 user_names: &FxIndexMap<Local, Symbol>,
479) -> FxIndexMap<Local, (String, bool)> {
480 let mut names = FxIndexMap::default();
481 let mut assigned_names = FxHashSet::default();
482 let mut idx = 0u64;
483 let mut fresh_name = || {
484 idx += 1;
485 (format!("#{idx}"), true)
486 };
487 for local in locals {
488 let name = if let Some(name) = user_names.get(&local) {
489 let name = name.as_str();
490 if assigned_names.contains(name) { fresh_name() } else { (name.to_owned(), false) }
491 } else {
492 fresh_name()
493 };
494 assigned_names.insert(name.0.clone());
495 names.insert(local, name);
496 }
497 names
498}
499
500#[derive(LintDiagnostic)]
501#[diag(mir_transform_tail_expr_drop_order)]
502struct TailExprDropOrderLint<'a> {
503 #[subdiagnostic]
504 local_labels: Vec<LocalLabel<'a>>,
505 #[label(mir_transform_drop_location)]
506 drop_span: Option<Span>,
507 #[note(mir_transform_note_epilogue)]
508 _epilogue: (),
509}
510
511struct LocalLabel<'a> {
512 span: Span,
513 name: &'a str,
514 is_generated_name: bool,
515 is_dropped_first_edition_2024: bool,
516 destructors: Vec<DestructorLabel<'a>>,
517}
518
519impl Subdiagnostic for LocalLabel<'_> {
521 fn add_to_diag<G: rustc_errors::EmissionGuarantee>(self, diag: &mut rustc_errors::Diag<'_, G>) {
522 diag.remove_arg("name");
524 diag.arg("name", self.name);
525 diag.remove_arg("is_generated_name");
526 diag.arg("is_generated_name", self.is_generated_name);
527 diag.remove_arg("is_dropped_first_edition_2024");
528 diag.arg("is_dropped_first_edition_2024", self.is_dropped_first_edition_2024);
529 let msg = diag.eagerly_translate(crate::fluent_generated::mir_transform_tail_expr_local);
530 diag.span_label(self.span, msg);
531 for dtor in self.destructors {
532 dtor.add_to_diag(diag);
533 }
534 let msg =
535 diag.eagerly_translate(crate::fluent_generated::mir_transform_label_local_epilogue);
536 diag.span_label(self.span, msg);
537 }
538}
539
540#[derive(Subdiagnostic)]
541#[note(mir_transform_tail_expr_dtor)]
542struct DestructorLabel<'a> {
543 #[primary_span]
544 span: Span,
545 dtor_kind: &'static str,
546 name: &'a str,
547}