Skip to main content

rustc_incremental/
assert_dep_graph.rs

1//! This pass is only used for the UNIT TESTS and DEBUGGING NEEDS
2//! around dependency graph construction. It serves two purposes; it
3//! will dump graphs in graphviz form to disk, and it searches for
4//! `#[rustc_if_this_changed]` and `#[rustc_then_this_would_need]`
5//! annotations. These annotations can be used to test whether paths
6//! exist in the graph. These checks run after codegen, so they view the
7//! the final state of the dependency graph. Note that there are
8//! similar assertions found in `persist::clean` which check the
9//! **initial** state of the dependency graph, just after it has been
10//! loaded from disk.
11//!
12//! In this code, we report errors on each `rustc_if_this_changed`
13//! annotation. If a path exists in all cases, then we would report
14//! "all path(s) exist". Otherwise, we report: "no path to `foo`" for
15//! each case where no path exists. `ui` tests can then be
16//! used to check when paths exist or do not.
17//!
18//! The full form of the `rustc_if_this_changed` annotation is
19//! `#[rustc_if_this_changed("foo")]`, which will report a
20//! source node of `foo(def_id)`. The `"foo"` is optional and
21//! defaults to `"Hir"` if omitted.
22//!
23//! Example:
24//!
25//! ```ignore (needs flags)
26//! #[rustc_if_this_changed(Hir)]
27//! fn foo() { }
28//!
29//! #[rustc_then_this_would_need(codegen)] //~ ERROR no path from `foo`
30//! fn bar() { }
31//!
32//! #[rustc_then_this_would_need(codegen)] //~ ERROR OK
33//! fn baz() { foo(); }
34//! ```
35
36use std::env;
37use std::fs::{self, File};
38use std::io::Write;
39
40use rustc_data_structures::fx::FxIndexSet;
41use rustc_data_structures::graph::linked_graph::{Direction, INCOMING, NodeIndex, OUTGOING};
42use rustc_hir::Attribute;
43use rustc_hir::attrs::AttributeKind;
44use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LocalDefId};
45use rustc_hir::intravisit::{self, Visitor};
46use rustc_middle::bug;
47use rustc_middle::dep_graph::{
48    DepGraphQuery, DepKind, DepNode, DepNodeExt, DepNodeFilter, EdgeFilter, dep_kinds,
49};
50use rustc_middle::hir::nested_filter;
51use rustc_middle::ty::TyCtxt;
52use rustc_span::{Span, Symbol, sym};
53use tracing::debug;
54use {rustc_graphviz as dot, rustc_hir as hir};
55
56use crate::errors;
57
58#[allow(missing_docs)]
59pub(crate) fn assert_dep_graph(tcx: TyCtxt<'_>) {
60    tcx.dep_graph.with_ignore(|| {
61        if tcx.sess.opts.unstable_opts.dump_dep_graph {
62            tcx.dep_graph.with_query(dump_graph);
63        }
64
65        if !tcx.sess.opts.unstable_opts.query_dep_graph {
66            return;
67        }
68
69        // if the `rustc_attrs` feature is not enabled, then the
70        // attributes we are interested in cannot be present anyway, so
71        // skip the walk.
72        if !tcx.features().rustc_attrs() {
73            return;
74        }
75
76        // Find annotations supplied by user (if any).
77        let (if_this_changed, then_this_would_need) = {
78            let mut visitor =
79                IfThisChanged { tcx, if_this_changed: ::alloc::vec::Vec::new()vec![], then_this_would_need: ::alloc::vec::Vec::new()vec![] };
80            visitor.process_attrs(CRATE_DEF_ID);
81            tcx.hir_visit_all_item_likes_in_crate(&mut visitor);
82            (visitor.if_this_changed, visitor.then_this_would_need)
83        };
84
85        if !if_this_changed.is_empty() || !then_this_would_need.is_empty() {
86            if !tcx.sess.opts.unstable_opts.query_dep_graph {
    {
        ::core::panicking::panic_fmt(format_args!("cannot use the `#[{0}]` or `#[{1}]` annotations without supplying `-Z query-dep-graph`",
                sym::rustc_if_this_changed, sym::rustc_then_this_would_need));
    }
};assert!(
87                tcx.sess.opts.unstable_opts.query_dep_graph,
88                "cannot use the `#[{}]` or `#[{}]` annotations \
89                    without supplying `-Z query-dep-graph`",
90                sym::rustc_if_this_changed,
91                sym::rustc_then_this_would_need
92            );
93        }
94
95        // Check paths.
96        check_paths(tcx, &if_this_changed, &then_this_would_need);
97    })
98}
99
100type Sources = Vec<(Span, DefId, DepNode)>;
101type Targets = Vec<(Span, Symbol, hir::HirId, DepNode)>;
102
103struct IfThisChanged<'tcx> {
104    tcx: TyCtxt<'tcx>,
105    if_this_changed: Sources,
106    then_this_would_need: Targets,
107}
108
109impl<'tcx> IfThisChanged<'tcx> {
110    fn process_attrs(&mut self, def_id: LocalDefId) {
111        let def_path_hash = self.tcx.def_path_hash(def_id.to_def_id());
112        let hir_id = self.tcx.local_def_id_to_hir_id(def_id);
113        let attrs = self.tcx.hir_attrs(hir_id);
114        for attr in attrs {
115            if let Attribute::Parsed(AttributeKind::RustcIfThisChanged(span, dep_node)) = *attr {
116                let dep_node = match dep_node {
117                    None => DepNode::from_def_path_hash(
118                        self.tcx,
119                        def_path_hash,
120                        dep_kinds::opt_hir_owner_nodes,
121                    ),
122                    Some(n) => {
123                        match DepNode::from_label_string(self.tcx, n.as_str(), def_path_hash) {
124                            Ok(n) => n,
125                            Err(()) => self
126                                .tcx
127                                .dcx()
128                                .emit_fatal(errors::UnrecognizedDepNode { span, name: n }),
129                        }
130                    }
131                };
132                self.if_this_changed.push((span, def_id.to_def_id(), dep_node));
133            } else if let Attribute::Parsed(AttributeKind::RustcThenThisWouldNeed(
134                _,
135                ref dep_nodes,
136            )) = *attr
137            {
138                for &n in dep_nodes {
139                    let Ok(dep_node) =
140                        DepNode::from_label_string(self.tcx, n.as_str(), def_path_hash)
141                    else {
142                        self.tcx
143                            .dcx()
144                            .emit_fatal(errors::UnrecognizedDepNode { span: n.span, name: n.name });
145                    };
146                    self.then_this_would_need.push((n.span, n.name, hir_id, dep_node));
147                }
148            }
149        }
150    }
151}
152
153impl<'tcx> Visitor<'tcx> for IfThisChanged<'tcx> {
154    type NestedFilter = nested_filter::OnlyBodies;
155
156    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
157        self.tcx
158    }
159
160    fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) {
161        self.process_attrs(item.owner_id.def_id);
162        intravisit::walk_item(self, item);
163    }
164
165    fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
166        self.process_attrs(trait_item.owner_id.def_id);
167        intravisit::walk_trait_item(self, trait_item);
168    }
169
170    fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
171        self.process_attrs(impl_item.owner_id.def_id);
172        intravisit::walk_impl_item(self, impl_item);
173    }
174
175    fn visit_field_def(&mut self, s: &'tcx hir::FieldDef<'tcx>) {
176        self.process_attrs(s.def_id);
177        intravisit::walk_field_def(self, s);
178    }
179}
180
181fn check_paths<'tcx>(tcx: TyCtxt<'tcx>, if_this_changed: &Sources, then_this_would_need: &Targets) {
182    // Return early here so as not to construct the query, which is not cheap.
183    if if_this_changed.is_empty() {
184        for &(target_span, _, _, _) in then_this_would_need {
185            tcx.dcx().emit_err(errors::MissingIfThisChanged { span: target_span });
186        }
187        return;
188    }
189    tcx.dep_graph.with_query(|query| {
190        for &(_, source_def_id, ref source_dep_node) in if_this_changed {
191            let dependents = query.transitive_predecessors(source_dep_node);
192            for &(target_span, ref target_pass, _, ref target_dep_node) in then_this_would_need {
193                if !dependents.contains(&target_dep_node) {
194                    tcx.dcx().emit_err(errors::NoPath {
195                        span: target_span,
196                        source: tcx.def_path_str(source_def_id),
197                        target: *target_pass,
198                    });
199                } else {
200                    tcx.dcx().emit_err(errors::Ok { span: target_span });
201                }
202            }
203        }
204    });
205}
206
207fn dump_graph(query: &DepGraphQuery) {
208    let path: String = env::var("RUST_DEP_GRAPH").unwrap_or_else(|_| "dep_graph".to_string());
209
210    let nodes = match env::var("RUST_DEP_GRAPH_FILTER") {
211        Ok(string) => {
212            // Expect one of: "-> target", "source -> target", or "source ->".
213            let edge_filter =
214                EdgeFilter::new(&string).unwrap_or_else(|e| ::rustc_middle::util::bug::bug_fmt(format_args!("invalid filter: {0}", e))bug!("invalid filter: {}", e));
215            let sources = node_set(query, &edge_filter.source);
216            let targets = node_set(query, &edge_filter.target);
217            filter_nodes(query, &sources, &targets)
218        }
219        Err(_) => query.nodes().into_iter().map(|n| n.kind).collect(),
220    };
221    let edges = filter_edges(query, &nodes);
222
223    {
224        // dump a .txt file with just the edges:
225        let txt_path = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}.txt", path))
    })format!("{path}.txt");
