Skip to content

Commit 7daf2e8

Browse files
authored
Rollup merge of rust-lang#64980 - ecstatic-morse:better-rustc-peek, r=oli-obk
Enable support for `IndirectlyMutableLocals` in `rustc_peek` This PR allows `rustc_peek` tests to be written for the `IndirectlyMutableLocals` analysis implemented in rust-lang#64470. See any of the tests in [`test/ui/mir-dataflow`](https://github.com/rust-lang/rust/blob/master/src/test/ui/mir-dataflow/inits-1.rs) for an example. Included in this PR is a major rewrite of the `rustc_peek` module. This was motivated by the differences between the `IndirectlyMutableLocals` analysis and the initialized places ones. To properly test `IndirectlyMutableLocals`, we must pass locals by-value to `rustc_peek`, since any local that is not `Freeze` will be marked as indirectly mutable as soon as a reference to it is taken. Unfortunately, `UnsafeCell` is not `Copy`, so we can only do one `rustc_peek` on each value with interior mutability inside a test. I'm not sure how to deal with this restriction; perhaps I need to special case borrows preceding a call to `rustc_peek` in the analysis itself? `rustc_peek` also assumed that the analysis was done on move paths and that its transfer function only needed to be applied at assignment statements. This PR removes both of those restrictions by adding a trait, `RustcPeekAt`, that controls how the peeked at `Place` maps to the current dataflow state and using a dataflow cursor to retrieve the state itself. Finally, this PR adds a test which demonstrates some unsoundness in the `IndirectlyMutableLocals` analysis by converting a reference to a `Freeze` field to a reference to a `!Freeze` field by offsetting a pointer (or in this case transmuting a pointer to a ZST field with the same address as a `!Freeze` field). This does not represent a hole in the language proper, since this analysis is only used to validate `const` bodies, in which the unsound code will only compile with `-Zunleash-the-miri-inside-of-you`. Nevertheless, this should get fixed. r? @oli-obk
2 parents 10b0fe9 + 33aa5e8 commit 7daf2e8

File tree

4 files changed

+237
-130
lines changed

4 files changed

+237
-130
lines changed

src/librustc_mir/transform/rustc_peek.rs

+184-130
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@ use syntax::ast;
33
use syntax::symbol::sym;
44
use syntax_pos::Span;
55

6-
use rustc::ty::{self, TyCtxt};
6+
use rustc::ty::{self, TyCtxt, Ty};
77
use rustc::hir::def_id::DefId;
8-
use rustc::mir::{self, Body, Location};
8+
use rustc::mir::{self, Body, Location, Local};
99
use rustc_index::bit_set::BitSet;
1010
use crate::transform::{MirPass, MirSource};
1111

1212
use crate::dataflow::{do_dataflow, DebugFormatted};
1313
use crate::dataflow::MoveDataParamEnv;
1414
use crate::dataflow::BitDenotation;
1515
use crate::dataflow::DataflowResults;
16+
use crate::dataflow::DataflowResultsCursor;
1617
use crate::dataflow::{
1718
DefinitelyInitializedPlaces, MaybeInitializedPlaces, MaybeUninitializedPlaces
1819
};
20+
use crate::dataflow::IndirectlyMutableLocals;
1921
use crate::dataflow::move_paths::{MovePathIndex, LookupResult};
2022
use crate::dataflow::move_paths::{HasMoveData, MoveData};
2123

@@ -50,6 +52,10 @@ impl<'tcx> MirPass<'tcx> for SanityCheck {
5052
do_dataflow(tcx, body, def_id, &attributes, &dead_unwinds,
5153
DefinitelyInitializedPlaces::new(tcx, body, &mdpe),
5254
|bd, i| DebugFormatted::new(&bd.move_data().move_paths[i]));
55+
let flow_indirectly_mut =
56+
do_dataflow(tcx, body, def_id, &attributes, &dead_unwinds,
57+
IndirectlyMutableLocals::new(tcx, body, param_env),
58+
|_, i| DebugFormatted::new(&i));
5359

5460
if has_rustc_mir_with(&attributes, sym::rustc_peek_maybe_init).is_some() {
5561
sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_inits);
@@ -60,6 +66,9 @@ impl<'tcx> MirPass<'tcx> for SanityCheck {
6066
if has_rustc_mir_with(&attributes, sym::rustc_peek_definite_init).is_some() {
6167
sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_def_inits);
6268
}
69+
if has_rustc_mir_with(&attributes, sym::rustc_peek_indirectly_mutable).is_some() {
70+
sanity_check_via_rustc_peek(tcx, body, def_id, &attributes, &flow_indirectly_mut);
71+
}
6372
if has_rustc_mir_with(&attributes, sym::stop_after_dataflow).is_some() {
6473
tcx.sess.fatal("stop_after_dataflow ended compilation");
6574
}
@@ -88,151 +97,196 @@ pub fn sanity_check_via_rustc_peek<'tcx, O>(
8897
def_id: DefId,
8998
_attributes: &[ast::Attribute],
9099
results: &DataflowResults<'tcx, O>,
91-
) where
92-
O: BitDenotation<'tcx, Idx = MovePathIndex> + HasMoveData<'tcx>,
93-
{
100+
) where O: RustcPeekAt<'tcx> {
94101
debug!("sanity_check_via_rustc_peek def_id: {:?}", def_id);
95-
// FIXME: this is not DRY. Figure out way to abstract this and
96-
// `dataflow::build_sets`. (But note it is doing non-standard
97-
// stuff, so such generalization may not be realistic.)
98102

