Skip to main content

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