Skip to content

Commit 7d9dadc

Browse files
Implement Maybe{Mut,}BorrowedLocals analyses
1 parent fc5c295 commit 7d9dadc

File tree

2 files changed

+209
-62
lines changed

2 files changed

+209
-62
lines changed
Lines changed: 208 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,250 @@
11
pub use super::*;
22

3-
use crate::dataflow::{BitDenotation, GenKillSet};
3+
use crate::dataflow::generic::{AnalysisDomain, GenKill, GenKillAnalysis};
44
use rustc::mir::visit::Visitor;
55
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+
}
626

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+
}
1633
}
1734

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 } }
2144
}
45+
}
2246

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 }
2550
}
2651
}
2752

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+
{
2957
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);
3281
}
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);
3590
}
3691

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+
) {
39100
}
101+
}
40102

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+
}
43107

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+
}
45113

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);
50126
}
51127
}
52128

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);
62136
}
63137
}
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(..) => {}
65155
}
66156
}
67157

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+
}
76188
}
77189
}
78190

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>,
82197
}
83198

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+
}
86212
}
87213

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;
90219
}
91220

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)
97240
}
98241
}
242+
}
99243

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+
}
101249
}
102250
}

src/librustc_mir/dataflow/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ pub(crate) use self::drop_flag_effects::*;
2323
pub use self::impls::borrows::Borrows;
2424
pub use self::impls::DefinitelyInitializedPlaces;
2525
pub use self::impls::EverInitializedPlaces;
26-
pub use self::impls::HaveBeenBorrowedLocals;
27-
pub use self::impls::IndirectlyMutableLocals;
26+
pub use self::impls::{MaybeBorrowedLocals, MaybeMutBorrowedLocals};
2827
pub use self::impls::{MaybeInitializedPlaces, MaybeUninitializedPlaces};
2928
pub use self::impls::{MaybeStorageLive, RequiresStorage};
3029

0 commit comments

Comments
 (0)