rustc_mir_transform/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
7use rustc_middle::mir::*;
8use rustc_middle::ty::{self, Ty, TyCtxt};
9
10pub(super) struct AddRetag;
11
12/// Determine whether this type may contain a reference (or box), and thus needs retagging.
13/// We will only recurse `depth` times into Tuples/ADTs to bound the cost of this.
14fn may_contain_reference<'tcx>(ty: Ty<'tcx>, depth: u32, tcx: TyCtxt<'tcx>) -> bool {
15 match ty.kind() {
16 // Primitive types that are not references
17 ty::Bool
18 | ty::Char
19 | ty::Float(_)
20 | ty::Int(_)
21 | ty::Uint(_)
22 | ty::RawPtr(..)
23 | ty::FnPtr(..)
24 | ty::Str
25 | ty::FnDef(..)
26 | ty::Never => false,
27 ty::Pat(base, ..) => may_contain_reference(*base, depth, tcx),
28 // References and Boxes (`noalias` sources)
29 ty::Ref(..) => true,
30 ty::Adt(..) if ty.is_box() => true,
31 // Compound types: recurse
32 ty::Array(ty, _) | ty::Slice(ty) => {
33 // This does not branch so we keep the depth the same.
34 may_contain_reference(*ty, depth, tcx)
35 }
36 ty::Tuple(tys) => {
37 depth == 0 || tys.iter().any(|ty| may_contain_reference(ty, depth - 1, tcx))
38 }
39 ty::Adt(adt, args) => {
40 depth == 0
41 || adt.variants().iter().any(|v| {
42 v.fields.iter().any(|f| may_contain_reference(f.ty(tcx, args), depth - 1, tcx))
43 })
44 }
45 // Conservative fallback
46 _ => true,
47 }
48}
49
50impl<'tcx> crate::MirPass<'tcx> for AddRetag {
51 fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
52 sess.opts.unstable_opts.mir_emit_retag
53 }
54
55 fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
56 // We need an `AllCallEdges` pass before we can do any work.
57 super::add_call_guards::AllCallEdges.run_pass(tcx, body);
58
59 let basic_blocks = body.basic_blocks.as_mut();
60 let local_decls = &body.local_decls;
61 let needs_retag = |place: &Place<'tcx>| {
62 // We're not really interested in stores to "outside" locations, they are hard to keep
63 // track of anyway.
64 !place.is_indirect_first_projection()
65 && may_contain_reference(place.ty(&*local_decls, tcx).ty, /*depth*/ 3, tcx)
66 && !local_decls[place.local].is_deref_temp()
67 };
68
69 // PART 1
70 // Retag arguments at the beginning of the start block.
71 {
72 // Gather all arguments, skip return value.
73 let places = local_decls.iter_enumerated().skip(1).take(body.arg_count).filter_map(
74 |(local, decl)| {
75 let place = Place::from(local);
76 needs_retag(&place).then_some((place, decl.source_info))
77 },
78 );
79
80 // Emit their retags.
81 basic_blocks[START_BLOCK].statements.splice(
82 0..0,
83 places.map(|(place, source_info)| {
84 Statement::new(
85 source_info,
86 StatementKind::Retag(RetagKind::FnEntry, Box::new(place)),
87 )
88 }),
89 );
90 }
91
92 // PART 2
93 // Retag return values of functions.
94 // We collect the return destinations because we cannot mutate while iterating.
95 let returns = basic_blocks
96 .iter_mut()
97 .filter_map(|block_data| {
98 match block_data.terminator().kind {
99 TerminatorKind::Call { target: Some(target), destination, .. }
100 if needs_retag(&destination) =>
101 {
102 // Remember the return destination for later
103 Some((block_data.terminator().source_info, destination, target))
104 }
105
106 // `Drop` is also a call, but it doesn't return anything so we are good.
107 TerminatorKind::Drop { .. } => None,
108 // Not a block ending in a Call -> ignore.
109 _ => None,
110 }
111 })
112 .collect::<Vec<_>>();
113 // Now we go over the returns we collected to retag the return values.
114 for (source_info, dest_place, dest_block) in returns {
115 basic_blocks[dest_block].statements.insert(
116 0,
117 Statement::new(
118 source_info,
119 StatementKind::Retag(RetagKind::Default, Box::new(dest_place)),
120 ),
121 );
122 }
123
124 // PART 3
125 // Add retag after assignments.
126 for block_data in basic_blocks {
127 // We want to insert statements as we iterate. To this end, we
128 // iterate backwards using indices.
129 for i in (0..block_data.statements.len()).rev() {
130 let (retag_kind, place) = match block_data.statements[i].kind {
131 // Retag after assignments of reference type.
132 StatementKind::Assign(box (ref place, ref rvalue)) => {
133 let add_retag = match rvalue {
134 // Ptr-creating operations already do their own internal retagging, no
135 // need to also add a retag statement. *Except* if we are deref'ing a
136 // Box, because those get desugared to directly working with the inner
137 // raw pointer! That's relevant for `RawPtr` as Miri otherwise makes it
138 // a NOP when the original pointer is already raw.
139 Rvalue::RawPtr(_mutbl, place) => {
140 // Using `is_box_global` here is a bit sketchy: if this code is
141 // generic over the allocator, we'll not add a retag! This is a hack
142 // to make Stacked Borrows compatible with custom allocator code.
143 // It means the raw pointer inherits the tag of the box, which mostly works
144 // but can sometimes lead to unexpected aliasing errors.
145 // Long-term, we'll want to move to an aliasing model where "cast to
146 // raw pointer" is a complete NOP, and then this will no longer be
147 // an issue.
148 if place.is_indirect_first_projection()
149 && body.local_decls[place.local].ty.is_box_global(tcx)
150 {
151 Some(RetagKind::Raw)
152 } else {
153 None
154 }
155 }
156 Rvalue::Ref(..) => None,
157 _ => {
158 if needs_retag(place) {
159 Some(RetagKind::Default)
160 } else {
161 None
162 }
163 }
164 };
165 if let Some(kind) = add_retag {
166 (kind, *place)
167 } else {
168 continue;
169 }
170 }
171 // Do nothing for the rest
172 _ => continue,
173 };
174 // Insert a retag after the statement.
175 let source_info = block_data.statements[i].source_info;
176 block_data.statements.insert(
177 i + 1,
178 Statement::new(source_info, StatementKind::Retag(retag_kind, Box::new(place))),
179 );
180 }
181 }
182 }
183
184 fn is_required(&self) -> bool {
185 true
186 }
187}