Skip to content

Commit fc92d3b

Browse files
Add dataflow-based const validation
1 parent 3698d04 commit fc92d3b

File tree

4 files changed

+1292
-0
lines changed

4 files changed

+1292
-0
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use rustc::hir::def_id::DefId;
2+
use rustc::mir;
3+
use rustc::ty::{self, TyCtxt};
4+
5+
pub use self::qualifs::Qualif;
6+
7+
mod resolver;
8+
mod qualifs;
9+
pub mod validation;
10+
11+
/// Information about the item currently being validated, as well as a reference to the global
12+
/// context.
13+
pub struct Item<'mir, 'tcx> {
14+
body: &'mir mir::Body<'tcx>,
15+
tcx: TyCtxt<'tcx>,
16+
def_id: DefId,
17+
param_env: ty::ParamEnv<'tcx>,
18+
mode: validation::Mode,
19+
}
20+
21+
impl Item<'mir, 'tcx> {
22+
pub fn new(
23+
tcx: TyCtxt<'tcx>,
24+
def_id: DefId,
25+
body: &'mir mir::Body<'tcx>,
26+
) -> Self {
27+
let param_env = tcx.param_env(def_id);
28+
let mode = validation::Mode::for_item(tcx, def_id)
29+
.expect("const validation must only be run inside a const context");
30+
31+
Item {
32+
body,
33+
tcx,
34+
def_id,
35+
param_env,
36+
mode,
37+
}
38+
}
39+
}
40+
41+
42+
fn is_lang_panic_fn(tcx: TyCtxt<'tcx>, def_id: DefId) -> bool {
43+
Some(def_id) == tcx.lang_items().panic_fn() ||
44+
Some(def_id) == tcx.lang_items().begin_panic_fn()
45+
}
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
use rustc::mir::visit::Visitor;
2+
use rustc::mir::{self, BasicBlock, Local, Location};
3+
use rustc_data_structures::bit_set::BitSet;
4+
5+
use std::cell::RefCell;
6+
use std::marker::PhantomData;
7+
use std::rc::Rc;
8+
9+
use crate::dataflow::{self as old_dataflow, generic as dataflow};
10+
use super::{Item, Qualif};
11+
use self::old_dataflow::IndirectlyMutableLocals;
12+
13+
/// A `Visitor` that propagates qualifs between locals. This defines the transfer function of
14+
/// `FlowSensitiveAnalysis` as well as the logic underlying `TempPromotionResolver`.
15+
///
16+
/// This transfer does nothing when encountering an indirect assignment. Consumers should rely on
17+
/// the `IndirectlyMutableLocals` dataflow pass to see if a `Local` may have become qualified via
18+
/// an indirect assignment or function call.
19+
struct TransferFunction<'a, 'mir, 'tcx, Q> {
20+
item: &'a Item<'mir, 'tcx>,
21+
qualifs_per_local: &'a mut BitSet<Local>,
22+
23+
_qualif: PhantomData<Q>,
24+
}
25+
26+
impl<Q> TransferFunction<'a, 'mir, 'tcx, Q>
27+
where
28+
Q: Qualif,
29+
{
30+
fn new(
31+
item: &'a Item<'mir, 'tcx>,
32+
qualifs_per_local: &'a mut BitSet<Local>,
33+
) -> Self {
34+
TransferFunction {
35+
item,
36+
qualifs_per_local,
37+
_qualif: PhantomData,
38+
}
39+
}
40+
41+
fn initialize_state(&mut self) {
42+
self.qualifs_per_local.clear();
43+
44+
for arg in self.item.body.args_iter() {
45+
let arg_ty = self.item.body.local_decls[arg].ty;
46+
if Q::in_any_value_of_ty(self.item, arg_ty).unwrap() {
47+
self.qualifs_per_local.insert(arg);
48+
}
49+
}
50+
}
51+
52+
fn assign_qualif_direct(&mut self, place: &mir::Place<'tcx>, value: bool) {
53+
debug_assert!(!place.is_indirect());
54+
55+
match (value, place) {
56+
(true, mir::Place { base: mir::PlaceBase::Local(local), .. }) => {
57+
self.qualifs_per_local.insert(*local);
58+
}
59+
60+
// For now, we do not clear the qualif if a local is overwritten in full by
61+
// an unqualified rvalue (e.g. `y = 5`). This is to be consistent
62+
// with aggregates where we overwrite all fields with assignments, which would not
63+
// get this feature.
64+
(false, mir::Place { base: mir::PlaceBase::Local(_local), projection: box [] }) => {
65+
// self.qualifs_per_local.remove(*local);
66+
}
67+
68+
_ => {}
69+
}
70+
}
71+
72+
fn apply_call_return_effect(
73+
&mut self,
74+
_block: BasicBlock,
75+
func: &mir::Operand<'tcx>,
76+
args: &[mir::Operand<'tcx>],
77+
return_place: &mir::Place<'tcx>,
78+
) {
79+
let return_ty = return_place.ty(self.item.body, self.item.tcx).ty;
80+
let qualif = Q::in_call(self.item, &mut self.qualifs_per_local, func, args, return_ty);
81+
if !return_place.is_indirect() {
82+
self.assign_qualif_direct(return_place, qualif);
83+
}
84+
}
85+
}
86+
87+
impl<Q> Visitor<'tcx> for TransferFunction<'_, '_, 'tcx, Q>
88+
where
89+
Q: Qualif,
90+
{
91+
fn visit_operand(&mut self, operand: &mir::Operand<'tcx>, location: Location) {
92+
self.super_operand(operand, location);
93+
94+
if !Q::IS_CLEARED_ON_MOVE {
95+
return;
96+
}
97+
98+
// If a local with no projections is moved from (e.g. `x` in `y = x`), record that
99+
// it no longer needs to be dropped.
100+
if let mir::Operand::Move(mir::Place {
101+
base: mir::PlaceBase::Local(local),
102+
projection: box [],
103+
}) = *operand {
104+
self.qualifs_per_local.remove(local);
105+
}
106+
}
107+
108+
fn visit_assign(
109+
&mut self,
110+
place: &mir::Place<'tcx>,
111+
rvalue: &mir::Rvalue<'tcx>,
112+
location: Location,
113+
) {
114+
let qualif = Q::in_rvalue(self.item, self.qualifs_per_local, rvalue);
115+
if !place.is_indirect() {
116+
self.assign_qualif_direct(place, qualif);
117+
}
118+
119+
// We need to assign qualifs to the left-hand side before visiting `rvalue` since
120+
// qualifs can be cleared on move.
121+
self.super_assign(place, rvalue, location);
122+
}
123+
124+
fn visit_terminator_kind(&mut self, kind: &mir::TerminatorKind<'tcx>, location: Location) {
125+
// The effect of assignment to the return place in `TerminatorKind::Call` is not applied
126+
// here; that occurs in `apply_call_return_effect`.
127+
128+
if let mir::TerminatorKind::DropAndReplace { value, location: dest, .. } = kind {
129+
let qualif = Q::in_operand(self.item, self.qualifs_per_local, value);
130+
if !dest.is_indirect() {
131+
self.assign_qualif_direct(dest, qualif);
132+
}
133+
}
134+
135+
// We need to assign qualifs to the dropped location before visiting the operand that
136+
// replaces it since qualifs can be cleared on move.
137+
self.super_terminator_kind(kind, location);
138+
}
139+
}
140+
141+
/// Types that can compute the qualifs of each local at each location in a `mir::Body`.
142+
///
143+
/// Code that wishes to use a `QualifResolver` must call `visit_{statement,terminator}` for each
144+
/// statement or terminator, processing blocks in reverse post-order beginning from the
145+
/// `START_BLOCK`. Calling code may optionally call `get` after visiting each statement or
146+
/// terminator to query the qualification state immediately before that statement or terminator.
147+
///
148+
/// These conditions are much more restrictive than woud be required by `FlowSensitiveResolver`
149+
/// alone. This is to allow a linear, on-demand `TempPromotionResolver` that can operate
150+
/// efficiently on simple CFGs.
151+
pub trait QualifResolver<Q> {
152+
/// Get the qualifs of each local at the last location visited.
153+
///
154+
/// This takes `&mut self` so qualifs can be computed lazily.
155+
fn get(&mut self) -> &BitSet<Local>;
156+
157+
/// A convenience method for `self.get().contains(local)`.
158+
fn contains(&mut self, local: Local) -> bool {
159+
self.get().contains(local)
160+
}
161+
162+
/// Resets the resolver to the `START_BLOCK`. This allows a resolver to be reused
163+
/// for multiple passes over a `mir::Body`.
164+
fn reset(&mut self);
165+
}
166+
167+
type IndirectlyMutableResults<'mir, 'tcx> =
168+
old_dataflow::DataflowResultsCursor<'mir, 'tcx, IndirectlyMutableLocals<'mir, 'tcx>>;
169+
170+
/// A resolver for qualifs that works on arbitrarily complex CFGs.
171+
///
172+
/// As soon as a `Local` becomes writable through a reference (as determined by the
173+
/// `IndirectlyMutableLocals` dataflow pass), we must assume that it takes on all other qualifs
174+
/// possible for its type. This is because no effort is made to track qualifs across indirect
175+
/// assignments (e.g. `*p = x` or calls to opaque functions).
176+
///
177+
/// It is possible to be more precise here by waiting until an indirect assignment actually occurs
178+
/// before marking a borrowed `Local` as qualified.
179+
pub struct FlowSensitiveResolver<'a, 'mir, 'tcx, Q>
180+
where
181+
Q: Qualif,
182+
{
183+
location: Location,
184+
indirectly_mutable_locals: Rc<RefCell<IndirectlyMutableResults<'mir, 'tcx>>>,
185+
cursor: dataflow::ResultsCursor<'mir, 'tcx, FlowSensitiveAnalysis<'a, 'mir, 'tcx, Q>>,
186+
qualifs_per_local: BitSet<Local>,
187+
}
188+
189+
impl<Q> FlowSensitiveResolver<'a, 'mir, 'tcx, Q>
190+
where
191+
Q: Qualif,
192+
{
193+
pub fn new(
194+
_: Q,
195+
item: &'a Item<'mir, 'tcx>,
196+
indirectly_mutable_locals: Rc<RefCell<IndirectlyMutableResults<'mir, 'tcx>>>,
197+
dead_unwinds: &BitSet<BasicBlock>,
198+
) -> Self {
199+
let analysis = FlowSensitiveAnalysis {
200+
item,
201+
_qualif: PhantomData,
202+
};
203+
let results =
204+
dataflow::Engine::new(item.body, dead_unwinds, analysis).iterate_to_fixpoint();
205+
let cursor = dataflow::ResultsCursor::new(item.body, results);
206+
207+
FlowSensitiveResolver {
208+
cursor,
209+
indirectly_mutable_locals,
210+
qualifs_per_local: BitSet::new_empty(item.body.local_decls.len()),
211+
location: Location { block: mir::START_BLOCK, statement_index: 0 },
212+
}
213+
}
214+
}
215+
216+
impl<Q> Visitor<'tcx> for FlowSensitiveResolver<'_, '_, 'tcx, Q>
217+
where
218+
Q: Qualif
219+
{
220+
fn visit_statement(&mut self, _: &mir::Statement<'tcx>, location: Location) {
221+
self.location = location;
222+
}
223+
224+
fn visit_terminator(&mut self, _: &mir::Terminator<'tcx>, location: Location) {
225+
self.location = location;
226+
}
227+
}
228+
229+
impl<Q> QualifResolver<Q> for FlowSensitiveResolver<'_, '_, '_, Q>
230+
where
231+
Q: Qualif
232+
{
233+
fn get(&mut self) -> &BitSet<Local> {
234+
let mut indirectly_mutable_locals = self.indirectly_mutable_locals.borrow_mut();
235+
236+
indirectly_mutable_locals.seek(self.location);
237+
self.cursor.seek_before(self.location);
238+
239+
self.qualifs_per_local.overwrite(indirectly_mutable_locals.get());
240+
self.qualifs_per_local.union(self.cursor.get());
241+
&self.qualifs_per_local
242+
}
243+
244+
fn contains(&mut self, local: Local) -> bool {
245+
self.cursor.seek_before(self.location);
246+
if self.cursor.get().contains(local) {
247+
return true;
248+
}
249+
250+
251+
let mut indirectly_mutable_locals = self.indirectly_mutable_locals.borrow_mut();
252+
indirectly_mutable_locals.seek(self.location);
253+
indirectly_mutable_locals.get().contains(local)
254+
}
255+
256+
fn reset(&mut self) {
257+
self.location = Location { block: mir::START_BLOCK, statement_index: 0 };
258+
}
259+
}
260+
261+
/// The dataflow analysis used to propagate qualifs on arbitrary CFGs.
262+
pub(super) struct FlowSensitiveAnalysis<'a, 'mir, 'tcx, Q> {
263+
item: &'a Item<'mir, 'tcx>,
264+
_qualif: PhantomData<Q>,
265+
}
266+
267+
impl<'a, 'mir, 'tcx, Q> FlowSensitiveAnalysis<'a, 'mir, 'tcx, Q>
268+
where
269+
Q: Qualif,
270+
{
271+
fn transfer_function(
272+
&self,
273+
state: &'a mut BitSet<Local>,
274+
) -> TransferFunction<'a, 'mir, 'tcx, Q> {
275+
TransferFunction::<Q>::new(self.item, state)
276+
}
277+
}
278+
279+
impl<Q> old_dataflow::BottomValue for FlowSensitiveAnalysis<'_, '_, '_, Q> {
280+
const BOTTOM_VALUE: bool = false;
281+
}
282+
283+
impl<Q> dataflow::Analysis<'tcx> for FlowSensitiveAnalysis<'_, '_, 'tcx, Q>
284+
where
285+
Q: Qualif,
286+
{
287+
type Idx = Local;
288+
289+
const NAME: &'static str = "flow_sensitive_qualif";
290+
291+
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
292+
body.local_decls.len()
293+
}
294+
295+
fn initialize_start_block(&self, _body: &mir::Body<'tcx>, state: &mut BitSet<Self::Idx>) {
296+
self.transfer_function(state).initialize_state();
297+
}
298+
299+
fn apply_statement_effect(
300+
&self,
301+
state: &mut BitSet<Self::Idx>,
302+
statement: &mir::Statement<'tcx>,
303+
location: Location,
304+
) {
305+
self.transfer_function(state).visit_statement(statement, location);
306+
}
307+
308+
fn apply_terminator_effect(
309+
&self,
310+
state: &mut BitSet<Self::Idx>,
311+
terminator: &mir::Terminator<'tcx>,
312+
location: Location,
313+
) {
314+
self.transfer_function(state).visit_terminator(terminator, location);
315+
}
316+
317+
fn apply_call_return_effect(
318+
&self,
319+
state: &mut BitSet<Self::Idx>,
320+
block: BasicBlock,
321+
func: &mir::Operand<'tcx>,
322+
args: &[mir::Operand<'tcx>],
323+
return_place: &mir::Place<'tcx>,
324+
) {
325+
self.transfer_function(state).apply_call_return_effect(block, func, args, return_place)
326+
}
327+
}

0 commit comments

Comments
 (0)