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, PatternKind, Ty, TyCtxt};
10
11use crate::PassPolicy;
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_def: ty::AdtDef<'tcx>,
19    nonnull_def: ty::AdtDef<'tcx>,
20) -> (Ty<'tcx>, Ty<'tcx>, Ty<'tcx>) {
21    let args = tcx.mk_args(&[pointee.into()]);
22    let unique_ty = Ty::new_adt(tcx, unique_def, args);
23    let nonnull_ty = Ty::new_adt(tcx, nonnull_def, 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_def: ty::AdtDef<'tcx>,
40    nonnull_def: ty::AdtDef<'tcx>,
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_def, self.nonnull_def);
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::BoxDerefTransmute,
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(unique_def) =
101            tcx.type_of(unique_did).instantiate_identity().skip_norm_wip().ty_adt_def()
102        else {
103            span_bug!(tcx.def_span(unique_did), "expected Box to contain Unique")
104        };
105
106        let nonnull_did = unique_def.non_enum_variant().fields[FieldIdx::ZERO].did;
107
108        let Some(nonnull_def) =
109            tcx.type_of(nonnull_did).instantiate_identity().skip_norm_wip().ty_adt_def()
110        else {
111            span_bug!(tcx.def_span(nonnull_did), "expected Unique to contain Nonnull")
112        };
113
114        let patch = MirPatch::new(body);
115
116        let local_decls = &mut body.local_decls;
117
118        let mut visitor =
119            ElaborateBoxDerefVisitor { tcx, unique_def, nonnull_def, local_decls, patch };
120
121        for (block, data) in body.basic_blocks.as_mut_preserves_cfg().iter_enumerated_mut() {
122            visitor.visit_basic_block_data(block, data);
123        }
124
125        visitor.patch.apply(body);
126
127        for debug_info in body.var_debug_info.iter_mut() {
128            if let VarDebugInfoContents::Place(place) = &mut debug_info.value {
129                let mut new_projections: Option<Vec<_>> = None;
130
131                for (base, elem) in place.iter_projections() {
132                    let base_ty = base.ty(&body.local_decls, tcx).ty;
133
134                    if let PlaceElem::Deref = elem
135                        && let Some(boxed_ty) = base_ty.boxed_ty()
136                    {
137                        // Clone the projections before us, since now we need to mutate them.
138                        let new_projections =
139                            new_projections.get_or_insert_with(|| base.projection.to_vec());
140
141                        let (unique_ty, nonnull_ty, ptr_ty) =
142                            build_ptr_tys(tcx, boxed_ty, unique_def, nonnull_def);
143
144                        new_projections.extend_from_slice(&build_projection(unique_ty, nonnull_ty));
145                        // While we can't project into a pattern type in a basic block,
146                        // this is debug info where it's fine.
147                        let pat_ty = Ty::new_pat(tcx, ptr_ty, tcx.mk_pat(PatternKind::NotNull));
148                        new_projections.push(PlaceElem::Field(FieldIdx::ZERO, pat_ty));
149                        new_projections.push(PlaceElem::Field(FieldIdx::ZERO, ptr_ty));
150                        new_projections.push(PlaceElem::Deref);
151                    } else if let Some(new_projections) = new_projections.as_mut() {
152                        // Keep building up our projections list once we've started it.
153                        new_projections.push(elem);
154                    }
155                }
156
157                // Store the mutated projections if we actually changed something.
158                if let Some(new_projections) = new_projections {
159                    place.projection = tcx.mk_place_elems(&new_projections);
160                }
161            }
162        }
163    }
164
165    fn policy(&self, _sess: &rustc_session::Session) -> PassPolicy {
166        // Implements Box dereference semantics so backends and Miri do not have to handle them.
167        PassPolicy::Required
168    }
169}