1use std::cell::RefCell;
2use std::collections::hash_map;
3use std::rc::Rc;
45use itertools::Itertoolsas _;
6use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
7use rustc_data_structures::unord::{UnordMap, UnordSet};
8use rustc_errors::formatting::DiagMessageAddArg;
9use rustc_errors::{Subdiagnostic, msg};
10use rustc_hir::CRATE_HIR_ID;
11use rustc_hir::def_id::LocalDefId;
12use rustc_index::bit_set::MixedBitSet;
13use rustc_index::{IndexSlice, IndexVec};
14use rustc_lint_defs::LintId;
15use rustc_lint_defs::builtin::TAIL_EXPR_DROP_ORDER;
16use rustc_macros::{Diagnostic, Subdiagnostic};
17use rustc_middle::mir::{
18self, BasicBlock, Body, ClearCrossCrate, Local, Location, MirDumper, Place, StatementKind,
19TerminatorKind,
20};
21use rustc_middle::ty::significant_drop_order::{
22extract_component_with_significant_dtor, ty_dtor_span,
23};
24use rustc_middle::ty::{self, TyCtxt};
25use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
26use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex};
27use rustc_mir_dataflow::{Analysis, MaybeReachable, ResultsCursor};
28use rustc_span::{DUMMY_SP, Span, Symbol, bug};
29use tracing::debug;
3031fn place_has_common_prefix<'tcx>(left: &Place<'tcx>, right: &Place<'tcx>) -> bool {
32left.local == right.local
33 && left.projection.iter().zip(right.projection).all(|(left, right)| left == right)
34}
3536/// Cache entry of `drop` at a `BasicBlock`
37#[derive(#[automatically_derived]
impl ::core::fmt::Debug for MovePathIndexAtBlock {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
Self::Unknown => ::core::fmt::Formatter::write_str(f, "Unknown"),
Self::None => ::core::fmt::Formatter::write_str(f, "None"),
Self::Some(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Some",
&__self_0),
}
}
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for MovePathIndexAtBlock { }
#[automatically_derived]
impl ::core::clone::Clone for MovePathIndexAtBlock {
#[inline]
fn clone(&self) -> Self {
let _: ::core::clone::AssertParamIsClone<MovePathIndex>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for MovePathIndexAtBlock { }Copy)]
38enum MovePathIndexAtBlock {
39/// We know nothing yet
40Unknown,
41/// We know that the `drop` here has no effect
42None,
43/// We know that the `drop` here will invoke a destructor
44Some(MovePathIndex),
45}
4647struct DropsReachable<'a, 'mir, 'tcx> {
48 body: &'a Body<'tcx>,
49 place: &'a Place<'tcx>,
50 drop_span: &'a mut Option<Span>,
51 move_data: &'a MoveData<'tcx>,
52 maybe_init: &'a mut ResultsCursor<'mir, 'tcx, MaybeInitializedPlaces<'mir, 'tcx>>,
53 block_drop_value_info: &'a mut IndexSlice<BasicBlock, MovePathIndexAtBlock>,
54 collected_drops: &'a mut MixedBitSet<MovePathIndex>,
55 visited: FxHashMap<BasicBlock, Rc<RefCell<MixedBitSet<MovePathIndex>>>>,
56}
5758impl<'a, 'mir, 'tcx> DropsReachable<'a, 'mir, 'tcx> {
59fn visit(&mut self, block: BasicBlock) {
60let move_set_size = self.move_data.move_paths.len();
61let make_new_path_set = || Rc::new(RefCell::new(MixedBitSet::new_empty(move_set_size)));
6263let data = &self.body.basic_blocks[block];
64let Some(terminator) = &data.terminator else { return };
65// Given that we observe these dropped locals here at `block` so far, we will try to update
66 // the successor blocks. An occupied entry at `block` in `self.visited` signals that we
67 // have visited `block` before.
68let dropped_local_here =
69Rc::clone(self.visited.entry(block).or_insert_with(make_new_path_set));
70// We could have invoked reverse lookup for a `MovePathIndex` every time, but unfortunately
71 // it is expensive. Let's cache them in `self.block_drop_value_info`.
72match self.block_drop_value_info[block] {
73 MovePathIndexAtBlock::Some(dropped) => {
74dropped_local_here.borrow_mut().insert(dropped);
75 }
76 MovePathIndexAtBlock::Unknown => {
77if let TerminatorKind::Drop { place, .. } = &terminator.kind
78 && let LookupResult::Exact(idx) | LookupResult::Parent(Some(idx)) =
79self.move_data.rev_lookup.find(place.as_ref())
80 {
81// Since we are working with MIRs at a very early stage, observing a `drop`
82 // terminator is not indicative enough that the drop will definitely happen.
83 // That is decided in the drop elaboration pass instead. Therefore, we need to
84 // consult with the maybe-initialization information.
85self.maybe_init.seek_before_primary_effect(Location {
86block,
87 statement_index: data.statements.len(),
88 });
8990// Check if the drop of `place` under inspection is really in effect. This is
91 // true only when `place` may have been initialized along a control flow path
92 // from a BID to the drop program point today. In other words, this is where
93 // the drop of `place` will happen in the future instead.
94if let MaybeReachable::Reachable(maybe_init) = self.maybe_init.get()
95 && maybe_init.contains(idx)
96 {
97// We also cache the drop information, so that we do not need to check on
98 // data-flow cursor again.
99self.block_drop_value_info[block] = MovePathIndexAtBlock::Some(idx);
100dropped_local_here.borrow_mut().insert(idx);
101 } else {
102self.block_drop_value_info[block] = MovePathIndexAtBlock::None;
103 }
104 }
105 }
106 MovePathIndexAtBlock::None => {}
107 }
108109for succ in terminator.successors() {
110let target = &self.body.basic_blocks[succ];
111if target.is_cleanup {
112continue;
113 }
114115// As long as we are passing through a new block, or new dropped places to propagate,
116 // we will proceed with `succ`
117let dropped_local_there = match self.visited.entry(succ) {
118 hash_map::Entry::Occupied(occupied_entry) => {
119if succ == block
120 || !occupied_entry.get().borrow_mut().union(&*dropped_local_here.borrow())
121 {
122// `succ` has been visited but no new drops observed so far,
123 // so we can bail on `succ` until new drop information arrives
124continue;
125 }
126 Rc::clone(occupied_entry.get())
127 }
128 hash_map::Entry::Vacant(vacant_entry) => Rc::clone(
129 vacant_entry.insert(Rc::new(RefCell::new(dropped_local_here.borrow().clone()))),
130 ),
131 };
132if let Some(terminator) = &target.terminator
133 && let TerminatorKind::Drop {
134 place: dropped_place,
135 target: _,
136 unwind: _,
137 replace: _,
138 drop: _,
139 } = &terminator.kind
140 && place_has_common_prefix(dropped_place, self.place)
141 {
142// We have now reached the current drop of the `place`.
143 // Let's check the observed dropped places in.
144self.collected_drops.union(&*dropped_local_there.borrow());
145if self.drop_span.is_none() {
146// FIXME(@dingxiangfei2009): it turns out that `self.body.source_scopes` are
147 // still a bit wonky. There is a high chance that this span still points to a
148 // block rather than a statement semicolon.
149*self.drop_span = Some(terminator.source_info.span);
150 }
151// Now we have discovered a simple control flow path from a future drop point
152 // to the current drop point.
153 // We will not continue from there.
154} else {
155self.visit(succ)
156 }
157 }
158 }
159}
160161/// Check if a moved place at `idx` is a part of a BID.
162/// The use of this check is that we will consider drops on these
163/// as a drop of the overall BID and, thus, we can exclude it from the diagnosis.
164fn place_descendent_of_bids<'tcx>(
165mut idx: MovePathIndex,
166 move_data: &MoveData<'tcx>,
167 bids: &UnordSet<&Place<'tcx>>,
168) -> bool {
169loop {
170let path = &move_data.move_paths[idx];
171if bids.contains(&path.place) {
172return true;
173 }
174if let Some(parent) = path.parent {
175idx = parent;
176 } else {
177return false;
178 }
179 }
180}
181182/// The core of the lint `tail-expr-drop-order`
183pub(crate) fn run_lint<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId, body: &Body<'tcx>) {
184if #[allow(non_exhaustive_omitted_patterns)] match tcx.def_kind(def_id) {
rustc_hir::def::DefKind::SyntheticCoroutineBody => true,
_ => false,
}matches!(tcx.def_kind(def_id), rustc_hir::def::DefKind::SyntheticCoroutineBody) {
185// A synthetic coroutine has no HIR body and it is enough to just analyse the original body
186return;
187 }
188if body.span.edition().at_least_rust_2024()
189 || tcx.skippable_lints(()).contains(&LintId::of(TAIL_EXPR_DROP_ORDER))
190 {
191return;
192 }
193194// FIXME(typing_env): This should be able to reveal the opaques local to the
195 // body using the typeck results.
196let typing_env = ty::TypingEnv::non_body_analysis(tcx, def_id);
197198// ## About BIDs in blocks ##
199 // Track the set of blocks that contain a backwards-incompatible drop (BID)
200 // and, for each block, the vector of locations.
201 //
202 // We group them per-block because they tend to scheduled in the same drop ladder block.
203let mut bid_per_block = FxIndexMap::default();
204let mut bid_places = UnordSet::new();
205206let mut ty_dropped_components = UnordMap::default();
207for (block, data) in body.basic_blocks.iter_enumerated() {
208for (statement_index, stmt) in data.statements.iter().enumerate() {
209if let StatementKind::BackwardIncompatibleDropHint { place, reason: _ } = &stmt.kind {
210let ty = place.ty(body, tcx).ty;
211if ty_dropped_components
212 .entry(ty)
213 .or_insert_with(|| extract_component_with_significant_dtor(tcx, typing_env, ty))
214 .is_empty()
215 {
216continue;
217 }
218 bid_per_block
219 .entry(block)
220 .or_insert(::alloc::vec::Vec::new()vec![])
221 .push((Location { block, statement_index }, &**place));
222 bid_places.insert(&**place);
223 }
224 }
225 }
226if bid_per_block.is_empty() {
227return;
228 }
229230if let Some(dumper) = MirDumper::new(tcx, "lint_tail_expr_drop_order", body) {
231dumper.dump_mir(body);
232 }
233234let locals_with_user_names = collect_user_names(body);
235let is_closure_like = tcx.is_closure_like(def_id.to_def_id());
236237// Compute the "maybe initialized" information for this body.
238 // When we encounter a DROP of some place P we only care
239 // about the drop if `P` may be initialized.
240let move_data = MoveData::gather_moves(body, tcx, |_| true);
241let mut maybe_init = MaybeInitializedPlaces::new(tcx, body, &move_data)
242 .iterate_to_fixpoint(tcx, body, None)
243 .into_results_cursor(body);
244let mut block_drop_value_info =
245IndexVec::from_elem_n(MovePathIndexAtBlock::Unknown, body.basic_blocks.len());
246for (&block, candidates) in &bid_per_block {
247// We will collect drops on locals on paths between BID points to their actual drop locations
248 // into `all_locals_dropped`.
249let mut all_locals_dropped = MixedBitSet::new_empty(move_data.move_paths.len());
250let mut drop_span = None;
251for &(_, place) in candidates.iter() {
252let mut collected_drops = MixedBitSet::new_empty(move_data.move_paths.len());
253// ## On detecting change in relative drop order ##
254 // Iterate through each BID-containing block `block`.
255 // If the place `P` targeted by the BID is "maybe initialized",
256 // then search forward to find the actual `DROP(P)` point.
257 // Everything dropped between the BID and the actual drop point
258 // is something whose relative drop order will change.
259DropsReachable {
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// Compute the set `all_locals_dropped` of local variables that are dropped
271 // after the BID point but before the current drop point.
272 //
273 // These are the variables whose drop impls will be reordered with respect
274 // to `place`.
275all_locals_dropped.union(&collected_drops);
276 }
277278// We shall now exclude some local bindings for the following cases.
279{
280let mut to_exclude = MixedBitSet::new_empty(all_locals_dropped.domain_size());
281// We will now do subtraction from the candidate dropped locals, because of the
282 // following reasons.
283for path_idx in all_locals_dropped.iter() {
284let move_path = &move_data.move_paths[path_idx];
285let dropped_local = move_path.place.local;
286// a) A return value _0 will eventually be used
287 // Example:
288 // fn f() -> Droppy {
289 // let _x = Droppy;
290 // Droppy
291 // }
292 // _0 holds the literal `Droppy` and rightfully `_x` has to be dropped first
293if dropped_local == Local::ZERO {
294{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event /rustc-dev/d080e7dff1b0fc54541545252818f8cccf995d05/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs:294",
"rustc_mir_transform::lint_tail_expr_drop_order",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("/rustc-dev/d080e7dff1b0fc54541545252818f8cccf995d05/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs"),
::tracing_core::__macro_support::Option::Some(294u32),
::tracing_core::__macro_support::Option::Some("rustc_mir_transform::lint_tail_expr_drop_order"),
::tracing_core::field::FieldSet::new(&["message",
{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("dropped_local")
}> =
::tracing::__macro_support::FieldName::new("dropped_local");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("skip return value")
as &dyn ::tracing::field::Value)),
(::tracing::__macro_support::Option::Some(&::tracing::field::debug(&dropped_local)
as &dyn ::tracing::field::Value))])
});
} else { ; }
};debug!(?dropped_local, "skip return value");
295 to_exclude.insert(path_idx);
296continue;
297 }
298// b) If we are analysing a closure, the captures are still dropped last.
299 // This is part of the closure capture lifetime contract.
300 // They are similar to the return value _0 with respect to lifetime rules.
301if is_closure_like && #[allow(non_exhaustive_omitted_patterns)] match dropped_local {
ty::CAPTURE_STRUCT_LOCAL => true,
_ => false,
}matches!(dropped_local, ty::CAPTURE_STRUCT_LOCAL) {
302{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event /rustc-dev/d080e7dff1b0fc54541545252818f8cccf995d05/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs:302",
"rustc_mir_transform::lint_tail_expr_drop_order",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("/rustc-dev/d080e7dff1b0fc54541545252818f8cccf995d05/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs"),
::tracing_core::__macro_support::Option::Some(302u32),
::tracing_core::__macro_support::Option::Some("rustc_mir_transform::lint_tail_expr_drop_order"),
::tracing_core::field::FieldSet::new(&["message",
{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("dropped_local")
}> =
::tracing::__macro_support::FieldName::new("dropped_local");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("skip closure captures")
as &dyn ::tracing::field::Value)),
(::tracing::__macro_support::Option::Some(&::tracing::field::debug(&dropped_local)
as &dyn ::tracing::field::Value))])
});
} else { ; }
};debug!(?dropped_local, "skip closure captures");
303 to_exclude.insert(path_idx);
304continue;
305 }
306// c) Sometimes we collect places that are projections into the BID locals,
307 // so they are considered dropped now.
308 // Example:
309 // struct NotVeryDroppy(Droppy);
310 // impl Drop for Droppy {..}
311 // fn f() -> NotVeryDroppy {
312 // let x = NotVeryDroppy(droppy());
313 // {
314 // let y: Droppy = x.0;
315 // NotVeryDroppy(y)
316 // }
317 // }
318 // `y` takes `x.0`, which invalidates `x` as a complete `NotVeryDroppy`
319 // so there is no point in linting against `x` any more.
320if place_descendent_of_bids(path_idx, &move_data, &bid_places) {
321{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event /rustc-dev/d080e7dff1b0fc54541545252818f8cccf995d05/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs:321",
"rustc_mir_transform::lint_tail_expr_drop_order",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("/rustc-dev/d080e7dff1b0fc54541545252818f8cccf995d05/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs"),
::tracing_core::__macro_support::Option::Some(321u32),
::tracing_core::__macro_support::Option::Some("rustc_mir_transform::lint_tail_expr_drop_order"),
::tracing_core::field::FieldSet::new(&["message",
{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("dropped_local")
}> =
::tracing::__macro_support::FieldName::new("dropped_local");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("skip descendent of bids")
as &dyn ::tracing::field::Value)),
(::tracing::__macro_support::Option::Some(&::tracing::field::debug(&dropped_local)
as &dyn ::tracing::field::Value))])
});
} else { ; }
};debug!(?dropped_local, "skip descendent of bids");
322 to_exclude.insert(path_idx);
323continue;
324 }
325let observer_ty = move_path.place.ty(body, tcx).ty;
326// d) The collected local has no custom destructor that passes our ecosystem filter.
327if 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{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event /rustc-dev/d080e7dff1b0fc54541545252818f8cccf995d05/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs:334",
"rustc_mir_transform::lint_tail_expr_drop_order",
::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("/rustc-dev/d080e7dff1b0fc54541545252818f8cccf995d05/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs"),
::tracing_core::__macro_support::Option::Some(334u32),
::tracing_core::__macro_support::Option::Some("rustc_mir_transform::lint_tail_expr_drop_order"),
::tracing_core::field::FieldSet::new(&["message",
{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("dropped_local")
}> =
::tracing::__macro_support::FieldName::new("dropped_local");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("skip non-droppy types")
as &dyn ::tracing::field::Value)),
(::tracing::__macro_support::Option::Some(&::tracing::field::debug(&dropped_local)
as &dyn ::tracing::field::Value))])
});
} else { ; }
};debug!(?dropped_local, "skip non-droppy types");
335 to_exclude.insert(path_idx);
336continue;
337 }
338 }
339// Suppose that all BIDs point into the same local,
340 // we can remove the this local from the observed drops,
341 // so that we can focus our diagnosis more on the others.
342if let Ok(local) = candidates.iter().map(|&(_, place)| place.local).all_equal_value() {
343for path_idx in all_locals_dropped.iter() {
344if move_data.move_paths[path_idx].place.local == local {
345 to_exclude.insert(path_idx);
346 }
347 }
348 }
349 all_locals_dropped.subtract(&to_exclude);
350 }
351if all_locals_dropped.is_empty() {
352// No drop effect is observable, so let us move on.
353continue;
354 }
355356// ## The final work to assemble the diagnosis ##
357 // First collect or generate fresh names for local variable bindings and temporary values.
358let 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 );
365366let mut lint_root = None;
367let mut local_labels = ::alloc::vec::Vec::new()vec![];
368// We now collect the types with custom destructors.
369for &(_, place) in candidates {
370let linted_local_decl = &body.local_decls[place.local];
371let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
372::rustc_span::macros::bug_impl(None,
format_args!("a name should have been assigned"), Location::caller())bug!("a name should have been assigned")373 };
374let name = name.as_str();
375376if 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 }
382383// Collect spans of the custom destructors.
384let mut seen_dyn = false;
385let destructors = ty_dropped_components
386 .get(&linted_local_decl.ty)
387 .unwrap()
388 .iter()
389 .filter_map(|&ty| {
390if let Some(span) = ty_dtor_span(tcx, ty) {
391Some(DestructorLabel { span, name, dtor_kind: "concrete" })
392 } else if #[allow(non_exhaustive_omitted_patterns)] match ty.kind() {
ty::Dynamic(..) => true,
_ => false,
}matches!(ty.kind(), ty::Dynamic(..)) {
393if seen_dyn {
394None
395} else {
396 seen_dyn = true;
397Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
398 }
399 } else {
400None
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 }
412413// Similarly, custom destructors of the observed drops.
414for path_idx in all_locals_dropped.iter() {
415let place = &move_data.move_paths[path_idx].place;
416// We are not using the type of the local because the drop may be partial.
417let observer_ty = place.ty(body, tcx).ty;
418419let observer_local_decl = &body.local_decls[place.local];
420let Some(&(ref name, is_generated_name)) = local_names.get(&place.local) else {
421::rustc_span::macros::bug_impl(None,
format_args!("a name should have been assigned"), Location::caller())bug!("a name should have been assigned")422 };
423let name = name.as_str();
424425let mut seen_dyn = false;
426let destructors = extract_component_with_significant_dtor(tcx, typing_env, observer_ty)
427 .into_iter()
428 .filter_map(|ty| {
429if let Some(span) = ty_dtor_span(tcx, ty) {
430Some(DestructorLabel { span, name, dtor_kind: "concrete" })
431 } else if #[allow(non_exhaustive_omitted_patterns)] match ty.kind() {
ty::Dynamic(..) => true,
_ => false,
}matches!(ty.kind(), ty::Dynamic(..)) {
432if seen_dyn {
433None
434} else {
435 seen_dyn = true;
436Some(DestructorLabel { span: DUMMY_SP, name, dtor_kind: "dyn" })
437 }
438 } else {
439None
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 }
451452let span = local_labels[0].span;
453 tcx.emit_node_span_lint(
454 TAIL_EXPR_DROP_ORDER,
455 lint_root.unwrap_or(CRATE_HIR_ID),
456 span,
457 TailExprDropOrderLint { local_labels, drop_span, _epilogue: () },
458 );
459 }
460}
461462/// Extract binding names if available for diagnosis
463fn collect_user_names(body: &Body<'_>) -> FxIndexMap<Local, Symbol> {
464let mut names = FxIndexMap::default();
465for var_debug_info in &body.var_debug_info {
466if let mir::VarDebugInfoContents::Place(place) = &var_debug_info.value
467 && let Some(local) = place.local_or_deref_local()
468 && !body.local_decls[local].from_compiler_desugaring()
469 {
470 names.entry(local).or_insert(var_debug_info.name);
471 }
472 }
473names474}
475476/// Assign names for anonymous or temporary values for diagnosis
477fn assign_observables_names(
478 locals: impl IntoIterator<Item = Local>,
479 user_names: &FxIndexMap<Local, Symbol>,
480) -> FxIndexMap<Local, (String, bool)> {
481let mut names = FxIndexMap::default();
482let mut assigned_names = FxHashSet::default();
483let mut idx = 0u64;
484let mut fresh_name = || {
485idx += 1;
486 (::alloc::__export::must_use({
::alloc::fmt::format(format_args!("#{0}", idx))
})format!("#{idx}"), true)
487 };
488for local in locals {
489let name = if let Some(name) = user_names.get(&local) {
490let name = name.as_str();
491if assigned_names.contains(name) { fresh_name() } else { (name.to_owned(), false) }
492 } else {
493 fresh_name()
494 };
495 assigned_names.insert(name.0.clone());
496 names.insert(local, name);
497 }
498names499}
500501#[derive(const _: () =
{
impl<'_sess, 'a> rustc_errors::Diagnostic<'_sess> for
TailExprDropOrderLint<'a> {
#[track_caller]
fn into_diag(self, dcx: rustc_errors::DiagCtxtHandle<'_sess>,
level: rustc_errors::Level) -> rustc_errors::Diag<'_sess> {
match self {
TailExprDropOrderLint {
local_labels: __binding_0,
drop_span: __binding_1,
_epilogue: __binding_2 } => {
let mut diag =
rustc_errors::Diag::new(dcx, level,
rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("relative drop order changing in Rust 2024")));
;
for __binding_0 in __binding_0 {
diag.subdiagnostic(__binding_0);
}
if let Some(__binding_1) = __binding_1 {
diag.span_label(__binding_1,
rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("now the temporary value is dropped here, before the local variables in the block or statement")));
}
diag.note(rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages")));
diag
}
}
}
}
};Diagnostic)]
502#[diag("relative drop order changing in Rust 2024")]
503struct TailExprDropOrderLint<'a> {
504#[subdiagnostic]
505local_labels: Vec<LocalLabel<'a>>,
506#[label(
507"now the temporary value is dropped here, before the local variables in the block or statement"
508)]
509drop_span: Option<Span>,
510#[note(
511"most of the time, changing drop order is harmless; inspect the `impl Drop`s for side effects like releasing locks or sending messages"
512)]
513_epilogue: (),
514}
515516struct LocalLabel<'a> {
517 span: Span,
518 name: &'a str,
519 is_generated_name: bool,
520 is_dropped_first_edition_2024: bool,
521 destructors: Vec<DestructorLabel<'a>>,
522}
523524/// A custom `Subdiagnostic` implementation so that the notes are delivered in a specific order
525impl Subdiagnosticfor LocalLabel<'_> {
526fn add_to_diag(self, diag: &mut rustc_errors::Diag<'_>) {
527diag.span_label(
528self.span,
529rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("{$is_generated_name ->\n [true] this value will be stored in a temporary; let us call it `{$name}`\n *[false] `{$name}` calls a custom destructor\n }"))msg!(
530"{$is_generated_name ->
531 [true] this value will be stored in a temporary; let us call it `{$name}`
532 *[false] `{$name}` calls a custom destructor
533 }"
534)535 .arg("name", self.name)
536 .arg("is_generated_name", self.is_generated_name)
537 .format(),
538 );
539for dtor in self.destructors {
540 dtor.add_to_diag(diag);
541 }
542diag.span_label(self.span, rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("{$is_dropped_first_edition_2024 ->\n [true] up until Edition 2021 `{$name}` is dropped last but will be dropped earlier in Edition 2024\n *[false] `{$name}` will be dropped later as of Edition 2024\n }"))msg!(
543"{$is_dropped_first_edition_2024 ->
544 [true] up until Edition 2021 `{$name}` is dropped last but will be dropped earlier in Edition 2024
545 *[false] `{$name}` will be dropped later as of Edition 2024
546 }"
547)548 .arg("is_dropped_first_edition_2024", self.is_dropped_first_edition_2024)
549 .arg("name", self.name)
550 .format());
551 }
552}
553554#[derive(const _: () =
{
impl<'a> rustc_errors::Subdiagnostic for DestructorLabel<'a> {
fn add_to_diag(self, diag: &mut rustc_errors::Diag<'_>) {
match self {
DestructorLabel {
span: __binding_0, dtor_kind: __binding_1, name: __binding_2
} => {
let mut sub_args = rustc_errors::DiagArgMap::default();
sub_args.insert("dtor_kind".into(),
rustc_errors::IntoDiagArg::into_diag_arg(__binding_1,
&mut diag.long_ty_path));
sub_args.insert("name".into(),
rustc_errors::IntoDiagArg::into_diag_arg(__binding_2,
&mut diag.long_ty_path));
let __message =
rustc_errors::format_diag_message(&rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("{$dtor_kind ->\n [dyn] `{$name}` may invoke a custom destructor because it contains a trait object\n *[concrete] `{$name}` invokes this custom destructor\n }")),
&sub_args);
diag.span_note(__binding_0, __message);
}
}
}
}
};Subdiagnostic)]
555#[note(
556"{$dtor_kind ->
557 [dyn] `{$name}` may invoke a custom destructor because it contains a trait object
558 *[concrete] `{$name}` invokes this custom destructor
559 }"
560)]
561struct DestructorLabel<'a> {
562#[primary_span]
563span: Span,
564 dtor_kind: &'static str,
565 name: &'a str,
566}