]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_mir_transform/src/add_retag.rs
Make MIR basic blocks field public
[rust.git] / compiler / rustc_mir_transform / src / add_retag.rs
1 //! This pass adds validation calls (AcquireValid, ReleaseValid) where appropriate.
2 //! It has to be run really early, before transformations like inlining, because
3 //! introducing these calls *adds* UB -- so, conceptually, this pass is actually part
4 //! of MIR building, and only after this pass we think of the program has having the
5 //! normal MIR semantics.
6
7 use crate::MirPass;
8 use rustc_middle::mir::*;
9 use rustc_middle::ty::{self, Ty, TyCtxt};
10
11 pub struct AddRetag;
12
13 /// Determines whether this place is "stable": Whether, if we evaluate it again
14 /// after the assignment, we can be sure to obtain the same place value.
15 /// (Concurrent accesses by other threads are no problem as these are anyway non-atomic
16 /// copies.  Data races are UB.)
17 fn is_stable(place: PlaceRef<'_>) -> bool {
18     place.projection.iter().all(|elem| {
19         match elem {
20             // Which place this evaluates to can change with any memory write,
21             // so cannot assume this to be stable.
22             ProjectionElem::Deref => false,
23             // Array indices are interesting, but MIR building generates a *fresh*
24             // temporary for every array access, so the index cannot be changed as
25             // a side-effect.
26             ProjectionElem::Index { .. } |
27             // The rest is completely boring, they just offset by a constant.
28             ProjectionElem::Field { .. } |
29             ProjectionElem::ConstantIndex { .. } |
30             ProjectionElem::Subslice { .. } |
31             ProjectionElem::Downcast { .. } => true,
32         }
33     })
34 }
35
36 /// Determine whether this type may contain a reference (or box), and thus needs retagging.
37 /// We will only recurse `depth` times into Tuples/ADTs to bound the cost of this.
38 fn may_contain_reference<'tcx>(ty: Ty<'tcx>, depth: u32, tcx: TyCtxt<'tcx>) -> bool {
39     match ty.kind() {
40         // Primitive types that are not references
41         ty::Bool
42         | ty::Char
43         | ty::Float(_)
44         | ty::Int(_)
45         | ty::Uint(_)
46         | ty::RawPtr(..)
47         | ty::FnPtr(..)
48         | ty::Str
49         | ty::FnDef(..)
50         | ty::Never => false,
51         // References
52         ty::Ref(..) => true,
53         ty::Adt(..) if ty.is_box() => true,
54         // Compound types: recurse
55         ty::Array(ty, _) | ty::Slice(ty) => {
56             // This does not branch so we keep the depth the same.
57             may_contain_reference(*ty, depth, tcx)
58         }
59         ty::Tuple(tys) => {
60             depth == 0 || tys.iter().any(|ty| may_contain_reference(ty, depth - 1, tcx))
61         }
62         ty::Adt(adt, subst) => {
63             depth == 0
64                 || adt.variants().iter().any(|v| {
65                     v.fields.iter().any(|f| may_contain_reference(f.ty(tcx, subst), depth - 1, tcx))
66                 })
67         }
68         // Conservative fallback
69         _ => true,
70     }
71 }
72
73 /// Determines whether or not this LocalDecl is temp, if not it needs retagging.
74 fn is_not_temp<'tcx>(local_decl: &LocalDecl<'tcx>) -> bool {
75     if let Some(local_info) = &local_decl.local_info {
76         match local_info.as_ref() {
77             LocalInfo::DerefTemp => return false,
78             _ => (),
79         };
80     }
81     return true;
82 }
83
84 impl<'tcx> MirPass<'tcx> for AddRetag {
85     fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
86         sess.opts.debugging_opts.mir_emit_retag
87     }
88
89     fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
90         // We need an `AllCallEdges` pass before we can do any work.
91         super::add_call_guards::AllCallEdges.run_pass(tcx, body);
92
93         let (span, arg_count) = (body.span, body.arg_count);
94         let basic_blocks = body.basic_blocks.as_mut();
95         let local_decls = &body.local_decls;
96         let needs_retag = |place: &Place<'tcx>| {
97             // FIXME: Instead of giving up for unstable places, we should introduce
98             // a temporary and retag on that.
99             is_stable(place.as_ref())
100                 && may_contain_reference(place.ty(&*local_decls, tcx).ty, /*depth*/ 3, tcx)
101                 && is_not_temp(&local_decls[place.local])
102         };
103         let place_base_raw = |place: &Place<'tcx>| {
104             // If this is a `Deref`, get the type of what we are deref'ing.
105             let deref_base =
106                 place.projection.iter().rposition(|p| matches!(p, ProjectionElem::Deref));
107             if let Some(deref_base) = deref_base {
108                 let base_proj = &place.projection[..deref_base];
109                 let ty = Place::ty_from(place.local, base_proj, &*local_decls, tcx).ty;
110                 ty.is_unsafe_ptr()
111             } else {
112                 // Not a deref, and thus not raw.
113                 false
114             }
115         };
116
117         // PART 1
118         // Retag arguments at the beginning of the start block.
119         {
120             // FIXME: Consider using just the span covering the function
121             // argument declaration.
122             let source_info = SourceInfo::outermost(span);
123             // Gather all arguments, skip return value.
124             let places = local_decls
125                 .iter_enumerated()
126                 .skip(1)
127                 .take(arg_count)
128                 .map(|(local, _)| Place::from(local))
129                 .filter(needs_retag);
130             // Emit their retags.
131             basic_blocks[START_BLOCK].statements.splice(
132                 0..0,
133                 places.map(|place| Statement {
134                     source_info,
135                     kind: StatementKind::Retag(RetagKind::FnEntry, Box::new(place)),
136                 }),
137             );
138         }
139
140         // PART 2
141         // Retag return values of functions.  Also escape-to-raw the argument of `drop`.
142         // We collect the return destinations because we cannot mutate while iterating.
143         let returns = basic_blocks
144             .iter_mut()
145             .filter_map(|block_data| {
146                 match block_data.terminator().kind {
147                     TerminatorKind::Call { target: Some(target), destination, .. }
148                         if needs_retag(&destination) =>
149                     {
150                         // Remember the return destination for later
151                         Some((block_data.terminator().source_info, destination, target))
152                     }
153
154                     // `Drop` is also a call, but it doesn't return anything so we are good.
155                     TerminatorKind::Drop { .. } | TerminatorKind::DropAndReplace { .. } => None,
156                     // Not a block ending in a Call -> ignore.
157                     _ => None,
158                 }
159             })
160             .collect::<Vec<_>>();
161         // Now we go over the returns we collected to retag the return values.
162         for (source_info, dest_place, dest_block) in returns {
163             basic_blocks[dest_block].statements.insert(
164                 0,
165                 Statement {
166                     source_info,
167                     kind: StatementKind::Retag(RetagKind::Default, Box::new(dest_place)),
168                 },
169             );
170         }
171
172         // PART 3
173         // Add retag after assignment.
174         for block_data in basic_blocks {
175             // We want to insert statements as we iterate.  To this end, we
176             // iterate backwards using indices.
177             for i in (0..block_data.statements.len()).rev() {
178                 let (retag_kind, place) = match block_data.statements[i].kind {
179                     // Retag-as-raw after escaping to a raw pointer, if the referent
180                     // is not already a raw pointer.
181                     StatementKind::Assign(box (lplace, Rvalue::AddressOf(_, ref rplace)))
182                         if !place_base_raw(rplace) =>
183                     {
184                         (RetagKind::Raw, lplace)
185                     }
186                     // Retag after assignments of reference type.
187                     StatementKind::Assign(box (ref place, ref rvalue)) if needs_retag(place) => {
188                         let kind = match rvalue {
189                             Rvalue::Ref(_, borrow_kind, _)
190                                 if borrow_kind.allows_two_phase_borrow() =>
191                             {
192                                 RetagKind::TwoPhase
193                             }
194                             _ => RetagKind::Default,
195                         };
196                         (kind, *place)
197                     }
198                     // Do nothing for the rest
199                     _ => continue,
200                 };
201                 // Insert a retag after the statement.
202                 let source_info = block_data.statements[i].source_info;
203                 block_data.statements.insert(
204                     i + 1,
205                     Statement {
206                         source_info,
207                         kind: StatementKind::Retag(retag_kind, Box::new(place)),
208                     },
209                 );
210             }
211         }
212     }
213 }