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 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 dump_mir(tcx, false, "lint_tail_expr_drop_order", &0 as _, body, |_, _| Ok(()));
231 let locals_with_user_names = collect_user_names(body);
232 let is_closure_like = tcx.is_closure_like(def_id.to_def_id());
233
234 let move_data = MoveData::gather_moves(body, tcx, |_| true);
238 let mut maybe_init = MaybeInitializedPlaces::new(tcx, body, &move_data)
239 .iterate_to_fixpoint(tcx, body, None)
240 .into_results_cursor(body);
241 let mut block_drop_value_info =
242 IndexVec::from_elem_n(MovePathIndexAtBlock::Unknown, body.basic_blocks.len());
243 for (&block, candidates) in &bid_per_block {
244 let mut all_locals_dropped = MixedBitSet::new_empty(move_data.move_paths.len());
247 let mut drop_span = None;
248 for &(_, place) in candidates.iter() {
249 let mut collected_drops = MixedBitSet::new_empty(move_data.move_paths.len());
250 DropsReachable {
257 body,
258 place,
259 drop_span: &mut drop_span,
260 move_data: &move_data,
261 maybe_init: &mut maybe_init,
262 block_drop_value_info: &mut block_drop_value_info,
263 collected_drops: &mut collected_drops,
264 visited: Default::default(),
265 }
266 .visit(block);
267 all_locals_dropped.union(&collected_drops);
273 }
274
275 {
277 let mut to_exclude = MixedBitSet::new_empty(all_locals_dropped.domain_size());
278 for path_idx in all_locals_dropped.iter() {
281 let move_path = &move_data.move_paths[path_idx];
282 let dropped_local = move_path.place.local;
283 if dropped_local == Local::ZERO {
291 debug!(?dropped_local, "skip return value");
292 to_exclude.insert(path_idx);
293 continue;
294 }
295 if is_closure_like && matches!(dropped_local, ty::CAPTURE_STRUCT_LOCAL) {
299 debug!(?dropped_local, "skip closure captures");
300 to_exclude.insert(path_idx);
301 continue;
302 }
303 if place_descendent_of_bids(path_idx, &move_data, &bid_places) {
318 debug!(?dropped_local, "skip descendent of bids");
319 to_exclude.insert(path_idx);
320 continue;
321 }
322 let observer_ty = move_path.place.ty(body, tcx).ty;
323 if ty_dropped_components
325 .entry(observer_ty)
326 .or_insert_with(|| {
327 extract_component_with_significant_dtor(tcx, typing_env, observer_ty)
328 })
329 .is_empty()
330 {
331 debug!(?dropped_local, "skip non-droppy types");
332 to_exclude.insert(path_idx);
333 continue;
334 }
335 }
336 if candidates.iter().all(|&(_, place)| candidates[0].1.local == place.local) {
340 for path_idx in all_locals_dropped.iter() {
341 if move_data.move_paths[path_idx].place.local == candidates[0].1.local {
342 to_exclude.insert(path_idx);
343 }
344 }
345 }
346 all_locals_dropped.subtract(&to_exclude);
347 }
348 if all_locals_dropped.is_empty() {
349 continue;
351 }
352
353 let local_names = assign_observables_names(
356 all_locals_dropped
357 .iter()
358 .map(|path_idx| move_data.move_paths[path_idx].place.local)
359 .chain(candidates.iter().map(|(_, place)| place.local)),
360 &locals_with_user_names,
361 );
362
363 let mut lint_root = None;
364 let mut local_labels = vec![];
365 for &(_, place) in candidates {
367 let linted_local_decl = &body.local_decls[place.local];
368 let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
369 bug!("a name should have been assigned")
370 };
371 let name = name.as_str();
372
373 if lint_root.is_none()
374 && let ClearCrossCrate::Set(data) =
375 &body.source_scopes[linted_local_decl.source_info.scope].local_data
376 {
377 lint_root = Some(data.lint_root);
378 }
379
380 let mut seen_dyn = false;
382 let destructors = ty_dropped_components
383 .get(&linted_local_decl.ty)
384 .unwrap()
385 .iter()
386 .filter_map(|&ty| {
387 if let Some(span) = ty_dtor_span(tcx, ty) {
388 Some(DestructorLabel { span, name, dtor_kind: "concrete" })
389 } else if matches!(ty.kind(), ty::Dynamic(..)) {
390 if seen_dyn {
391 None
392 } else {
393 seen_dyn = true;
394 Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
395 }
396 } else {
397 None
398 }
399 })
400 .collect();
401 local_labels.push(LocalLabel {
402 span: linted_local_decl.source_info.span,
403 destructors,
404 name,
405 is_generated_name,
406 is_dropped_first_edition_2024: true,
407 });
408 }
409
410 for path_idx in all_locals_dropped.iter() {
412 let place = &move_data.move_paths[path_idx].place;
413 let observer_ty = place.ty(body, tcx).ty;
415
416 let observer_local_decl = &body.local_decls[place.local];
417 let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
418 bug!("a name should have been assigned")
419 };
420 let name = name.as_str();
421
422 let mut seen_dyn = false;
423 let destructors = extract_component_with_significant_dtor(tcx, typing_env, observer_ty)
424 .into_iter()
425 .filter_map(|ty| {
426 if let Some(span) = ty_dtor_span(tcx, ty) {
427 Some(DestructorLabel { span, name, dtor_kind: "concrete" })
428 } else if matches!(ty.kind(), ty::Dynamic(..)) {
429 if seen_dyn {
430 None
431 } else {
432 seen_dyn = true;
433 Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
434 }
435 } else {
436 None
437 }
438 })
439 .collect();
440 local_labels.push(LocalLabel {
441 span: observer_local_decl.source_info.span,
442 destructors,
443 name,
444 is_generated_name,
445 is_dropped_first_edition_2024: false,
446 });
447 }
448
449 let span = local_labels[0].span;
450 tcx.emit_node_span_lint(
451 lint::builtin::TAIL_EXPR_DROP_ORDER,
452 lint_root.unwrap_or(CRATE_HIR_ID),
453 span,
454 TailExprDropOrderLint { local_labels, drop_span, _epilogue: () },
455 );
456 }
457}
458
459fn collect_user_names(body: &Body<'_>) -> FxIndexMap<Local, Symbol> {
461 let mut names = FxIndexMap::default();
462 for var_debug_info in &body.var_debug_info {
463 if let mir::VarDebugInfoContents::Place(place) = &var_debug_info.value
464 && let Some(local) = place.local_or_deref_local()
465 {
466 names.entry(local).or_insert(var_debug_info.name);
467 }
468 }
469 names
470}
471
472fn assign_observables_names(
474 locals: impl IntoIterator<Item = Local>,
475 user_names: &FxIndexMap<Local, Symbol>,
476) -> FxIndexMap<Local, (String, bool)> {
477 let mut names = FxIndexMap::default();
478 let mut assigned_names = FxHashSet::default();
479 let mut idx = 0u64;
480 let mut fresh_name = || {
481 idx += 1;
482 (format!("#{idx}"), true)
483 };
484 for local in locals {
485 let name = if let Some(name) = user_names.get(&local) {
486 let name = name.as_str();
487 if assigned_names.contains(name) { fresh_name() } else { (name.to_owned(), false) }
488 } else {
489 fresh_name()
490 };
491 assigned_names.insert(name.0.clone());
492 names.insert(local, name);
493 }
494 names
495}
496
497#[derive(LintDiagnostic)]
498#[diag(mir_transform_tail_expr_drop_order)]
499struct TailExprDropOrderLint<'a> {
500 #[subdiagnostic]
501 local_labels: Vec<LocalLabel<'a>>,
502 #[label(mir_transform_drop_location)]
503 drop_span: Option<Span>,
504 #[note(mir_transform_note_epilogue)]
505 _epilogue: (),
506}
507
508struct LocalLabel<'a> {
509 span: Span,
510 name: &'a str,
511 is_generated_name: bool,
512 is_dropped_first_edition_2024: bool,
513 destructors: Vec<DestructorLabel<'a>>,
514}
515
516impl Subdiagnostic for LocalLabel<'_> {
518 fn add_to_diag<G: rustc_errors::EmissionGuarantee>(self, diag: &mut rustc_errors::Diag<'_, G>) {
519 diag.arg("name", self.name);
520 diag.arg("is_generated_name", self.is_generated_name);
521 diag.arg("is_dropped_first_edition_2024", self.is_dropped_first_edition_2024);
522 let msg = diag.eagerly_translate(crate::fluent_generated::mir_transform_tail_expr_local);
523 diag.span_label(self.span, msg);
524 for dtor in self.destructors {
525 dtor.add_to_diag(diag);
526 }
527 let msg =
528 diag.eagerly_translate(crate::fluent_generated::mir_transform_label_local_epilogue);
529 diag.span_label(self.span, msg);
530 }
531}
532
533#[derive(Subdiagnostic)]
534#[note(mir_transform_tail_expr_dtor)]
535struct DestructorLabel<'a> {
536 #[primary_span]
537 span: Span,
538 dtor_kind: &'static str,
539 name: &'a str,
540}