Skip to main content

rustc_mir_dataflow/
rustc_peek.rs

1use rustc_hir::attrs::RustcMirKind;
2use rustc_hir::find_attr;
3use rustc_middle::mir::{self, Body, Local, Location};
4use rustc_middle::ty::{self, Ty, TyCtxt};
5use rustc_span::{Span, sym};
6use tracing::{debug, info};
7
8use crate::errors::{
9    PeekArgumentNotALocal, PeekArgumentUntracked, PeekBitNotSet, PeekMustBeNotTemporary,
10    PeekMustBePlaceOrRefPlace, StopAfterDataFlowEndedCompilation,
11};
12use crate::framework::BitSetExt;
13use crate::impls::{MaybeInitializedPlaces, MaybeLiveLocals, MaybeUninitializedPlaces};
14use crate::move_paths::{HasMoveData, LookupResult, MoveData, MovePathIndex};
15use crate::{Analysis, JoinSemiLattice, ResultsCursor};
16
17pub fn sanity_check<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
18    let def_id = body.source.def_id();
19    if let Some(kind) = {

    #[allow(deprecated)]
    {
        {
            'done:
                {
                for i in tcx.get_all_attrs(def_id) {
                    #[allow(unused_imports)]
                    use rustc_hir::attrs::AttributeKind::*;
                    let i: &rustc_hir::Attribute = i;
                    match i {
                        rustc_hir::Attribute::Parsed(RustcMir(kind)) => {
                            break 'done Some(kind);
                        }
                        rustc_hir::Attribute::Unparsed(..) =>
                            {}
                            #[deny(unreachable_patterns)]
                            _ => {}
                    }
                }
                None
            }
        }
    }
}find_attr!(tcx, def_id, RustcMir(kind) => kind) {
20        let move_data = MoveData::gather_moves(body, tcx, |_| true);
21        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_dataflow/src/rustc_peek.rs:21",
                        "rustc_mir_dataflow::rustc_peek", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_dataflow/src/rustc_peek.rs"),
                        ::tracing_core::__macro_support::Option::Some(21u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_mir_dataflow::rustc_peek"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("running rustc_peek::SanityCheck on {0}",
                                                    tcx.def_path_str(def_id)) as &dyn Value))])
            });
    } else { ; }
};debug!("running rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
22        if kind.contains(&RustcMirKind::PeekMaybeInit) {
23            let flow_inits = MaybeInitializedPlaces::new(tcx, body, &move_data)
24                .iterate_to_fixpoint(tcx, body, None)
25                .into_results_cursor(body);
26            sanity_check_via_rustc_peek(tcx, flow_inits);
27        }
28
29        if kind.contains(&RustcMirKind::PeekMaybeUninit) {
30            let flow_uninits = MaybeUninitializedPlaces::new(tcx, body, &move_data)
31                .iterate_to_fixpoint(tcx, body, None)
32                .into_results_cursor(body);
33            sanity_check_via_rustc_peek(tcx, flow_uninits);
34        }
35
36        if kind.contains(&RustcMirKind::PeekLiveness) {
37            let flow_liveness =
38                MaybeLiveLocals.iterate_to_fixpoint(tcx, body, None).into_results_cursor(body);
39            sanity_check_via_rustc_peek(tcx, flow_liveness);
40        }
41
42        if kind.contains(&RustcMirKind::StopAfterDataflow) {
43            tcx.dcx().emit_fatal(StopAfterDataFlowEndedCompilation);
44        }
45    } else {
46        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_dataflow/src/rustc_peek.rs:46",
                        "rustc_mir_dataflow::rustc_peek", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_dataflow/src/rustc_peek.rs"),
                        ::tracing_core::__macro_support::Option::Some(46u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_mir_dataflow::rustc_peek"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("skipping rustc_peek::SanityCheck on {0}",
                                                    tcx.def_path_str(def_id)) as &dyn Value))])
            });
    } else { ; }
};debug!("skipping rustc_peek::SanityCheck on {}", tcx.def_path_str(def_id));
47    }
48}
49
50/// This function scans `mir` for all calls to the intrinsic
51/// `rustc_peek` that have the expression form `rustc_peek(&expr)`.
52///
53/// For each such call, determines what the dataflow bit-state is for
54/// the L-value corresponding to `expr`; if the bit-state is a 1, then
55/// that call to `rustc_peek` is ignored by the sanity check. If the
56/// bit-state is a 0, then this pass emits an error message saying
57/// "rustc_peek: bit not set".
58///
59/// The intention is that one can write unit tests for dataflow by
60/// putting code into a UI test and using `rustc_peek` to
61/// make observations about the results of dataflow static analyses.
62///
63/// (If there are any calls to `rustc_peek` that do not match the
64/// expression form above, then that emits an error as well, but those
65/// errors are not intended to be used for unit tests.)
66fn sanity_check_via_rustc_peek<'tcx, A>(tcx: TyCtxt<'tcx>, mut cursor: ResultsCursor<'_, 'tcx, A>)
67where
68    A: RustcPeekAt<'tcx>,
69{
70    let def_id = cursor.body().source.def_id();
71    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_dataflow/src/rustc_peek.rs:71",
                        "rustc_mir_dataflow::rustc_peek", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_dataflow/src/rustc_peek.rs"),
                        ::tracing_core::__macro_support::Option::Some(71u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_mir_dataflow::rustc_peek"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("sanity_check_via_rustc_peek def_id: {0:?}",
                                                    def_id) as &dyn Value))])
            });
    } else { ; }
};debug!("sanity_check_via_rustc_peek def_id: {:?}", def_id);
72
73    let peek_calls = cursor.body().basic_blocks.iter_enumerated().filter_map(|(bb, block_data)| {
74        PeekCall::from_terminator(tcx, block_data.terminator()).map(|call| (bb, block_data, call))
75    });
76
77    for (bb, block_data, call) in peek_calls {
78        // Look for a sequence like the following to indicate that we should be peeking at `_1`:
79        //    _2 = &_1;
80        //    rustc_peek(_2);
81        //
82        //    /* or */
83        //
84        //    _2 = _1;
85        //    rustc_peek(_2);
86        let (statement_index, peek_rval) = block_data
87            .statements
88            .iter()
89            .enumerate()
90            .find_map(|(i, stmt)| value_assigned_to_local(stmt, call.arg).map(|rval| (i, rval)))
91            .expect(
92                "call to rustc_peek should be preceded by \
93                    assignment to temporary holding its argument",
94            );
95
96        match (call.kind, peek_rval) {
97            (PeekCallKind::ByRef, mir::Rvalue::Ref(_, _, place))
98            | (
99                PeekCallKind::ByVal,
100                mir::Rvalue::Use(mir::Operand::Move(place) | mir::Operand::Copy(place)),
101            ) => {
102                let loc = Location { block: bb, statement_index };
103                cursor.seek_before_primary_effect(loc);
104                let state = cursor.get();
105                let analysis = cursor.analysis();
106                analysis.peek_at(tcx, *place, state, call);
107            }
108
109            _ => {
110                tcx.dcx().emit_err(PeekMustBePlaceOrRefPlace { span: call.span });
111            }
112        }
113    }
114}
115
116/// If `stmt` is an assignment where the LHS is the given local (with no projections), returns the
117/// RHS of the assignment.
118fn value_assigned_to_local<'a, 'tcx>(
119    stmt: &'a mir::Statement<'tcx>,
120    local: Local,
121) -> Option<&'a mir::Rvalue<'tcx>> {
122    if let mir::StatementKind::Assign(box (place, rvalue)) = &stmt.kind
123        && let Some(l) = place.as_local()
124        && local == l
125    {
126        return Some(&*rvalue);
127    }
128
129    None
130}
131
132#[derive(#[automatically_derived]
impl ::core::clone::Clone for PeekCallKind {
    #[inline]
    fn clone(&self) -> PeekCallKind { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for PeekCallKind { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for PeekCallKind {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                PeekCallKind::ByVal => "ByVal",
                PeekCallKind::ByRef => "ByRef",
            })
    }
}Debug)]
133enum PeekCallKind {
134    ByVal,
135    ByRef,
136}
137
138impl PeekCallKind {
139    fn from_arg_ty(arg: Ty<'_>) -> Self {
140        match arg.kind() {
141            ty::Ref(_, _, _) => PeekCallKind::ByRef,
142            _ => PeekCallKind::ByVal,
143        }
144    }
145}
146
147#[derive(#[automatically_derived]
impl ::core::clone::Clone for PeekCall {
    #[inline]
    fn clone(&self) -> PeekCall {
        let _: ::core::clone::AssertParamIsClone<Local>;
        let _: ::core::clone::AssertParamIsClone<PeekCallKind>;
        let _: ::core::clone::AssertParamIsClone<Span>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for PeekCall { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for PeekCall {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f, "PeekCall",
            "arg", &self.arg, "kind", &self.kind, "span", &&self.span)
    }
}Debug)]
148struct PeekCall {
149    arg: Local,
150    kind: PeekCallKind,
151    span: Span,
152}
153
154impl PeekCall {
155    fn from_terminator<'tcx>(
156        tcx: TyCtxt<'tcx>,
157        terminator: &mir::Terminator<'tcx>,
158    ) -> Option<Self> {
159        use mir::Operand;
160
161        let span = terminator.source_info.span;
162        if let mir::TerminatorKind::Call { func: Operand::Constant(func), args, .. } =
163            &terminator.kind
164            && let ty::FnDef(def_id, fn_args) = *func.const_.ty().kind()
165        {
166            if tcx.intrinsic(def_id)?.name != sym::rustc_peek {
167                return None;
168            }
169
170            match (&fn_args.len(), &1) {
    (left_val, right_val) => {
        if !(*left_val == *right_val) {
            let kind = ::core::panicking::AssertKind::Eq;
            ::core::panicking::assert_failed(kind, &*left_val, &*right_val,
                ::core::option::Option::None);
        }
    }
};assert_eq!(fn_args.len(), 1);
171            let kind = PeekCallKind::from_arg_ty(fn_args.type_at(0));
172            let arg = match &args[0].node {
173                Operand::Copy(place) | Operand::Move(place) => {
174                    if let Some(local) = place.as_local() {
175                        local
176                    } else {
177                        tcx.dcx().emit_err(PeekMustBeNotTemporary { span });
178                        return None;
179                    }
180                }
181                _ => {
182                    tcx.dcx().emit_err(PeekMustBeNotTemporary { span });
183                    return None;
184                }
185            };
186
187            return Some(PeekCall { arg, kind, span });
188        }
189
190        None
191    }
192}
193
194trait RustcPeekAt<'tcx>: Analysis<'tcx> {
195    fn peek_at(
196        &self,
197        tcx: TyCtxt<'tcx>,
198        place: mir::Place<'tcx>,
199        state: &Self::Domain,
200        call: PeekCall,
201    );
202}
203
204impl<'tcx, A, D> RustcPeekAt<'tcx> for A
205where
206    A: Analysis<'tcx, Domain = D> + HasMoveData<'tcx>,
207    D: JoinSemiLattice + Clone + BitSetExt<MovePathIndex>,
208{
209    fn peek_at(
210        &self,
211        tcx: TyCtxt<'tcx>,
212        place: mir::Place<'tcx>,
213        state: &Self::Domain,
214        call: PeekCall,
215    ) {
216        match self.move_data().rev_lookup.find(place.as_ref()) {
217            LookupResult::Exact(peek_mpi) => {
218                let bit_state = state.contains(peek_mpi);
219                {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_dataflow/src/rustc_peek.rs:219",
                        "rustc_mir_dataflow::rustc_peek", ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_dataflow/src/rustc_peek.rs"),
                        ::tracing_core::__macro_support::Option::Some(219u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_mir_dataflow::rustc_peek"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("rustc_peek({0:?} = &{1:?}) bit_state: {2}",
                                                    call.arg, place, bit_state) as &dyn Value))])
            });
    } else { ; }
};debug!("rustc_peek({:?} = &{:?}) bit_state: {}", call.arg, place, bit_state);
220                if !bit_state {
221                    tcx.dcx().emit_err(PeekBitNotSet { span: call.span });
222                }
223            }
224
225            LookupResult::Parent(..) => {
226                tcx.dcx().emit_err(PeekArgumentUntracked { span: call.span });
227            }
228        }
229    }
230}
231
232impl<'tcx> RustcPeekAt<'tcx> for MaybeLiveLocals {
233    fn peek_at(
234        &self,
235        tcx: TyCtxt<'tcx>,
236        place: mir::Place<'tcx>,
237        state: &Self::Domain,
238        call: PeekCall,
239    ) {
240        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_dataflow/src/rustc_peek.rs:240",
                        "rustc_mir_dataflow::rustc_peek", ::tracing::Level::INFO,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_dataflow/src/rustc_peek.rs"),
                        ::tracing_core::__macro_support::Option::Some(240u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_mir_dataflow::rustc_peek"),
                        ::tracing_core::field::FieldSet::new(&["message", "place"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::INFO <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::INFO <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("peek_at")
                                            as &dyn Value)),
                                (&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&debug(&place) as
                                            &dyn Value))])
            });
    } else { ; }
};info!(?place, "peek_at");
241        let Some(local) = place.as_local() else {
242            tcx.dcx().emit_err(PeekArgumentNotALocal { span: call.span });
243            return;
244        };
245
246        if !state.contains(local) {
247            tcx.dcx().emit_err(PeekBitNotSet { span: call.span });
248        }
249    }
250}