|
1 | 1 | pub use super::*;
|
2 | 2 |
|
3 |
| -use crate::dataflow::{BitDenotation, GenKillSet}; |
| 3 | +use crate::dataflow::generic::{AnalysisDomain, GenKill, GenKillAnalysis}; |
4 | 4 | use rustc::mir::visit::Visitor;
|
5 | 5 | use rustc::mir::*;
|
| 6 | +use rustc::ty::{ParamEnv, TyCtxt}; |
| 7 | +use rustc_span::DUMMY_SP; |
| 8 | + |
| 9 | +pub type MaybeMutBorrowedLocals<'mir, 'tcx> = MaybeBorrowedLocals<MutBorrow<'mir, 'tcx>>; |
| 10 | + |
| 11 | +/// A dataflow analysis that tracks whether a pointer or reference could possibly exist that points |
| 12 | +/// to a given local. |
| 13 | +/// |
| 14 | +/// The `K` parameter determines what kind of borrows are tracked. By default, |
| 15 | +/// `MaybeBorrowedLocals` looks for *any* borrow of a local. If you are only interested in borrows |
| 16 | +/// that might allow mutation, use the `MaybeMutBorrowedLocals` type alias instead. |
| 17 | +/// |
| 18 | +/// At present, this is used as a very limited form of alias analysis. For example, |
| 19 | +/// `MaybeBorrowedLocals` is used to compute which locals are live during a yield expression for |
| 20 | +/// immovable generators. `MaybeMutBorrowedLocals` is used during const checking to prove that a |
| 21 | +/// local has not been mutated via indirect assignment (e.g., `*p = 42`), the side-effects of a |
| 22 | +/// function call or inline assembly. |
| 23 | +pub struct MaybeBorrowedLocals<K = AnyBorrow> { |
| 24 | + kind: K, |
| 25 | +} |
6 | 26 |
|
7 |
| -/// This calculates if any part of a MIR local could have previously been borrowed. |
8 |
| -/// This means that once a local has been borrowed, its bit will be set |
9 |
| -/// from that point and onwards, until we see a StorageDead statement for the local, |
10 |
| -/// at which points there is no memory associated with the local, so it cannot be borrowed. |
11 |
| -/// This is used to compute which locals are live during a yield expression for |
12 |
| -/// immovable generators. |
13 |
| -#[derive(Copy, Clone)] |
14 |
| -pub struct HaveBeenBorrowedLocals<'a, 'tcx> { |
15 |
| - body: &'a Body<'tcx>, |
| 27 | +impl MaybeBorrowedLocals { |
| 28 | + /// A dataflow analysis that records whether a pointer or reference exists that may alias the |
| 29 | + /// given local. |
| 30 | + pub fn new() -> Self { |
| 31 | + MaybeBorrowedLocals { kind: AnyBorrow } |
| 32 | + } |
16 | 33 | }
|
17 | 34 |
|
18 |
| -impl<'a, 'tcx> HaveBeenBorrowedLocals<'a, 'tcx> { |
19 |
| - pub fn new(body: &'a Body<'tcx>) -> Self { |
20 |
| - HaveBeenBorrowedLocals { body } |
| 35 | +impl MaybeMutBorrowedLocals<'mir, 'tcx> { |
| 36 | + /// A dataflow analysis that records whether a pointer or reference exists that may *mutably* |
| 37 | + /// alias the given local. |
| 38 | + pub fn new_mut_only( |
| 39 | + tcx: TyCtxt<'tcx>, |
| 40 | + body: &'mir mir::Body<'tcx>, |
| 41 | + param_env: ParamEnv<'tcx>, |
| 42 | + ) -> Self { |
| 43 | + MaybeBorrowedLocals { kind: MutBorrow { body, tcx, param_env } } |
21 | 44 | }
|
| 45 | +} |
22 | 46 |
|
23 |
| - pub fn body(&self) -> &Body<'tcx> { |
24 |
| - self.body |
| 47 | +impl<K> MaybeBorrowedLocals<K> { |
| 48 | + fn transfer_function<'a, T>(&'a self, trans: &'a mut T) -> TransferFunction<'a, T, K> { |
| 49 | + TransferFunction { kind: &self.kind, trans } |
25 | 50 | }
|
26 | 51 | }
|
27 | 52 |
|
28 |
| -impl<'a, 'tcx> BitDenotation<'tcx> for HaveBeenBorrowedLocals<'a, 'tcx> { |
| 53 | +impl<K> AnalysisDomain<'tcx> for MaybeBorrowedLocals<K> |
| 54 | +where |
| 55 | + K: BorrowAnalysisKind<'tcx>, |
| 56 | +{ |
29 | 57 | type Idx = Local;
|
30 |
| - fn name() -> &'static str { |
31 |
| - "has_been_borrowed_locals" |
| 58 | + |
| 59 | + const NAME: &'static str = K::ANALYSIS_NAME; |
| 60 | + |
| 61 | + fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize { |
| 62 | + body.local_decls().len() |
| 63 | + } |
| 64 | + |
| 65 | + fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut BitSet<Self::Idx>) { |
| 66 | + // No locals are aliased on function entry |
| 67 | + } |
| 68 | +} |
| 69 | + |
| 70 | +impl<K> GenKillAnalysis<'tcx> for MaybeBorrowedLocals<K> |
| 71 | +where |
| 72 | + K: BorrowAnalysisKind<'tcx>, |
| 73 | +{ |
| 74 | + fn statement_effect( |
| 75 | + &self, |
| 76 | + trans: &mut impl GenKill<Self::Idx>, |
| 77 | + statement: &mir::Statement<'tcx>, |
| 78 | + location: Location, |
| 79 | + ) { |
| 80 | + self.transfer_function(trans).visit_statement(statement, location); |
32 | 81 | }
|
33 |
| - fn bits_per_block(&self) -> usize { |
34 |
| - self.body.local_decls.len() |
| 82 | + |
| 83 | + fn terminator_effect( |
| 84 | + &self, |
| 85 | + trans: &mut impl GenKill<Self::Idx>, |
| 86 | + terminator: &mir::Terminator<'tcx>, |
| 87 | + location: Location, |
| 88 | + ) { |
| 89 | + self.transfer_function(trans).visit_terminator(terminator, location); |
35 | 90 | }
|
36 | 91 |
|
37 |
| - fn start_block_effect(&self, _on_entry: &mut BitSet<Local>) { |
38 |
| - // Nothing is borrowed on function entry |
| 92 | + fn call_return_effect( |
| 93 | + &self, |
| 94 | + _trans: &mut impl GenKill<Self::Idx>, |
| 95 | + _block: mir::BasicBlock, |
| 96 | + _func: &mir::Operand<'tcx>, |
| 97 | + _args: &[mir::Operand<'tcx>], |
| 98 | + _dest_place: &mir::Place<'tcx>, |
| 99 | + ) { |
39 | 100 | }
|
| 101 | +} |
40 | 102 |
|
41 |
| - fn statement_effect(&self, trans: &mut GenKillSet<Local>, loc: Location) { |
42 |
| - let stmt = &self.body[loc.block].statements[loc.statement_index]; |
| 103 | +impl<K> BottomValue for MaybeBorrowedLocals<K> { |
| 104 | + // bottom = unborrowed |
| 105 | + const BOTTOM_VALUE: bool = false; |
| 106 | +} |
43 | 107 |
|
44 |
| - BorrowedLocalsVisitor { trans }.visit_statement(stmt, loc); |
| 108 | +/// A `Visitor` that defines the transfer function for `MaybeBorrowedLocals`. |
| 109 | +struct TransferFunction<'a, T, K> { |
| 110 | + trans: &'a mut T, |
| 111 | + kind: &'a K, |
| 112 | +} |
45 | 113 |
|
46 |
| - // StorageDead invalidates all borrows and raw pointers to a local |
47 |
| - match stmt.kind { |
48 |
| - StatementKind::StorageDead(l) => trans.kill(l), |
49 |
| - _ => (), |
| 114 | +impl<T, K> Visitor<'tcx> for TransferFunction<'a, T, K> |
| 115 | +where |
| 116 | + T: GenKill<Local>, |
| 117 | + K: BorrowAnalysisKind<'tcx>, |
| 118 | +{ |
| 119 | + fn visit_statement(&mut self, stmt: &Statement<'tcx>, location: Location) { |
| 120 | + self.super_statement(stmt, location); |
| 121 | + |
| 122 | + // When we reach a `StorageDead` statement, we can assume that any pointers to this memory |
| 123 | + // are now invalid. |
| 124 | + if let StatementKind::StorageDead(local) = stmt.kind { |
| 125 | + self.trans.kill(local); |
50 | 126 | }
|
51 | 127 | }
|
52 | 128 |
|
53 |
| - fn terminator_effect(&self, trans: &mut GenKillSet<Local>, loc: Location) { |
54 |
| - let terminator = self.body[loc.block].terminator(); |
55 |
| - BorrowedLocalsVisitor { trans }.visit_terminator(terminator, loc); |
56 |
| - match &terminator.kind { |
57 |
| - // Drop terminators borrows the location |
58 |
| - TerminatorKind::Drop { location, .. } |
59 |
| - | TerminatorKind::DropAndReplace { location, .. } => { |
60 |
| - if let Some(local) = find_local(location) { |
61 |
| - trans.gen(local); |
| 129 | + fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) { |
| 130 | + self.super_rvalue(rvalue, location); |
| 131 | + |
| 132 | + match rvalue { |
| 133 | + mir::Rvalue::AddressOf(mt, borrowed_place) => { |
| 134 | + if !borrowed_place.is_indirect() && self.kind.in_address_of(*mt, borrowed_place) { |
| 135 | + self.trans.gen(borrowed_place.local); |
62 | 136 | }
|
63 | 137 | }
|
64 |
| - _ => (), |
| 138 | + |
| 139 | + mir::Rvalue::Ref(_, kind, borrowed_place) => { |
| 140 | + if !borrowed_place.is_indirect() && self.kind.in_ref(*kind, borrowed_place) { |
| 141 | + self.trans.gen(borrowed_place.local); |
| 142 | + } |
| 143 | + } |
| 144 | + |
| 145 | + mir::Rvalue::Cast(..) |
| 146 | + | mir::Rvalue::Use(..) |
| 147 | + | mir::Rvalue::Repeat(..) |
| 148 | + | mir::Rvalue::Len(..) |
| 149 | + | mir::Rvalue::BinaryOp(..) |
| 150 | + | mir::Rvalue::CheckedBinaryOp(..) |
| 151 | + | mir::Rvalue::NullaryOp(..) |
| 152 | + | mir::Rvalue::UnaryOp(..) |
| 153 | + | mir::Rvalue::Discriminant(..) |
| 154 | + | mir::Rvalue::Aggregate(..) => {} |
65 | 155 | }
|
66 | 156 | }
|
67 | 157 |
|
68 |
| - fn propagate_call_return( |
69 |
| - &self, |
70 |
| - _in_out: &mut BitSet<Local>, |
71 |
| - _call_bb: mir::BasicBlock, |
72 |
| - _dest_bb: mir::BasicBlock, |
73 |
| - _dest_place: &mir::Place<'tcx>, |
74 |
| - ) { |
75 |
| - // Nothing to do when a call returns successfully |
| 158 | + fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) { |
| 159 | + self.super_terminator(terminator, location); |
| 160 | + |
| 161 | + match terminator.kind { |
| 162 | + // Drop terminators may call custom drop glue (`Drop::drop`), which takes `&mut self` |
| 163 | + // as a parameter. Hypothetically, a drop impl could launder that reference into the |
| 164 | + // surrounding environment through a raw pointer, thus creating a valid `*mut` pointing |
| 165 | + // to the dropped local. We are not yet willing to declare this particular case UB, so |
| 166 | + // we must treat all dropped locals as mutably borrowed for now. See discussion on |
| 167 | + // [#61069]. |
| 168 | + // |
| 169 | + // [#61069]: https://github.com/rust-lang/rust/pull/61069 |
| 170 | + mir::TerminatorKind::Drop { location: dropped_place, .. } |
| 171 | + | mir::TerminatorKind::DropAndReplace { location: dropped_place, .. } => { |
| 172 | + self.trans.gen(dropped_place.local); |
| 173 | + } |
| 174 | + |
| 175 | + TerminatorKind::Abort |
| 176 | + | TerminatorKind::Assert { .. } |
| 177 | + | TerminatorKind::Call { .. } |
| 178 | + | TerminatorKind::FalseEdges { .. } |
| 179 | + | TerminatorKind::FalseUnwind { .. } |
| 180 | + | TerminatorKind::GeneratorDrop |
| 181 | + | TerminatorKind::Goto { .. } |
| 182 | + | TerminatorKind::Resume |
| 183 | + | TerminatorKind::Return |
| 184 | + | TerminatorKind::SwitchInt { .. } |
| 185 | + | TerminatorKind::Unreachable |
| 186 | + | TerminatorKind::Yield { .. } => {} |
| 187 | + } |
76 | 188 | }
|
77 | 189 | }
|
78 | 190 |
|
79 |
| -impl<'a, 'tcx> BottomValue for HaveBeenBorrowedLocals<'a, 'tcx> { |
80 |
| - // bottom = unborrowed |
81 |
| - const BOTTOM_VALUE: bool = false; |
| 191 | +pub struct AnyBorrow; |
| 192 | + |
| 193 | +pub struct MutBorrow<'mir, 'tcx> { |
| 194 | + tcx: TyCtxt<'tcx>, |
| 195 | + body: &'mir Body<'tcx>, |
| 196 | + param_env: ParamEnv<'tcx>, |
82 | 197 | }
|
83 | 198 |
|
84 |
| -struct BorrowedLocalsVisitor<'gk> { |
85 |
| - trans: &'gk mut GenKillSet<Local>, |
| 199 | +impl MutBorrow<'mir, 'tcx> { |
| 200 | + // `&` and `&raw` only allow mutation if the borrowed place is `!Freeze`. |
| 201 | + // |
| 202 | + // This assumes that it is UB to take the address of a struct field whose type is |
| 203 | + // `Freeze`, then use pointer arithmetic to derive a pointer to a *different* field of |
| 204 | + // that same struct whose type is `!Freeze`. If we decide that this is not UB, we will |
| 205 | + // have to check the type of the borrowed **local** instead of the borrowed **place** |
| 206 | + // below. See [rust-lang/unsafe-code-guidelines#134]. |
| 207 | + // |
| 208 | + // [rust-lang/unsafe-code-guidelines#134]: https://github.com/rust-lang/unsafe-code-guidelines/issues/134 |
| 209 | + fn shared_borrow_allows_mutation(&self, place: &Place<'tcx>) -> bool { |
| 210 | + !place.ty(self.body, self.tcx).ty.is_freeze(self.tcx, self.param_env, DUMMY_SP) |
| 211 | + } |
86 | 212 | }
|
87 | 213 |
|
88 |
| -fn find_local(place: &Place<'_>) -> Option<Local> { |
89 |
| - if !place.is_indirect() { Some(place.local) } else { None } |
| 214 | +pub trait BorrowAnalysisKind<'tcx> { |
| 215 | + const ANALYSIS_NAME: &'static str; |
| 216 | + |
| 217 | + fn in_address_of(&self, mt: Mutability, place: &Place<'tcx>) -> bool; |
| 218 | + fn in_ref(&self, kind: mir::BorrowKind, place: &Place<'tcx>) -> bool; |
90 | 219 | }
|
91 | 220 |
|
92 |
| -impl<'tcx> Visitor<'tcx> for BorrowedLocalsVisitor<'_> { |
93 |
| - fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) { |
94 |
| - if let Rvalue::Ref(_, _, ref place) = *rvalue { |
95 |
| - if let Some(local) = find_local(place) { |
96 |
| - self.trans.gen(local); |
| 221 | +impl BorrowAnalysisKind<'tcx> for AnyBorrow { |
| 222 | + const ANALYSIS_NAME: &'static str = "maybe_borrowed_locals"; |
| 223 | + |
| 224 | + fn in_ref(&self, _: mir::BorrowKind, _: &Place<'_>) -> bool { |
| 225 | + true |
| 226 | + } |
| 227 | + fn in_address_of(&self, _: Mutability, _: &Place<'_>) -> bool { |
| 228 | + true |
| 229 | + } |
| 230 | +} |
| 231 | + |
| 232 | +impl BorrowAnalysisKind<'tcx> for MutBorrow<'mir, 'tcx> { |
| 233 | + const ANALYSIS_NAME: &'static str = "maybe_mut_borrowed_locals"; |
| 234 | + |
| 235 | + fn in_ref(&self, kind: mir::BorrowKind, place: &Place<'tcx>) -> bool { |
| 236 | + match kind { |
| 237 | + mir::BorrowKind::Mut { .. } => true, |
| 238 | + mir::BorrowKind::Shared | mir::BorrowKind::Shallow | mir::BorrowKind::Unique => { |
| 239 | + self.shared_borrow_allows_mutation(place) |
97 | 240 | }
|
98 | 241 | }
|
| 242 | + } |
99 | 243 |
|
100 |
| - self.super_rvalue(rvalue, location) |
| 244 | + fn in_address_of(&self, mt: Mutability, place: &Place<'tcx>) -> bool { |
| 245 | + match mt { |
| 246 | + Mutability::Mut => true, |
| 247 | + Mutability::Not => self.shared_borrow_allows_mutation(place), |
| 248 | + } |
101 | 249 | }
|
102 | 250 | }
|
0 commit comments