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.
8 use rustc_middle::mir::*;
9 use rustc_middle::ty::{self, Ty, TyCtxt};
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| {
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
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,
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 {
40 // Primitive types that are not references
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)
60 depth == 0 || tys.iter().any(|ty| may_contain_reference(ty, depth - 1, tcx))
62 ty::Adt(adt, subst) => {
64 || adt.variants().iter().any(|v| {
65 v.fields.iter().any(|f| may_contain_reference(f.ty(tcx, subst), depth - 1, tcx))
68 // Conservative fallback
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,
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
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);
93 let (span, arg_count) = (body.span, body.arg_count);
94 let (basic_blocks, local_decls) = body.basic_blocks_and_local_decls_mut();
95 let needs_retag = |place: &Place<'tcx>| {
96 // FIXME: Instead of giving up for unstable places, we should introduce
97 // a temporary and retag on that.
98 is_stable(place.as_ref())
99 && may_contain_reference(place.ty(&*local_decls, tcx).ty, /*depth*/ 3, tcx)
100 && is_not_temp(&local_decls[place.local])
102 let place_base_raw = |place: &Place<'tcx>| {
103 // If this is a `Deref`, get the type of what we are deref'ing.
105 place.projection.iter().rposition(|p| matches!(p, ProjectionElem::Deref));
106 if let Some(deref_base) = deref_base {
107 let base_proj = &place.projection[..deref_base];
108 let ty = Place::ty_from(place.local, base_proj, &*local_decls, tcx).ty;
111 // Not a deref, and thus not raw.
117 // Retag arguments at the beginning of the start block.
119 // FIXME: Consider using just the span covering the function
120 // argument declaration.
121 let source_info = SourceInfo::outermost(span);
122 // Gather all arguments, skip return value.
123 let places = local_decls
127 .map(|(local, _)| Place::from(local))
128 .filter(needs_retag);
129 // Emit their retags.
130 basic_blocks[START_BLOCK].statements.splice(
132 places.map(|place| Statement {
134 kind: StatementKind::Retag(RetagKind::FnEntry, Box::new(place)),
140 // Retag return values of functions. Also escape-to-raw the argument of `drop`.
141 // We collect the return destinations because we cannot mutate while iterating.
142 let returns = basic_blocks
144 .filter_map(|block_data| {
145 match block_data.terminator().kind {
146 TerminatorKind::Call { target: Some(target), destination, .. }
147 if needs_retag(&destination) =>
149 // Remember the return destination for later
150 Some((block_data.terminator().source_info, destination, target))
153 // `Drop` is also a call, but it doesn't return anything so we are good.
154 TerminatorKind::Drop { .. } | TerminatorKind::DropAndReplace { .. } => None,
155 // Not a block ending in a Call -> ignore.
159 .collect::<Vec<_>>();
160 // Now we go over the returns we collected to retag the return values.
161 for (source_info, dest_place, dest_block) in returns {
162 basic_blocks[dest_block].statements.insert(
166 kind: StatementKind::Retag(RetagKind::Default, Box::new(dest_place)),
172 // Add retag after assignment.
173 for block_data in basic_blocks {
174 // We want to insert statements as we iterate. To this end, we
175 // iterate backwards using indices.
176 for i in (0..block_data.statements.len()).rev() {
177 let (retag_kind, place) = match block_data.statements[i].kind {
178 // Retag-as-raw after escaping to a raw pointer, if the referent
179 // is not already a raw pointer.
180 StatementKind::Assign(box (lplace, Rvalue::AddressOf(_, ref rplace)))
181 if !place_base_raw(rplace) =>
183 (RetagKind::Raw, lplace)
185 // Retag after assignments of reference type.
186 StatementKind::Assign(box (ref place, ref rvalue)) if needs_retag(place) => {
187 let kind = match rvalue {
188 Rvalue::Ref(_, borrow_kind, _)
189 if borrow_kind.allows_two_phase_borrow() =>
193 _ => RetagKind::Default,
197 // Do nothing for the rest
200 // Insert a retag after the statement.
201 let source_info = block_data.statements[i].source_info;
202 block_data.statements.insert(
206 kind: StatementKind::Retag(retag_kind, Box::new(place)),