99-
for bb in body.basic_blocks().indices() {
100-
each_block(tcx, body, results, bb);
101-
}
102-
}
103+
let mut cursor = DataflowResultsCursor::new(results, body);
103104

104-
fn each_block<'tcx, O>(
105-
tcx: TyCtxt<'tcx>,
106-
body: &Body<'tcx>,
107-
results: &DataflowResults<'tcx, O>,
108-
bb: mir::BasicBlock,
109-
) where
110-
O: BitDenotation<'tcx, Idx = MovePathIndex> + HasMoveData<'tcx>,
111-
{
112-
let move_data = results.0.operator.move_data();
113-
let mir::BasicBlockData { ref statements, ref terminator, is_cleanup: _ } = body[bb];
114-
115-
let (args, span) = match is_rustc_peek(tcx, terminator) {
116-
Some(args_and_span) => args_and_span,
117-
None => return,
118-
};
119-
assert!(args.len() == 1);
120-
let peek_arg_place = match args[0] {
121-
mir::Operand::Copy(ref place @ mir::Place {
122-
base: mir::PlaceBase::Local(_),
123-
projection: box [],
124-
}) |
125-
mir::Operand::Move(ref place @ mir::Place {
126-
base: mir::PlaceBase::Local(_),
127-
projection: box [],
128-
}) => Some(place),
129-
_ => None,
130-
};
131-
132-
let peek_arg_place = match peek_arg_place {
133-
Some(arg) => arg,
134-
None => {
135-
tcx.sess.diagnostic().span_err(
136-
span, "dataflow::sanity_check cannot feed a non-temp to rustc_peek.");
137-
return;
138-
}
139-
};
140-
141-
let mut on_entry = results.0.sets.entry_set_for(bb.index()).to_owned();
142-
let mut trans = results.0.sets.trans_for(bb.index()).clone();
143-
144-
// Emulate effect of all statements in the block up to (but not
145-
// including) the borrow within `peek_arg_place`. Do *not* include
146-
// call to `peek_arg_place` itself (since we are peeking the state
147-
// of the argument at time immediate preceding Call to
148-
// `rustc_peek`).
149-
150-
for (j, stmt) in statements.iter().enumerate() {
151-
debug!("rustc_peek: ({:?},{}) {:?}", bb, j, stmt);
152-
let (place, rvalue) = match stmt.kind {
153-
mir::StatementKind::Assign(box(ref place, ref rvalue)) => {
154-
(place, rvalue)
105+
let peek_calls = body
106+
.basic_blocks()
107+
.iter_enumerated()
108+
.filter_map(|(bb, block_data)| {
109+
PeekCall::from_terminator(tcx, block_data.terminator())
110+
.map(|call| (bb, block_data, call))
111+
});
112+
113+
for (bb, block_data, call) in peek_calls {
114+
// Look for a sequence like the following to indicate that we should be peeking at `_1`:
115+
// _2 = &_1;
116+
// rustc_peek(_2);
117+
//
118+
// /* or */
119+
//
120+
// _2 = _1;
121+
// rustc_peek(_2);
122+
let (statement_index, peek_rval) = block_data
123+
.statements
124+
.iter()
125+
.enumerate()
126+
.filter_map(|(i, stmt)| value_assigned_to_local(stmt, call.arg).map(|rval| (i, rval)))
127+
.next()
128+
.expect("call to rustc_peek should be preceded by \
129+
assignment to temporary holding its argument");
130+
131+
match (call.kind, peek_rval) {
132+
| (PeekCallKind::ByRef, mir::Rvalue::Ref(_, _, place))
133+
| (PeekCallKind::ByVal, mir::Rvalue::Use(mir::Operand::Move(place)))
134+
| (PeekCallKind::ByVal, mir::Rvalue::Use(mir::Operand::Copy(place)))
135+
=> {
136+
let loc = Location { block: bb, statement_index };
137+
cursor.seek(loc);
138+
let state = cursor.get();
139+
results.operator().peek_at(tcx, place, state, call);
155140
}
156-
mir::StatementKind::FakeRead(..) |
157-
mir::StatementKind::StorageLive(_) |
158-
mir::StatementKind::StorageDead(_) |
159-
mir::StatementKind::InlineAsm { .. } |
160-
mir::StatementKind::Retag { .. } |
161-
mir::StatementKind::AscribeUserType(..) |
162-
mir::StatementKind::Nop => continue,
163-
mir::StatementKind::SetDiscriminant{ .. } =>
164-
span_bug!(stmt.source_info.span,
165-
"sanity_check should run before Deaggregator inserts SetDiscriminant"),
166-
};
167141

168-
if place == peek_arg_place {
169-
if let mir::Rvalue::Ref(_, mir::BorrowKind::Shared, ref peeking_at_place) = *rvalue {
170-
// Okay, our search is over.
171-
match move_data.rev_lookup.find(peeking_at_place.as_ref()) {
172-
LookupResult::Exact(peek_mpi) => {
173-
let bit_state = on_entry.contains(peek_mpi);
174-
debug!("rustc_peek({:?} = &{:?}) bit_state: {}",
175-
place, peeking_at_place, bit_state);
176-
if !bit_state {
177-
tcx.sess.span_err(span, "rustc_peek: bit not set");
178-
}
179-
}
180-
LookupResult::Parent(..) => {
181-
tcx.sess.span_err(span, "rustc_peek: argument untracked");
182-
}
183-
}
184-
return;
185-
} else {
186-
// Our search should have been over, but the input
187-
// does not match expectations of `rustc_peek` for
188-
// this sanity_check.
142+
_ => {
189143
let msg = "rustc_peek: argument expression \
190-
must be immediate borrow of form `&expr`";
191-
tcx.sess.span_err(span, msg);
144+
must be either `place` or `&place`";
145+
tcx.sess.span_err(call.span, msg);
192146
}
193147
}
148+
}
149+
}
194150

195-
let lhs_mpi = move_data.rev_lookup.find(place.as_ref());
196-
197-
debug!("rustc_peek: computing effect on place: {:?} ({:?}) in stmt: {:?}",
198-
place, lhs_mpi, stmt);
199-
// reset GEN and KILL sets before emulating their effect.
200-
trans.clear();
201-
results.0.operator.before_statement_effect(
202-
&mut trans,
203-
Location { block: bb, statement_index: j });
204-
results.0.operator.statement_effect(
205-
&mut trans,
206-
Location { block: bb, statement_index: j });
207-
trans.apply(&mut on_entry);
151+
/// If `stmt` is an assignment where the LHS is the given local (with no projections), returns the
152+
/// RHS of the assignment.
153+
fn value_assigned_to_local<'a, 'tcx>(
154+
stmt: &'a mir::Statement<'tcx>,
155+
local: Local,
156+
) -> Option<&'a mir::Rvalue<'tcx>> {
157+
if let mir::StatementKind::Assign(box (place, rvalue)) = &stmt.kind {
158+
if let mir::Place { base: mir::PlaceBase::Local(l), projection: box [] } = place {
159+
if local == *l {
160+
return Some(&*rvalue);
161+
}
162+
}
208163
}
209164

210-
results.0.operator.before_terminator_effect(
211-
&mut trans,
212-
Location { block: bb, statement_index: statements.len() });
165+
None
166+
}
213167

214-
tcx.sess.span_err(span, &format!("rustc_peek: MIR did not match \
215-
anticipated pattern; note that \
216-
rustc_peek expects input of \
217-
form `&expr`"));
168+
#[derive(Clone, Copy, Debug)]
169+
enum PeekCallKind {
170+
ByVal,
171+
ByRef,
218172
}
219173

220-
fn is_rustc_peek<'a, 'tcx>(
221-
tcx: TyCtxt<'tcx>,
222-
terminator: &'a Option<mir::Terminator<'tcx>>,
223-
) -> Option<(&'a [mir::Operand<'tcx>], Span)> {
224-
if let Some(mir::Terminator { ref kind, source_info, .. }) = *terminator {
225-
if let mir::TerminatorKind::Call { func: ref oper, ref args, .. } = *kind {
226-
if let mir::Operand::Constant(ref func) = *oper {
227-
if let ty::FnDef(def_id, _) = func.literal.ty.kind {
228-
let abi = tcx.fn_sig(def_id).abi();
229-
let name = tcx.item_name(def_id);
230-
if abi == Abi::RustIntrinsic && name == sym::rustc_peek {
231-
return Some((args, source_info.span));
174+
impl PeekCallKind {
175+
fn from_arg_ty(arg: Ty<'_>) -> Self {
176+
match arg.kind {
177+
ty::Ref(_, _, _) => PeekCallKind::ByRef,
178+
_ => PeekCallKind::ByVal,
179+
}
180+
}
181+
}
182+
183+
#[derive(Clone, Copy, Debug)]
184+
pub struct PeekCall {
185+
arg: Local,
186+
kind: PeekCallKind,
187+
span: Span,
188+
}
189+
190+
impl PeekCall {
191+
fn from_terminator<'tcx>(
192+
tcx: TyCtxt<'tcx>,
193+
terminator: &mir::Terminator<'tcx>,
194+
) -> Option<Self> {
195+
use mir::{Operand, Place, PlaceBase};
196+
197+
let span = terminator.source_info.span;
198+
if let mir::TerminatorKind::Call { func: Operand::Constant(func), args, .. } =
199+
&terminator.kind
200+
{
201+
if let ty::FnDef(def_id, substs) = func.literal.ty.kind {
202+
let sig = tcx.fn_sig(def_id);
203+
let name = tcx.item_name(def_id);
204+
if sig.abi() != Abi::RustIntrinsic || name != sym::rustc_peek {
205+
return None;
206+
}
207+
208+
assert_eq!(args.len(), 1);
209+
let kind = PeekCallKind::from_arg_ty(substs.type_at(0));
210+
let arg = match args[0] {
211+
| Operand::Copy(Place { base: PlaceBase::Local(local), projection: box [] })
212+
| Operand::Move(Place { base: PlaceBase::Local(local), projection: box [] })
213+
=> local,
214+
215+
_ => {
216+
tcx.sess.diagnostic().span_err(
217+
span, "dataflow::sanity_check cannot feed a non-temp to rustc_peek.");
218+
return None;
232219
}
220+
};
221+
222+
return Some(PeekCall {
223+
arg,
224+
kind,
225+
span,
226+
});
227+
}
228+
}
229+
230+
None
231+
}
232+
}
233+
234+
pub trait RustcPeekAt<'tcx>: BitDenotation<'tcx> {
235+
fn peek_at(
236+
&self,
237+
tcx: TyCtxt<'tcx>,
238+
place: &mir::Place<'tcx>,
239+
flow_state: &BitSet<Self::Idx>,
240+
call: PeekCall,
241+
);
242+
}
243+
244+
impl<'tcx, O> RustcPeekAt<'tcx> for O
245+
where O: BitDenotation<'tcx, Idx = MovePathIndex> + HasMoveData<'tcx>,
246+
{
247+
fn peek_at(
248+
&self,
249+
tcx: TyCtxt<'tcx>,
250+
place: &mir::Place<'tcx>,
251+
flow_state: &BitSet<Self::Idx>,
252+
call: PeekCall,
253+
) {
254+
match self.move_data().rev_lookup.find(place.as_ref()) {
255+
LookupResult::Exact(peek_mpi) => {
256+
let bit_state = flow_state.contains(peek_mpi);
257+
debug!("rustc_peek({:?} = &{:?}) bit_state: {}",
258+
call.arg, place, bit_state);
259+
if !bit_state {
260+
tcx.sess.span_err(call.span, "rustc_peek: bit not set");
233261
}
234262
}
263+
264+
LookupResult::Parent(..) => {
265+
tcx.sess.span_err(call.span, "rustc_peek: argument untracked");
266+
}
267+
}
268+
}
269+
}
270+
271+
impl<'tcx> RustcPeekAt<'tcx> for IndirectlyMutableLocals<'_, 'tcx> {
272+
fn peek_at(
273+
&self,
274+
tcx: TyCtxt<'tcx>,
275+
place: &mir::Place<'tcx>,
276+
flow_state: &BitSet<Local>,
277+
call: PeekCall,
278+
) {
279+
warn!("peek_at: place={:?}", place);
280+
let local = match place {
281+
mir::Place { base: mir::PlaceBase::Local(l), projection: box [] } => *l,
282+
_ => {
283+
tcx.sess.span_err(call.span, "rustc_peek: argument was not a local");
284+
return;
285+
}
286+
};
287+
288+
if !flow_state.contains(local) {
289+
tcx.sess.span_err(call.span, "rustc_peek: bit not set");
235290
}
236291
}
237-
return None;
238292
}

src/libsyntax_pos/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@ symbols! {
597597
rustc_peek_definite_init,
598598
rustc_peek_maybe_init,
599599
rustc_peek_maybe_uninit,
600+
rustc_peek_indirectly_mutable,
600601
rustc_private,
601602
rustc_proc_macro_decls,
602603
rustc_promotable,

0 commit comments

Comments
 (0)