Skip to main content

rustc_mir_transform/
remove_uninit_drops.rs

1use rustc_abi::FieldIdx;
2use rustc_index::bit_set::MixedBitSet;
3use rustc_middle::mir::{Body, TerminatorKind};
4use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt, VariantDef};
5use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
6use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex};
7use rustc_mir_dataflow::{Analysis, MaybeReachable, move_path_children_matching};
8
9use crate::PassPolicy;
10
11/// Removes `Drop` terminators whose target is known to be uninitialized at
12/// that point.
13///
14/// This is redundant with drop elaboration, but we need to do it prior to const-checking, and
15/// running const-checking after drop elaboration makes it optimization dependent, causing issues
16/// like [#90770].
17///
18/// [#90770]: https://github.com/rust-lang/rust/issues/90770
19pub(super) struct RemoveUninitDrops;
20
21impl<'tcx> crate::MirPass<'tcx> for RemoveUninitDrops {
22    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
23        let typing_env = body.typing_env(tcx);
24        let move_data = MoveData::gather_moves(body, tcx, |ty| ty.needs_drop(tcx, typing_env));
25
26        let mut maybe_inits = MaybeInitializedPlaces::new(tcx, body, &move_data)
27            .exclude_inactive_in_otherwise()
28            .iterate_to_fixpoint(tcx, body, Some("remove_uninit_drops"))
29            .into_results_cursor(body);
30
31        let mut to_remove = vec![];
32        for (bb, block) in body.basic_blocks.iter_enumerated() {
33            let terminator = block.terminator();
34            let TerminatorKind::Drop { place, .. } = &terminator.kind else { continue };
35
36            maybe_inits.seek_before_primary_effect(body.terminator_loc(bb));
37            let MaybeReachable::Reachable(maybe_inits) = maybe_inits.get() else { continue };
38
39            // If there's no move path for the dropped place, it's probably a `Deref`. Let it alone.
40            let LookupResult::Exact(mpi) = move_data.rev_lookup.find(place.as_ref()) else {
41                continue;
42            };
43
44            let should_keep = is_needs_drop_and_init(
45                tcx,
46                typing_env,
47                maybe_inits,
48                &move_data,
49                place.ty(body, tcx).ty,
50                mpi,
51            );
52            if !should_keep {
53                to_remove.push(bb)
54            }
55        }
56
57        for bb in to_remove {
58            let block = &mut body.basic_blocks_mut()[bb];
59
60            let TerminatorKind::Drop { target, .. } = &block.terminator().kind else {
61                unreachable!()
62            };
63
64            // Replace block terminator with `Goto`.
65            block.terminator_mut().kind = TerminatorKind::Goto { target: *target };
66        }
67    }
68
69    fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy {
70        // Const checking relies on uninitialized drops being removed before drop elaboration.
71        PassPolicy::Required
72    }
73}
74
75fn is_needs_drop_and_init<'tcx>(
76    tcx: TyCtxt<'tcx>,
77    typing_env: ty::TypingEnv<'tcx>,
78    maybe_inits: &MixedBitSet<MovePathIndex>,
79    move_data: &MoveData<'tcx>,
80    ty: Ty<'tcx>,
81    mpi: MovePathIndex,
82) -> bool {
83    // No need to look deeper if the root is definitely uninit or if it has no `Drop` impl.
84    if !maybe_inits.contains(mpi) || !ty.needs_drop(tcx, typing_env) {
85        return false;
86    }
87
88    let field_needs_drop_and_init = |(f, f_ty, mpi)| {
89        let child = move_path_children_matching(move_data, mpi, |x| x.is_field_to(f));
90        let Some(mpi) = child else {
91            return Ty::needs_drop(f_ty, tcx, typing_env);
92        };
93
94        is_needs_drop_and_init(tcx, typing_env, maybe_inits, move_data, f_ty, mpi)
95    };
96
97    // This pass is only needed for const-checking, so it doesn't handle as many cases as
98    // `DropCtxt::open_drop`, since they aren't relevant in a const-context.
99    match ty.kind() {
100        ty::Adt(adt, args) => {
101            let dont_elaborate = adt.is_union() || adt.is_manually_drop() || adt.has_dtor(tcx);
102            if dont_elaborate {
103                return true;
104            }
105
106            // Look at all our fields, or if we are an enum all our variants and their fields.
107            //
108            // If a field's projection *is not* present in `MoveData`, it has the same
109            // initializedness as its parent (maybe init).
110            //
111            // If its projection *is* present in `MoveData`, then the field may have been moved
112            // from separate from its parent. Recurse.
113            adt.variants().iter_enumerated().any(|(vid, variant)| {
114                // Enums have multiple variants, which are discriminated with a `Downcast`
115                // projection. Structs have a single variant, and don't use a `Downcast`
116                // projection.
117                let mpi = if adt.is_enum() {
118                    let downcast =
119                        move_path_children_matching(move_data, mpi, |x| x.is_downcast_to(vid));
120                    let Some(dc_mpi) = downcast else {
121                        return variant_needs_drop(tcx, typing_env, args, variant);
122                    };
123
124                    dc_mpi
125                } else {
126                    mpi
127                };
128
129                variant
130                    .fields
131                    .iter()
132                    .enumerate()
133                    .map(|(f, field)| {
134                        (FieldIdx::from_usize(f), field.ty(tcx, args).skip_norm_wip(), mpi)
135                    })
136                    .any(field_needs_drop_and_init)
137            })
138        }
139
140        ty::Tuple(fields) => fields
141            .iter()
142            .enumerate()
143            .map(|(f, f_ty)| (FieldIdx::from_usize(f), f_ty, mpi))
144            .any(field_needs_drop_and_init),
145
146        _ => true,
147    }
148}
149
150fn variant_needs_drop<'tcx>(
151    tcx: TyCtxt<'tcx>,
152    typing_env: ty::TypingEnv<'tcx>,
153    args: GenericArgsRef<'tcx>,
154    variant: &VariantDef,
155) -> bool {
156    variant.fields.iter().any(|field| {
157        let f_ty = field.ty(tcx, args).skip_norm_wip();
158        f_ty.needs_drop(tcx, typing_env)
159    })
160}