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