]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_mir_dataflow/src/rustc_peek.rs
New upstream version 1.64.0+dfsg1
[rustc.git] / compiler / rustc_mir_dataflow / src / rustc_peek.rs
CommitLineData
dfeec247
XL
1use rustc_span::symbol::sym;
2use rustc_span::Span;
3157f602 3
923072b8 4use rustc_index::bit_set::ChunkedBitSet;
c295e0f8 5use rustc_middle::mir::MirPass;
f9f354fc 6use rustc_middle::mir::{self, Body, Local, Location};
ba9703b0 7use rustc_middle::ty::{self, Ty, TyCtxt};
3157f602 8
5e7ed085 9use crate::framework::BitSetExt;
c295e0f8 10use crate::impls::{
3c0e092e 11 DefinitelyInitializedPlaces, MaybeInitializedPlaces, MaybeLiveLocals, MaybeUninitializedPlaces,
f9f354fc 12};
c295e0f8
XL
13use crate::move_paths::{HasMoveData, MoveData};
14use crate::move_paths::{LookupResult, MovePathIndex};
15use crate::MoveDataParamEnv;
16use crate::{Analysis, JoinSemiLattice, Results, ResultsCursor};
041b39d2 17
041b39d2
XL
18pub struct SanityCheck;
19
a2a8927a 20// FIXME: This should be a `MirLint`, but it needs to be moved back to `rustc_mir_transform` first.
e1599b0c 21impl<'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 88pub 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.
146fn 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)]
162enum PeekCallKind {
163 ByVal,
164 ByRef,
165}
166
167impl 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)]
177pub struct PeekCall {
178 arg: Local,
179 kind: PeekCallKind,
180 span: Span,
181}
182
183impl 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 231pub 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 241impl<'tcx, A, D> RustcPeekAt<'tcx> for A
dfeec247 242where
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
269impl<'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}