226        let mut file = File::create_buffered(&txt_path).unwrap();
227        for (source, target) in &edges {
228            file.write_fmt(format_args!("{0:?} -> {1:?}\n", source, target))write!(file, "{source:?} -> {target:?}\n").unwrap();
229        }
230    }
231
232    {
233        // dump a .dot file in graphviz format:
234        let dot_path = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}.dot", path))
    })format!("{path}.dot");
235        let mut v = Vec::new();
236        dot::render(&GraphvizDepGraph(nodes, edges), &mut v).unwrap();
237        fs::write(dot_path, v).unwrap();
238    }
239}
240
241#[allow(missing_docs)]
242struct GraphvizDepGraph(FxIndexSet<DepKind>, Vec<(DepKind, DepKind)>);
243
244impl<'a> dot::GraphWalk<'a> for GraphvizDepGraph {
245    type Node = DepKind;
246    type Edge = (DepKind, DepKind);
247    fn nodes(&self) -> dot::Nodes<'_, DepKind> {
248        let nodes: Vec<_> = self.0.iter().cloned().collect();
249        nodes.into()
250    }
251    fn edges(&self) -> dot::Edges<'_, (DepKind, DepKind)> {
252        self.1[..].into()
253    }
254    fn source(&self, edge: &(DepKind, DepKind)) -> DepKind {
255        edge.0
256    }
257    fn target(&self, edge: &(DepKind, DepKind)) -> DepKind {
258        edge.1
259    }
260}
261
262impl<'a> dot::Labeller<'a> for GraphvizDepGraph {
263    type Node = DepKind;
264    type Edge = (DepKind, DepKind);
265    fn graph_id(&self) -> dot::Id<'_> {
266        dot::Id::new("DependencyGraph").unwrap()
267    }
268    fn node_id(&self, n: &DepKind) -> dot::Id<'_> {
269        let s: String = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:?}", n))
    })format!("{n:?}")
