Skip to main content

rustc_mir_transform/
elaborate_drops.rs

1use std::fmt;
2
3use rustc_abi::{FieldIdx, VariantIdx};
4use rustc_index::IndexVec;
5use rustc_index::bit_set::DenseBitSet;
6use rustc_middle::mir::*;
7use rustc_middle::ty::{self, TyCtxt};
8use rustc_mir_dataflow::impls::{MaybeInitializedPlaces, MaybeUninitializedPlaces};
9use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex};
10use rustc_mir_dataflow::{
11    Analysis, DropFlagState, MoveDataTypingEnv, ResultsCursor, on_all_children_bits,
12    on_lookup_result_bits,
13};
14use rustc_span::Span;
15use tracing::{debug, instrument};
16
17use crate::elaborate_drop::{DropElaborator, DropFlagMode, DropStyle, Unwind, elaborate_drop};
18use crate::patch::MirPatch;
19
20/// During MIR building, Drop terminators are inserted in every place where a drop may occur.
21/// However, in this phase, the presence of these terminators does not guarantee that a destructor
22/// will run, as the target of the drop may be uninitialized.
23/// In general, the compiler cannot determine at compile time whether a destructor will run or not.
24///
25/// At a high level, this pass refines Drop to only run the destructor if the
26/// target is initialized. The way this is achieved is by inserting drop flags for every variable
27/// that may be dropped, and then using those flags to determine whether a destructor should run.
28/// Once this is complete, Drop terminators in the MIR correspond to a call to the "drop glue" or
29/// "drop shim" for the type of the dropped place.
30///
31/// This pass relies on dropped places having an associated move path, which is then used to
32/// determine the initialization status of the place and its descendants.
33/// It's worth noting that a MIR containing a Drop without an associated move path is probably ill
34/// formed, as it would allow running a destructor on a place behind a reference:
35///
36/// ```text
37/// fn drop_term<T>(t: &mut T) {
38///     mir! {
39///         {
40///             Drop(*t, exit)
41///         }
42///         exit = {
43///             Return()
44///         }
45///     }
46/// }
47/// ```
48pub(super) struct ElaborateDrops;
49
50impl<'tcx> crate::MirPass<'tcx> for ElaborateDrops {
51    #[instrument(level = "trace", skip(self, tcx, body))]
52    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
53        debug!("elaborate_drops({:?} @ {:?})", body.source, body.span);
54        // FIXME(#132279): This is used during the phase transition from analysis
55        // to runtime, so we have to manually specify the correct typing mode.
56        let typing_env = ty::TypingEnv::post_analysis(tcx, body.source.def_id());
57        // For types that do not need dropping, the behaviour is trivial. So we only need to track
58        // init/uninit for types that do need dropping.
59        let move_data = MoveData::gather_moves(body, tcx, |ty| ty.needs_drop(tcx, typing_env));
60        let elaborate_patch = {
61            let env = MoveDataTypingEnv { move_data, typing_env };
62
63            let mut inits = MaybeInitializedPlaces::new(tcx, body, &env.move_data)
64                .exclude_inactive_in_otherwise()
65                .skipping_unreachable_unwind()
66                .iterate_to_fixpoint(tcx, body, Some("elaborate_drops"))
67                .into_results_cursor(body);
68            let dead_unwinds = compute_dead_unwinds(body, &mut inits);
69
70            let uninits = MaybeUninitializedPlaces::new(tcx, body, &env.move_data)
71                .include_inactive_in_otherwise()
72                .mark_inactive_variants_as_uninit()
73                .skipping_unreachable_unwind(dead_unwinds)
74                .iterate_to_fixpoint(tcx, body, Some("elaborate_drops"))
75                .into_results_cursor(body);
76
77            let drop_flags = IndexVec::from_elem(None, &env.move_data.move_paths);
78            ElaborateDropsCtxt {
79                tcx,
80                body,
81                env: &env,
82                init_data: InitializationData { inits, uninits },
83                drop_flags,
84                patch: MirPatch::new(body),
85            }
86            .elaborate()
87        };
88        elaborate_patch.apply(body);
89    }
90
91    fn is_required(&self) -> bool {
92        true
93    }
94}
95
96/// Records unwind edges which are known to be unreachable, because they are in `drop` terminators
97/// that can't drop anything.
98#[instrument(level = "trace", skip(body, flow_inits), ret)]
99fn compute_dead_unwinds<'a, 'tcx>(
100    body: &'a Body<'tcx>,
101    flow_inits: &mut ResultsCursor<'a, 'tcx, MaybeInitializedPlaces<'a, 'tcx>>,
102) -> DenseBitSet<BasicBlock> {
103    // We only need to do this pass once, because unwind edges can only
104    // reach cleanup blocks, which can't have unwind edges themselves.
105    let mut dead_unwinds = DenseBitSet::new_empty(body.basic_blocks.len());
106    for (bb, bb_data) in body.basic_blocks.iter_enumerated() {
107        let TerminatorKind::Drop { place, unwind: UnwindAction::Cleanup(_), .. } =
108            bb_data.terminator().kind
109        else {
110            continue;
111        };
112
113        flow_inits.seek_before_primary_effect(body.terminator_loc(bb));
114        if flow_inits.analysis().is_unwind_dead(place, flow_inits.get()) {
115            dead_unwinds.insert(bb);
116        }
117    }
118
119    dead_unwinds
120}
121
122struct InitializationData<'a, 'tcx> {
123    inits: ResultsCursor<'a, 'tcx, MaybeInitializedPlaces<'a, 'tcx>>,
124    uninits: ResultsCursor<'a, 'tcx, MaybeUninitializedPlaces<'a, 'tcx>>,
125}
126
127impl InitializationData<'_, '_> {
128    fn seek_before(&mut self, loc: Location) {
129        self.inits.seek_before_primary_effect(loc);
130        self.uninits.seek_before_primary_effect(loc);
131    }
132
133    fn maybe_init_uninit(&self, path: MovePathIndex) -> (bool, bool) {
134        (self.inits.get().contains(path), self.uninits.get().contains(path))
135    }
136}
137
138impl<'a, 'tcx> DropElaborator<'a, 'tcx> for ElaborateDropsCtxt<'a, 'tcx> {
139    type Path = MovePathIndex;
140
141    fn patch_ref(&self) -> &MirPatch<'tcx> {
142        &self.patch
143    }
144
145    fn patch(&mut self) -> &mut MirPatch<'tcx> {
146        &mut self.patch
147    }
148
149    fn body(&self) -> &'a Body<'tcx> {
150        self.body
151    }
152
153    fn tcx(&self) -> TyCtxt<'tcx> {
154        self.tcx
155    }
156
157    fn typing_env(&self) -> ty::TypingEnv<'tcx> {
158        self.env.typing_env
159    }
160
161    fn allow_async_drops(&self) -> bool {
162        true
163    }
164
165    #[instrument(level = "debug", skip(self), ret)]
166    fn drop_style(&self, path: Self::Path, mode: DropFlagMode) -> DropStyle {
167        let ((maybe_init, maybe_uninit), multipart) = match mode {
168            DropFlagMode::Shallow => (self.init_data.maybe_init_uninit(path), false),
169            DropFlagMode::Deep => {
170                let mut some_maybe_init = false;
171                let mut some_maybe_uninit = false;
172                let mut children_count = 0;
173                on_all_children_bits(self.move_data(), path, |child| {
174                    let (maybe_init, maybe_uninit) = self.init_data.maybe_init_uninit(child);
175                    debug!("elaborate_drop: state({:?}) = {:?}", child, (maybe_init, maybe_uninit));
176                    some_maybe_init |= maybe_init;
177                    some_maybe_uninit |= maybe_uninit;
178                    children_count += 1;
179                });
180                ((some_maybe_init, some_maybe_uninit), children_count != 1)
181            }
182        };
183        match (maybe_init, maybe_uninit, multipart) {
184            (false, _, _) => DropStyle::Dead,
185            (true, false, _) => DropStyle::Static,
186            (true, true, false) => DropStyle::Conditional,
187            (true, true, true) => DropStyle::Open,
188        }
189    }
190
191    fn clear_drop_flag(&mut self, loc: Location, path: Self::Path, mode: DropFlagMode) {
192        match mode {
193            DropFlagMode::Shallow => {
194                self.set_drop_flag(loc, path, DropFlagState::Absent);
195            }
196            DropFlagMode::Deep => {
197                on_all_children_bits(self.move_data(), path, |child| {
198                    self.set_drop_flag(loc, child, DropFlagState::Absent)
199                });
200            }
201        }
202    }
203
204    fn field_subpath(&self, path: Self::Path, field: FieldIdx) -> Option<Self::Path> {
205        rustc_mir_dataflow::move_path_children_matching(self.move_data(), path, |e| match e {
206            ProjectionElem::Field(idx, _) => idx == field,
207            _ => false,
208        })
209    }
210
211    fn array_subpath(&self, path: Self::Path, index: u64, size: u64) -> Option<Self::Path> {
212        rustc_mir_dataflow::move_path_children_matching(self.move_data(), path, |e| match e {
213            ProjectionElem::ConstantIndex { offset, min_length, from_end } => {
214                debug_assert!(size == min_length, "min_length should be exact for arrays");
215                assert!(!from_end, "from_end should not be used for array element ConstantIndex");
216                offset == index
217            }
218            _ => false,
219        })
220    }
221
222    fn deref_subpath(&self, path: Self::Path) -> Option<Self::Path> {
223        rustc_mir_dataflow::move_path_children_matching(self.move_data(), path, |e| {
224            e == ProjectionElem::Deref
225        })
226    }
227
228    fn downcast_subpath(&self, path: Self::Path, variant: VariantIdx) -> Option<Self::Path> {
229        rustc_mir_dataflow::move_path_children_matching(self.move_data(), path, |e| match e {
230            ProjectionElem::Downcast(_, idx) => idx == variant,
231            _ => false,
232        })
233    }
234
235    fn get_drop_flag(&mut self, path: Self::Path) -> Option<Operand<'tcx>> {
236        self.drop_flag(path).map(Operand::Copy)
237    }
238}
239
240struct ElaborateDropsCtxt<'a, 'tcx> {
241    tcx: TyCtxt<'tcx>,
242    body: &'a Body<'tcx>,
243    env: &'a MoveDataTypingEnv<'tcx>,
244    init_data: InitializationData<'a, 'tcx>,
245    drop_flags: IndexVec<MovePathIndex, Option<Local>>,
246    patch: MirPatch<'tcx>,
247}
248
249impl fmt::Debug for ElaborateDropsCtxt<'_, '_> {
250    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
251        f.debug_struct("ElaborateDropsCtxt").finish_non_exhaustive()
252    }
253}
254
255impl<'a, 'tcx> ElaborateDropsCtxt<'a, 'tcx> {
256    fn move_data(&self) -> &'a MoveData<'tcx> {
257        &self.env.move_data
258    }
259
260    fn create_drop_flag(&mut self, index: MovePathIndex, span: Span) {
261        let patch = &mut self.patch;
262        debug!("create_drop_flag({:?})", self.body.span);
263        self.drop_flags[index].get_or_insert_with(|| patch.new_temp(self.tcx.types.bool, span));
264    }
265
266    fn drop_flag(&mut self, index: MovePathIndex) -> Option<Place<'tcx>> {
267        self.drop_flags[index].map(Place::from)
268    }
269
270    /// create a patch that elaborates all drops in the input
271    /// MIR.
272    fn elaborate(mut self) -> MirPatch<'tcx> {
273        self.collect_drop_flags();
274
275        self.elaborate_drops();
276
277        self.drop_flags_on_init();
278        self.drop_flags_for_fn_rets();
279        self.drop_flags_for_args();
280        self.drop_flags_for_locs();
281
282        self.patch
283    }
284
285    fn collect_drop_flags(&mut self) {
286        for (bb, data) in self.body.basic_blocks.iter_enumerated() {
287            let terminator = data.terminator();
288            let TerminatorKind::Drop { ref place, .. } = terminator.kind else { continue };
289
290            let path = self.move_data().rev_lookup.find(place.as_ref());
291            debug!("collect_drop_flags: {:?}, place {:?} ({:?})", bb, place, path);
292
293            match path {
294                LookupResult::Exact(path) => {
295                    self.init_data.seek_before(self.body.terminator_loc(bb));
296                    on_all_children_bits(self.move_data(), path, |child| {
297                        let (maybe_init, maybe_uninit) = self.init_data.maybe_init_uninit(child);
298                        debug!(
299                            "collect_drop_flags: collecting {:?} from {:?}@{:?} - {:?}",
300                            child,
301                            place,
302                            path,
303                            (maybe_init, maybe_uninit)
304                        );
305                        if maybe_init && maybe_uninit {
306                            self.create_drop_flag(child, terminator.source_info.span)
307                        }
308                    });
309                }
310                LookupResult::Parent(None) => {}
311                LookupResult::Parent(Some(parent)) => {
312                    if self.body.local_decls[place.local].is_deref_temp() {
313                        continue;
314                    }
315
316                    self.init_data.seek_before(self.body.terminator_loc(bb));
317                    let (_maybe_init, maybe_uninit) = self.init_data.maybe_init_uninit(parent);
318                    if maybe_uninit {
319                        self.tcx.dcx().span_delayed_bug(
320                            terminator.source_info.span,
321                            format!(
322                                "drop of untracked, uninitialized value {bb:?}, place {place:?} ({path:?})"
323                            ),
324                        );
325                    }
326                }
327            };
328        }
329    }
330
331    fn elaborate_drops(&mut self) {
332        // This function should mirror what `collect_drop_flags` does.
333        for (bb, data) in self.body.basic_blocks.iter_enumerated() {
334            let terminator = data.terminator();
335            let TerminatorKind::Drop { place, target, unwind, replace, drop } = terminator.kind
336            else {
337                continue;
338            };
339
340            // This place does not need dropping. It does not have an associated move-path, so the
341            // match below will conservatively keep an unconditional drop. As that drop is useless,
342            // just remove it here and now.
343            if !place
344                .ty(&self.body.local_decls, self.tcx)
345                .ty
346                .needs_drop(self.tcx, self.typing_env())
347            {
348                self.patch.patch_terminator(bb, TerminatorKind::Goto { target });
349                continue;
350            }
351
352            let path = self.move_data().rev_lookup.find(place.as_ref());
353            match path {
354                LookupResult::Exact(path) => {
355                    let unwind = match unwind {
356                        _ if data.is_cleanup => Unwind::InCleanup,
357                        UnwindAction::Cleanup(cleanup) => Unwind::To(cleanup),
358                        UnwindAction::Continue => Unwind::To(self.patch.resume_block()),
359                        UnwindAction::Unreachable => {
360                            Unwind::To(self.patch.unreachable_cleanup_block())
361                        }
362                        UnwindAction::Terminate(reason) => {
363                            debug_assert_ne!(
364                                reason,
365                                UnwindTerminateReason::InCleanup,
366                                "we are not in a cleanup block, InCleanup reason should be impossible"
367                            );
368                            Unwind::To(self.patch.terminate_block(reason))
369                        }
370                    };
371                    self.init_data.seek_before(self.body.terminator_loc(bb));
372                    elaborate_drop(
373                        self,
374                        terminator.source_info,
375                        place,
376                        path,
377                        target,
378                        unwind,
379                        bb,
380                        drop,
381                    )
382                }
383                LookupResult::Parent(None) => {}
384                LookupResult::Parent(Some(_)) => {
385                    if !replace {
386                        self.tcx.dcx().span_bug(
387                            terminator.source_info.span,
388                            format!("drop of untracked value {bb:?}"),
389                        );
390                    }
391                    // A drop and replace behind a pointer/array/whatever.
392                    // The borrow checker requires that these locations are initialized before the
393                    // assignment, so we just leave an unconditional drop.
394                    assert!(!data.is_cleanup);
395                }
396            }
397        }
398    }
399
400    fn constant_bool(&self, span: Span, val: bool) -> Rvalue<'tcx> {
401        Rvalue::Use(
402            Operand::Constant(Box::new(ConstOperand {
403                span,
404                user_ty: None,
405                const_: Const::from_bool(self.tcx, val),
406            })),
407            WithRetag::Yes,
408        )
409    }
410
411    fn set_drop_flag(&mut self, loc: Location, path: MovePathIndex, val: DropFlagState) {
412        if let Some(flag) = self.drop_flags[path] {
413            let span = self.patch.source_info_for_location(self.body, loc).span;
414            let val = self.constant_bool(span, val.value());
415            self.patch.add_assign(loc, Place::from(flag), val);
416        }
417    }
418
419    fn drop_flags_on_init(&mut self) {
420        let loc = Location::START;
421        let span = self.patch.source_info_for_location(self.body, loc).span;
422        let false_ = self.constant_bool(span, false);
423        for flag in self.drop_flags.iter().flatten() {
424            self.patch.add_assign(loc, Place::from(*flag), false_.clone());
425        }
426    }
427
428    fn drop_flags_for_fn_rets(&mut self) {
429        for (bb, data) in self.body.basic_blocks.iter_enumerated() {
430            if let TerminatorKind::Call {
431                destination,
432                target: Some(tgt),
433                unwind: UnwindAction::Cleanup(_),
434                ..
435            } = data.terminator().kind
436            {
437                assert!(!self.patch.is_term_patched(bb));
438
439                let loc = Location { block: tgt, statement_index: 0 };
440                let path = self.move_data().rev_lookup.find(destination.as_ref());
441                on_lookup_result_bits(self.move_data(), path, |child| {
442                    self.set_drop_flag(loc, child, DropFlagState::Present)
443                });
444            }
445        }
446    }
447
448    fn drop_flags_for_args(&mut self) {
449        let loc = Location::START;
450        rustc_mir_dataflow::drop_flag_effects_for_function_entry(
451            self.body,
452            &self.env.move_data,
453            |path, ds| {
454                self.set_drop_flag(loc, path, ds);
455            },
456        )
457    }
458
459    fn drop_flags_for_locs(&mut self) {
460        // We intentionally iterate only over the *old* basic blocks.
461        //
462        // Basic blocks created by drop elaboration update their
463        // drop flags by themselves, to avoid the drop flags being
464        // clobbered before they are read.
465
466        for (bb, data) in self.body.basic_blocks.iter_enumerated() {
467            debug!("drop_flags_for_locs({:?})", data);
468            for i in 0..(data.statements.len() + 1) {
469                debug!("drop_flag_for_locs: stmt {}", i);
470                if i == data.statements.len() {
471                    match data.terminator().kind {
472                        TerminatorKind::Drop { .. } => {
473                            // drop elaboration should handle that by itself
474                            continue;
475                        }
476                        TerminatorKind::UnwindResume => {
477                            // It is possible for `Resume` to be patched
478                            // (in particular it can be patched to be replaced with
479                            // a Goto; see `MirPatch::new`).
480                        }
481                        _ => {
482                            assert!(!self.patch.is_term_patched(bb));
483                        }
484                    }
485                }
486                let loc = Location { block: bb, statement_index: i };
487                rustc_mir_dataflow::drop_flag_effects_for_location(
488                    self.body,
489                    &self.env.move_data,
490                    loc,
491                    |path, ds| self.set_drop_flag(loc, path, ds),
492                )
493            }
494
495            // There may be a critical edge after this call,
496            // so mark the return as initialized *before* the
497            // call.
498            if let TerminatorKind::Call {
499                destination,
500                target: Some(_),
501                unwind:
502                    UnwindAction::Continue | UnwindAction::Unreachable | UnwindAction::Terminate(_),
503                ..
504            } = data.terminator().kind
505            {
506                assert!(!self.patch.is_term_patched(bb));
507
508                let loc = Location { block: bb, statement_index: data.statements.len() };
509                let path = self.move_data().rev_lookup.find(destination.as_ref());
510                on_lookup_result_bits(self.move_data(), path, |child| {
511                    self.set_drop_flag(loc, child, DropFlagState::Present)
512                });
513            }
514        }
515    }
516}