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