270            .chars()
271            .map(|c| if c == '_' || c.is_alphanumeric() { c } else { '_' })
272            .collect();
273        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_incremental/src/assert_dep_graph.rs:273",
                        "rustc_incremental::assert_dep_graph",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_incremental/src/assert_dep_graph.rs"),
                        ::tracing_core::__macro_support::Option::Some(273u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_incremental::assert_dep_graph"),
                        ::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!("n={0:?} s={1:?}",
                                                    n, s) as &dyn Value))])
            });
    } else { ; }
};debug!("n={:?} s={:?}", n, s);
274        dot::Id::new(s).unwrap()
275    }
276    fn node_label(&self, n: &DepKind) -> dot::LabelText<'_> {
277        dot::LabelText::label(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:?}", n))
    })format!("{n:?}"))
278    }
279}
280
281// Given an optional filter like `"x,y,z"`, returns either `None` (no
282// filter) or the set of nodes whose labels contain all of those
283// substrings.
284fn node_set<'q>(
285    query: &'q DepGraphQuery,
286    filter: &DepNodeFilter,
287) -> Option<FxIndexSet<&'q DepNode>> {
288    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_incremental/src/assert_dep_graph.rs:288",
                        "rustc_incremental::assert_dep_graph",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_incremental/src/assert_dep_graph.rs"),
                        ::tracing_core::__macro_support::Option::Some(288u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_incremental::assert_dep_graph"),
                        ::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!("node_set(filter={0:?})",
                                                    filter) as &dyn Value))])
            });
    } else { ; }
};debug!("node_set(filter={:?})", filter);
289
290    if filter.accepts_all() {
291        return None;
292    }
293
294    Some(query.nodes().into_iter().filter(|n| filter.test(n)).collect())
295}
296
297fn filter_nodes<'q>(
298    query: &'q DepGraphQuery,
299    sources: &Option<FxIndexSet<&'q DepNode>>,
300    targets: &Option<FxIndexSet<&'q DepNode>>,
301) -> FxIndexSet<DepKind> {
302    if let Some(sources) = sources {
303        if let Some(targets) = targets {
304            walk_between(query, sources, targets)
305        } else {
306            walk_nodes(query, sources, OUTGOING)
307        }
308    } else if let Some(targets) = targets {
309        walk_nodes(query, targets, INCOMING)
310    } else {
311        query.nodes().into_iter().map(|n| n.kind).collect()
312    }
313}
314
315fn walk_nodes<'q>(
316    query: &'q DepGraphQuery,
317    starts: &FxIndexSet<&'q DepNode>,
318    direction: Direction,
319) -> FxIndexSet<DepKind> {
320    let mut set = FxIndexSet::default();
321    for &start in starts {
322        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_incremental/src/assert_dep_graph.rs:322",
                        "rustc_incremental::assert_dep_graph",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_incremental/src/assert_dep_graph.rs"),
                        ::tracing_core::__macro_support::Option::Some(322u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_incremental::assert_dep_graph"),
                        ::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!("walk_nodes: start={0:?} outgoing?={1:?}",
                                                    start, direction == OUTGOING) as &dyn Value))])
            });
    } else { ; }
};debug!("walk_nodes: start={:?} outgoing?={:?}", start, direction == OUTGOING);
323        if set.insert(start.kind) {
324            let mut stack = <[_]>::into_vec(::alloc::boxed::box_new([query.indices[start]]))vec![query.indices[start]];
325            while let Some(index) = stack.pop() {
326                for (_, edge) in query.graph.adjacent_edges(index, direction) {
327                    let neighbor_index = edge.source_or_target(direction);
328                    let neighbor = query.graph.node_data(neighbor_index);
329                    if set.insert(neighbor.kind) {
330                        stack.push(neighbor_index);
331                    }
332                }
333            }
334        }
335    }
336    set
337}
338
339fn walk_between<'q>(
340    query: &'q DepGraphQuery,
341    sources: &FxIndexSet<&'q DepNode>,
342    targets: &FxIndexSet<&'q DepNode>,
343) -> FxIndexSet<DepKind> {
344    // This is a bit tricky. We want to include a node only if it is:
345    // (a) reachable from a source and (b) will reach a target. And we
346    // have to be careful about cycles etc. Luckily efficiency is not
347    // a big concern!
348
349    #[derive(#[automatically_derived]
impl ::core::marker::Copy for State { }Copy, #[automatically_derived]
impl ::core::clone::Clone for State {
    #[inline]
    fn clone(&self) -> State { *self }
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for State {
    #[inline]
    fn eq(&self, other: &State) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq)]
350    enum State {
351        Undecided,
352        Deciding,
353        Included,
354        Excluded,
355    }
356
357    let mut node_states = ::alloc::vec::from_elem(State::Undecided, query.graph.len_nodes())vec![State::Undecided; query.graph.len_nodes()];
358
359    for &target in targets {
360        node_states[query.indices[target].0] = State::Included;
361    }
362
363    for source in sources.iter().map(|&n| query.indices[n]) {
364        recurse(query, &mut node_states, source);
365    }
366
367    return query
368        .nodes()
369        .into_iter()
370        .filter(|&n| {
371            let index = query.indices[n];
372            node_states[index.0] == State::Included
373        })
374        .map(|n| n.kind)
375        .collect();
376
377    fn recurse(query: &DepGraphQuery, node_states: &mut [State], node: NodeIndex) -> bool {
378        match node_states[node.0] {
379            // known to reach a target
380            State::Included => return true,
381
382            // known not to reach a target
383            State::Excluded => return false,
384
385            // backedge, not yet known, say false
386            State::Deciding => return false,
387
388            State::Undecided => {}
389        }
390
391        node_states[node.0] = State::Deciding;
392
393        for neighbor_index in query.graph.successor_nodes(node) {
394            if recurse(query, node_states, neighbor_index) {
395                node_states[node.0] = State::Included;
396            }
397        }
398
399        // if we didn't find a path to target, then set to excluded
400        if node_states[node.0] == State::Deciding {
401            node_states[node.0] = State::Excluded;
402            false
403        } else {
404            if !(node_states[node.0] == State::Included) {
    ::core::panicking::panic("assertion failed: node_states[node.0] == State::Included")
};assert!(node_states[node.0] == State::Included);
405            true
406        }
407    }
408}
409
410fn filter_edges(query: &DepGraphQuery, nodes: &FxIndexSet<DepKind>) -> Vec<(DepKind, DepKind)> {
411    let uniq: FxIndexSet<_> = query
412        .edges()
413        .into_iter()
414        .map(|(s, t)| (s.kind, t.kind))
415        .filter(|(source, target)| nodes.contains(source) && nodes.contains(target))
416        .collect();
417    uniq.into_iter().collect()
418}