rustc_mir_transform/
elaborate_box_derefs.rs

1//! This pass transforms derefs of Box into a deref of the pointer inside Box.
2//!
3//! Box is not actually a pointer so it is incorrect to dereference it directly.
4
5use rustc_abi::FieldIdx;
6use rustc_hir::def_id::DefId;
7use rustc_middle::mir::visit::MutVisitor;
8use rustc_middle::mir::*;
9use rustc_middle::span_bug;
10use rustc_middle::ty::{Ty, TyCtxt};
11
12use crate::patch::MirPatch;
13
14/// Constructs the types used when accessing a Box's pointer
15fn build_ptr_tys<'tcx>(
16    tcx: TyCtxt<'tcx>,
17    pointee: Ty<'tcx>,
18    unique_did: DefId,
19    nonnull_did: DefId,
20) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>) {
21    let args = tcx.mk_args(&[pointee.into()]);
22    let unique_ty = tcx.type_of(unique_did).instantiate(tcx, args);
23    let nonnull_ty = tcx.type_of(nonnull_did).instantiate(tcx, args);
24    let ptr_ty = Ty::new_imm_ptr(tcx, pointee);
25
26    (unique_ty, nonnull_ty, ptr_ty)
27}
28
29/// Constructs the projection needed to access a Box's pointer
30pub(super) fn build_projection<'tcx>(
31    unique_ty: Ty<'tcx>,
32    nonnull_ty: Ty<'tcx>,
33) -> [PlaceElem<'tcx>; 2] {
34    [PlaceElem::Field(FieldIdx::ZERO, unique_ty), PlaceElem::Field(FieldIdx::ZERO, nonnull_ty)]
35}
36
37struct ElaborateBoxDerefVisitor<'a, 'tcx> {
38    tcx: TyCtxt<'tcx>,
39    unique_did: DefId,
40    nonnull_did: DefId,
41    local_decls: &'a mut LocalDecls<'tcx>,
42    patch: MirPatch<'tcx>,
43}
44
45impl<'a, 'tcx> MutVisitor<'tcx> for ElaborateBoxDerefVisitor<'a, 'tcx> {
46    fn tcx(&self) -> TyCtxt<'tcx> {
47        self.tcx
48    }
49
50    fn visit_place(
51        &mut self,
52        place: &mut Place<'tcx>,
53        context: visit::PlaceContext,
54        location: Location,
55    ) {
56        let tcx = self.tcx;
57
58        let base_ty = self.local_decls[place.local].ty;
59
60        // Derefer ensures that derefs are always the first projection
61        if let Some(PlaceElem::Deref) = place.projection.first()
62            && let Some(boxed_ty) = base_ty.boxed_ty()
63        {
64            let source_info = self.local_decls[place.local].source_info;
65
66            let (unique_ty, nonnull_ty, ptr_ty) =
67                build_ptr_tys(tcx, boxed_ty, self.unique_did, self.nonnull_did);
68
69            let ptr_local = self.patch.new_temp(ptr_ty, source_info.span);
70
71            self.patch.add_assign(
72                location,
73                Place::from(ptr_local),
74                Rvalue::Cast(
75                    CastKind::Transmute,
76                    Operand::Copy(
77                        Place::from(place.local)
78                            .project_deeper(&build_projection(unique_ty, nonnull_ty), tcx),
79                    ),
80                    ptr_ty,
81                ),
82            );
83
84            place.local = ptr_local;
85        }
86
87        self.super_place(place, context, location);
88    }
89}
90
91pub(super) struct ElaborateBoxDerefs;
92
93impl<'tcx> crate::MirPass<'tcx> for ElaborateBoxDerefs {
94    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
95        // If box is not present, this pass doesn't need to do anything.
96        let Some(def_id) = tcx.lang_items().owned_box() else { return };
97
98        let unique_did = tcx.adt_def(def_id).non_enum_variant().fields[FieldIdx::ZERO].did;
99
100        let Some(nonnull_def) = tcx.type_of(unique_did).instantiate_identity().ty_adt_def() else {
101            span_bug!(tcx.def_span(unique_did), "expected Box to contain Unique")
102        };
103
104        let nonnull_did = nonnull_def.non_enum_variant().fields[FieldIdx::ZERO].did;
105
106        let patch = MirPatch::new(body);
107
108        let local_decls = &mut body.local_decls;
109
110        let mut visitor =
111            ElaborateBoxDerefVisitor { tcx, unique_did, nonnull_did, local_decls, patch };
112
113        for (block, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
114            visitor.visit_basic_block_data(block, data);
115        }
116
117        visitor.patch.apply(body);
118
119        for debug_info in body.var_debug_info.iter_mut() {
120            if let VarDebugInfoContents::Place(place) = &mut debug_info.value {
121                let mut new_projections: Option<Vec<_>> = None;
122
123                for (base, elem) in place.iter_projections() {
124                    let base_ty = base.ty(&body.local_decls, tcx).ty;
125
126                    if let PlaceElem::Deref = elem
127                        && let Some(boxed_ty) = base_ty.boxed_ty()
128                    {
129                        // Clone the projections before us, since now we need to mutate them.
130                        let new_projections =
131                            new_projections.get_or_insert_with(|| base.projection.to_vec());
132
133                        let (unique_ty, nonnull_ty, ptr_ty) =
134                            build_ptr_tys(tcx, boxed_ty, unique_did, nonnull_did);
135
136                        new_projections.extend_from_slice(&build_projection(unique_ty, nonnull_ty));
137                        // While we can't project into `NonNull<_>` in a basic block
138                        // due to MCP#807, this is debug info where it's fine.
139                        new_projections.push(PlaceElem::Field(FieldIdx::ZERO, ptr_ty));
140                        new_projections.push(PlaceElem::Deref);
141                    } else if let Some(new_projections) = new_projections.as_mut() {
142                        // Keep building up our projections list once we've started it.
143                        new_projections.push(elem);
144                    }
145                }
146
147                // Store the mutated projections if we actually changed something.
148                if let Some(new_projections) = new_projections {
149                    place.projection = tcx.mk_place_elems(&new_projections);
150                }
151            }
152        }
153    }
154
155    fn is_required(&self) -> bool {
156        true
157    }
158}