]>
Commit | Line | Data |
---|---|---|
1b1a35ee XL |
1 | use std::borrow::Borrow; |
2 | ||
3 | use rustc_ast::ast; | |
dfeec247 XL |
4 | use rustc_span::symbol::sym; |
5 | use rustc_span::Span; | |
6 | use rustc_target::spec::abi::Abi; | |
3157f602 | 7 | |
29967ef6 | 8 | use crate::transform::MirPass; |
dfeec247 | 9 | use rustc_index::bit_set::BitSet; |
f9f354fc | 10 | use rustc_middle::mir::{self, Body, Local, Location}; |
ba9703b0 | 11 | use rustc_middle::ty::{self, Ty, TyCtxt}; |
3157f602 | 12 | |
f9f354fc XL |
13 | use crate::dataflow::impls::{ |
14 | DefinitelyInitializedPlaces, MaybeInitializedPlaces, MaybeLiveLocals, MaybeMutBorrowedLocals, | |
15 | MaybeUninitializedPlaces, | |
16 | }; | |
dfeec247 XL |
17 | use crate::dataflow::move_paths::{HasMoveData, MoveData}; |
18 | use crate::dataflow::move_paths::{LookupResult, MovePathIndex}; | |
dfeec247 | 19 | use crate::dataflow::MoveDataParamEnv; |
1b1a35ee | 20 | use crate::dataflow::{Analysis, JoinSemiLattice, Results, ResultsCursor}; |
041b39d2 | 21 | |
041b39d2 XL |
22 | pub struct SanityCheck; |
23 | ||
e1599b0c | 24 | impl<'tcx> MirPass<'tcx> for SanityCheck { |
29967ef6 | 25 | fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { |
74b04a01 | 26 | use crate::dataflow::has_rustc_mir_with; |
29967ef6 | 27 | let def_id = body.source.def_id(); |
48663c56 | 28 | if !tcx.has_attr(def_id, sym::rustc_mir) { |
532ac7d7 | 29 | debug!("skipping rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id)); |
041b39d2 XL |
30 | return; |
31 | } else { | |
532ac7d7 | 32 | debug!("running rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id)); |
041b39d2 XL |
33 | } |
34 | ||
35 | let attributes = tcx.get_attrs(def_id); | |
36 | let param_env = tcx.param_env(def_id); | |
60c5eb7d | 37 | let move_data = MoveData::gather_moves(body, tcx, param_env).unwrap(); |
74b04a01 | 38 | let mdpe = MoveDataParamEnv { move_data, param_env }; |
3dfed10e | 39 | let sess = &tcx.sess; |
74b04a01 | 40 | |
3dfed10e | 41 | if has_rustc_mir_with(sess, &attributes, sym::rustc_peek_maybe_init).is_some() { |
f9f354fc | 42 | let flow_inits = MaybeInitializedPlaces::new(tcx, body, &mdpe) |
29967ef6 | 43 | .into_engine(tcx, body) |
f9f354fc XL |
44 | .iterate_to_fixpoint(); |
45 | ||
29967ef6 | 46 | sanity_check_via_rustc_peek(tcx, body, &attributes, &flow_inits); |
041b39d2 | 47 | } |
f9f354fc | 48 | |
3dfed10e | 49 | if has_rustc_mir_with(sess, &attributes, sym::rustc_peek_maybe_uninit).is_some() { |
f9f354fc | 50 | let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &mdpe) |
29967ef6 | 51 | .into_engine(tcx, body) |
f9f354fc XL |
52 | .iterate_to_fixpoint(); |
53 | ||
29967ef6 | 54 | sanity_check_via_rustc_peek(tcx, body, &attributes, &flow_uninits); |
041b39d2 | 55 | } |
f9f354fc | 56 | |
3dfed10e | 57 | if has_rustc_mir_with(sess, &attributes, sym::rustc_peek_definite_init).is_some() { |
f9f354fc | 58 | let flow_def_inits = DefinitelyInitializedPlaces::new(tcx, body, &mdpe) |
29967ef6 | 59 | .into_engine(tcx, body) |
f9f354fc XL |
60 | .iterate_to_fixpoint(); |
61 | ||
29967ef6 | 62 | sanity_check_via_rustc_peek(tcx, body, &attributes, &flow_def_inits); |
041b39d2 | 63 | } |
f9f354fc | 64 | |
3dfed10e | 65 | if has_rustc_mir_with(sess, &attributes, sym::rustc_peek_indirectly_mutable).is_some() { |
f9f354fc | 66 | let flow_mut_borrowed = MaybeMutBorrowedLocals::mut_borrows_only(tcx, body, param_env) |
29967ef6 | 67 | .into_engine(tcx, body) |
f9f354fc XL |
68 | .iterate_to_fixpoint(); |
69 | ||
29967ef6 | 70 | sanity_check_via_rustc_peek(tcx, body, &attributes, &flow_mut_borrowed); |
e74abb32 | 71 | } |
f9f354fc | 72 | |
3dfed10e | 73 | if has_rustc_mir_with(sess, &attributes, sym::rustc_peek_liveness).is_some() { |
29967ef6 | 74 | let flow_liveness = MaybeLiveLocals.into_engine(tcx, body).iterate_to_fixpoint(); |
f9f354fc | 75 | |
29967ef6 | 76 | sanity_check_via_rustc_peek(tcx, body, &attributes, &flow_liveness); |
f9f354fc XL |
77 | } |
78 | ||
3dfed10e | 79 | if has_rustc_mir_with(sess, &attributes, sym::stop_after_dataflow).is_some() { |
041b39d2 XL |
80 | tcx.sess.fatal("stop_after_dataflow ended compilation"); |
81 | } | |
82 | } | |
83 | } | |
3157f602 XL |
84 | |
85 | /// This function scans `mir` for all calls to the intrinsic | |
86 | /// `rustc_peek` that have the expression form `rustc_peek(&expr)`. | |
87 | /// | |
88 | /// For each such call, determines what the dataflow bit-state is for | |
89 | /// the L-value corresponding to `expr`; if the bit-state is a 1, then | |
90 | /// that call to `rustc_peek` is ignored by the sanity check. If the | |
91 | /// bit-state is a 0, then this pass emits a error message saying | |
92 | /// "rustc_peek: bit not set". | |
93 | /// | |
94 | /// The intention is that one can write unit tests for dataflow by | |
95 | /// putting code into a compile-fail test and using `rustc_peek` to | |
96 | /// make observations about the results of dataflow static analyses. | |
97 | /// | |
98 | /// (If there are any calls to `rustc_peek` that do not match the | |
99 | /// expression form above, then that emits an error as well, but those | |
100 | /// errors are not intended to be used for unit tests.) | |
74b04a01 | 101 | pub fn sanity_check_via_rustc_peek<'tcx, A>( |
dc9dc135 XL |
102 | tcx: TyCtxt<'tcx>, |
103 | body: &Body<'tcx>, | |
dc9dc135 | 104 | _attributes: &[ast::Attribute], |
74b04a01 | 105 | results: &Results<'tcx, A>, |
dfeec247 | 106 | ) where |
74b04a01 | 107 | A: RustcPeekAt<'tcx>, |
dfeec247 | 108 | { |
29967ef6 | 109 | let def_id = body.source.def_id(); |
532ac7d7 | 110 | debug!("sanity_check_via_rustc_peek def_id: {:?}", def_id); |
3157f602 | 111 | |
74b04a01 | 112 | let mut cursor = ResultsCursor::new(body, results); |
e74abb32 | 113 | |
dfeec247 XL |
114 | let peek_calls = body.basic_blocks().iter_enumerated().filter_map(|(bb, block_data)| { |
115 | PeekCall::from_terminator(tcx, block_data.terminator()).map(|call| (bb, block_data, call)) | |
116 | }); | |
e74abb32 XL |
117 | |
118 | for (bb, block_data, call) in peek_calls { | |
119 | // Look for a sequence like the following to indicate that we should be peeking at `_1`: | |
120 | // _2 = &_1; | |
121 | // rustc_peek(_2); | |
122 | // | |
123 | // /* or */ | |
124 | // | |
125 | // _2 = _1; | |
126 | // rustc_peek(_2); | |
127 | let (statement_index, peek_rval) = block_data | |
128 | .statements | |
129 | .iter() | |
130 | .enumerate() | |
f9f354fc | 131 | .find_map(|(i, stmt)| value_assigned_to_local(stmt, call.arg).map(|rval| (i, rval))) |
dfeec247 XL |
132 | .expect( |
133 | "call to rustc_peek should be preceded by \ | |
134 | assignment to temporary holding its argument", | |
135 | ); | |
e74abb32 XL |
136 | |
137 | match (call.kind, peek_rval) { | |
dfeec247 | 138 | (PeekCallKind::ByRef, mir::Rvalue::Ref(_, _, place)) |
ba9703b0 XL |
139 | | ( |
140 | PeekCallKind::ByVal, | |
141 | mir::Rvalue::Use(mir::Operand::Move(place) | mir::Operand::Copy(place)), | |
142 | ) => { | |
e74abb32 | 143 | let loc = Location { block: bb, statement_index }; |
f9f354fc | 144 | cursor.seek_before_primary_effect(loc); |
e74abb32 | 145 | let state = cursor.get(); |
ba9703b0 | 146 | results.analysis.peek_at(tcx, *place, state, call); |
e74abb32 XL |
147 | } |
148 | ||
149 | _ => { | |
150 | let msg = "rustc_peek: argument expression \ | |
151 | must be either `place` or `&place`"; | |
152 | tcx.sess.span_err(call.span, msg); | |
153 | } | |
154 | } | |
3157f602 XL |
155 | } |
156 | } | |
157 | ||
e74abb32 XL |
158 | /// If `stmt` is an assignment where the LHS is the given local (with no projections), returns the |
159 | /// RHS of the assignment. | |
160 | fn value_assigned_to_local<'a, 'tcx>( | |
161 | stmt: &'a mir::Statement<'tcx>, | |
162 | local: Local, | |
163 | ) -> Option<&'a mir::Rvalue<'tcx>> { | |
164 | if let mir::StatementKind::Assign(box (place, rvalue)) = &stmt.kind { | |
165 | if let Some(l) = place.as_local() { | |
166 | if local == l { | |
167 | return Some(&*rvalue); | |
3157f602 | 168 | } |
e74abb32 XL |
169 | } |
170 | } | |
3157f602 | 171 | |
e74abb32 XL |
172 | None |
173 | } | |
174 | ||
175 | #[derive(Clone, Copy, Debug)] | |
176 | enum PeekCallKind { | |
177 | ByVal, | |
178 | ByRef, | |
179 | } | |
180 | ||
181 | impl PeekCallKind { | |
182 | fn from_arg_ty(arg: Ty<'_>) -> Self { | |
1b1a35ee | 183 | match arg.kind() { |
e74abb32 XL |
184 | ty::Ref(_, _, _) => PeekCallKind::ByRef, |
185 | _ => PeekCallKind::ByVal, | |
186 | } | |
187 | } | |
188 | } | |
189 | ||
190 | #[derive(Clone, Copy, Debug)] | |
191 | pub struct PeekCall { | |
192 | arg: Local, | |
193 | kind: PeekCallKind, | |
194 | span: Span, | |
195 | } | |
196 | ||
197 | impl PeekCall { | |
198 | fn from_terminator<'tcx>( | |
199 | tcx: TyCtxt<'tcx>, | |
200 | terminator: &mir::Terminator<'tcx>, | |
201 | ) -> Option<Self> { | |
202 | use mir::Operand; | |
203 | ||
204 | let span = terminator.source_info.span; | |
205 | if let mir::TerminatorKind::Call { func: Operand::Constant(func), args, .. } = | |
206 | &terminator.kind | |
207 | { | |
1b1a35ee | 208 | if let ty::FnDef(def_id, substs) = *func.literal.ty.kind() { |
e74abb32 XL |
209 | let sig = tcx.fn_sig(def_id); |
210 | let name = tcx.item_name(def_id); | |
211 | if sig.abi() != Abi::RustIntrinsic || name != sym::rustc_peek { | |
212 | return None; | |
213 | } | |
214 | ||
215 | assert_eq!(args.len(), 1); | |
216 | let kind = PeekCallKind::from_arg_ty(substs.type_at(0)); | |
217 | let arg = match &args[0] { | |
218 | Operand::Copy(place) | Operand::Move(place) => { | |
219 | if let Some(local) = place.as_local() { | |
220 | local | |
221 | } else { | |
222 | tcx.sess.diagnostic().span_err( | |
223 | span, | |
224 | "dataflow::sanity_check cannot feed a non-temp to rustc_peek.", | |
225 | ); | |
226 | return None; | |
9e0c209e SL |
227 | } |
228 | } | |
e74abb32 XL |
229 | _ => { |
230 | tcx.sess.diagnostic().span_err( | |
231 | span, | |
232 | "dataflow::sanity_check cannot feed a non-temp to rustc_peek.", | |
233 | ); | |
234 | return None; | |
9e0c209e | 235 | } |
e74abb32 XL |
236 | }; |
237 | ||
dfeec247 | 238 | return Some(PeekCall { arg, kind, span }); |
3157f602 XL |
239 | } |
240 | } | |
241 | ||
e74abb32 | 242 | None |
3157f602 | 243 | } |
e74abb32 | 244 | } |
3157f602 | 245 | |
74b04a01 | 246 | pub trait RustcPeekAt<'tcx>: Analysis<'tcx> { |
e74abb32 XL |
247 | fn peek_at( |
248 | &self, | |
249 | tcx: TyCtxt<'tcx>, | |
ba9703b0 | 250 | place: mir::Place<'tcx>, |
1b1a35ee | 251 | flow_state: &Self::Domain, |
e74abb32 XL |
252 | call: PeekCall, |
253 | ); | |
3157f602 XL |
254 | } |
255 | ||
1b1a35ee | 256 | impl<'tcx, A, D> RustcPeekAt<'tcx> for A |
dfeec247 | 257 | where |
1b1a35ee XL |
258 | A: Analysis<'tcx, Domain = D> + HasMoveData<'tcx>, |
259 | D: JoinSemiLattice + Clone + Borrow<BitSet<MovePathIndex>>, | |
e74abb32 XL |
260 | { |
261 | fn peek_at( | |
262 | &self, | |
263 | tcx: TyCtxt<'tcx>, | |
ba9703b0 | 264 | place: mir::Place<'tcx>, |
1b1a35ee | 265 | flow_state: &Self::Domain, |
e74abb32 XL |
266 | call: PeekCall, |
267 | ) { | |
268 | match self.move_data().rev_lookup.find(place.as_ref()) { | |
269 | LookupResult::Exact(peek_mpi) => { | |
1b1a35ee | 270 | let bit_state = flow_state.borrow().contains(peek_mpi); |
dfeec247 | 271 | debug!("rustc_peek({:?} = &{:?}) bit_state: {}", call.arg, place, bit_state); |
e74abb32 XL |
272 | if !bit_state { |
273 | tcx.sess.span_err(call.span, "rustc_peek: bit not set"); | |
3157f602 XL |
274 | } |
275 | } | |
e74abb32 XL |
276 | |
277 | LookupResult::Parent(..) => { | |
278 | tcx.sess.span_err(call.span, "rustc_peek: argument untracked"); | |
279 | } | |
280 | } | |
281 | } | |
282 | } | |
283 | ||
74b04a01 | 284 | impl<'tcx> RustcPeekAt<'tcx> for MaybeMutBorrowedLocals<'_, 'tcx> { |
e74abb32 XL |
285 | fn peek_at( |
286 | &self, | |
287 | tcx: TyCtxt<'tcx>, | |
ba9703b0 | 288 | place: mir::Place<'tcx>, |
e74abb32 XL |
289 | flow_state: &BitSet<Local>, |
290 | call: PeekCall, | |
291 | ) { | |
292 | warn!("peek_at: place={:?}", place); | |
293 | let local = if let Some(l) = place.as_local() { | |
294 | l | |
295 | } else { | |
296 | tcx.sess.span_err(call.span, "rustc_peek: argument was not a local"); | |
297 | return; | |
298 | }; | |
299 | ||
300 | if !flow_state.contains(local) { | |
301 | tcx.sess.span_err(call.span, "rustc_peek: bit not set"); | |
3157f602 XL |
302 | } |
303 | } | |
3157f602 | 304 | } |
f9f354fc XL |
305 | |
306 | impl<'tcx> RustcPeekAt<'tcx> for MaybeLiveLocals { | |
307 | fn peek_at( | |
308 | &self, | |
309 | tcx: TyCtxt<'tcx>, | |
310 | place: mir::Place<'tcx>, | |
311 | flow_state: &BitSet<Local>, | |
312 | call: PeekCall, | |
313 | ) { | |
314 | warn!("peek_at: place={:?}", place); | |
315 | let local = if let Some(l) = place.as_local() { | |
316 | l | |
317 | } else { | |
318 | tcx.sess.span_err(call.span, "rustc_peek: argument was not a local"); | |
319 | return; | |
320 | }; | |
321 | ||
322 | if !flow_state.contains(local) { | |
323 | tcx.sess.span_err(call.span, "rustc_peek: bit not set"); | |
324 | } | |
325 | } | |
326 | } |