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