]> git.lizzy.rs Git - rust.git/blob - src/librustc_incremental/assert_dep_graph.rs
Rollup merge of #67422 - GuillaumeGomez:cleanup-err-codes, r=Dylan-DPC
[rust.git] / src / librustc_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::dirty_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. `compile-fail` 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 //! ```
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
36 use graphviz as dot;
37 use rustc::dep_graph::{DepGraphQuery, DepNode, DepKind};
38 use rustc::dep_graph::debug::{DepNodeFilter, EdgeFilter};
39 use rustc::hir::def_id::DefId;
40 use rustc::ty::TyCtxt;
41 use rustc_data_structures::fx::FxHashSet;
42 use rustc_data_structures::graph::implementation::{
43     Direction, INCOMING, OUTGOING, NodeIndex
44 };
45 use rustc::hir;
46 use rustc::hir::intravisit::{self, NestedVisitorMap, Visitor};
47 use std::env;
48 use std::fs::{self, File};
49 use std::io::Write;
50 use syntax::{ast, symbol::sym};
51 use syntax_pos::Span;
52
53 pub fn assert_dep_graph(tcx: TyCtxt<'_>) {
54     tcx.dep_graph.with_ignore(|| {
55         if tcx.sess.opts.debugging_opts.dump_dep_graph {
56             dump_graph(tcx);
57         }
58
59         // if the `rustc_attrs` feature is not enabled, then the
60         // attributes we are interested in cannot be present anyway, so
61         // skip the walk.
62         if !tcx.features().rustc_attrs {
63             return;
64         }
65
66         // Find annotations supplied by user (if any).
67         let (if_this_changed, then_this_would_need) = {
68             let mut visitor = IfThisChanged { tcx,
69                                             if_this_changed: vec![],
70                                             then_this_would_need: vec![] };
71             visitor.process_attrs(hir::CRATE_HIR_ID, &tcx.hir().krate().attrs);
72             tcx.hir().krate().visit_all_item_likes(&mut visitor.as_deep_visitor());
73             (visitor.if_this_changed, visitor.then_this_would_need)
74         };
75
76         if !if_this_changed.is_empty() || !then_this_would_need.is_empty() {
77             assert!(tcx.sess.opts.debugging_opts.query_dep_graph,
78                     "cannot use the `#[{}]` or `#[{}]` annotations \
79                     without supplying `-Z query-dep-graph`",
80                     sym::rustc_if_this_changed, sym::rustc_then_this_would_need);
81         }
82
83         // Check paths.
84         check_paths(tcx, &if_this_changed, &then_this_would_need);
85     })
86 }
87
88 type Sources = Vec<(Span, DefId, DepNode)>;
89 type Targets = Vec<(Span, ast::Name, hir::HirId, DepNode)>;
90
91 struct IfThisChanged<'tcx> {
92     tcx: TyCtxt<'tcx>,
93     if_this_changed: Sources,
94     then_this_would_need: Targets,
95 }
96
97 impl IfThisChanged<'tcx> {
98     fn argument(&self, attr: &ast::Attribute) -> Option<ast::Name> {
99         let mut value = None;
100         for list_item in attr.meta_item_list().unwrap_or_default() {
101             match list_item.ident() {
102                 Some(ident) if list_item.is_word() && value.is_none() =>
103                     value = Some(ident.name),
104                 _ =>
105                     // FIXME better-encapsulate meta_item (don't directly access `node`)
106                     span_bug!(list_item.span(), "unexpected meta-item {:?}", list_item),
107             }
108         }
109         value
110     }
111
112     fn process_attrs(&mut self, hir_id: hir::HirId, attrs: &[ast::Attribute]) {
113         let def_id = self.tcx.hir().local_def_id(hir_id);
114         let def_path_hash = self.tcx.def_path_hash(def_id);
115         for attr in attrs {
116             if attr.check_name(sym::rustc_if_this_changed) {
117                 let dep_node_interned = self.argument(attr);
118                 let dep_node = match dep_node_interned {
119                     None => def_path_hash.to_dep_node(DepKind::Hir),
120                     Some(n) => {
121                         match DepNode::from_label_string(&n.as_str(), def_path_hash) {
122                             Ok(n) => n,
123                             Err(()) => {
124                                 self.tcx.sess.span_fatal(
125                                     attr.span,
126                                     &format!("unrecognized DepNode variant {:?}", n));
127                             }
128                         }
129                     }
130                 };
131                 self.if_this_changed.push((attr.span, def_id, dep_node));
132             } else if attr.check_name(sym::rustc_then_this_would_need) {
133                 let dep_node_interned = self.argument(attr);
134                 let dep_node = match dep_node_interned {
135                     Some(n) => {
136                         match DepNode::from_label_string(&n.as_str(), def_path_hash) {
137                             Ok(n) => n,
138                             Err(()) => {
139                                 self.tcx.sess.span_fatal(
140                                     attr.span,
141                                     &format!("unrecognized DepNode variant {:?}", n));
142                             }
143                         }
144                     }
145                     None => {
146                         self.tcx.sess.span_fatal(
147                             attr.span,
148                             "missing DepNode variant");
149                     }
150                 };
151                 self.then_this_would_need.push((attr.span,
152                                                 dep_node_interned.unwrap(),
153                                                 hir_id,
154                                                 dep_node));
155             }
156         }
157     }
158 }
159
160 impl Visitor<'tcx> for IfThisChanged<'tcx> {
161     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
162         NestedVisitorMap::OnlyBodies(&self.tcx.hir())
163     }
164
165     fn visit_item(&mut self, item: &'tcx hir::Item) {
166         self.process_attrs(item.hir_id, &item.attrs);
167         intravisit::walk_item(self, item);
168     }
169
170     fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem) {
171         self.process_attrs(trait_item.hir_id, &trait_item.attrs);
172         intravisit::walk_trait_item(self, trait_item);
173     }
174
175     fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem) {
176         self.process_attrs(impl_item.hir_id, &impl_item.attrs);
177         intravisit::walk_impl_item(self, impl_item);
178     }
179
180     fn visit_struct_field(&mut self, s: &'tcx hir::StructField) {
181         self.process_attrs(s.hir_id, &s.attrs);
182         intravisit::walk_struct_field(self, s);
183     }
184 }
185
186 fn check_paths<'tcx>(tcx: TyCtxt<'tcx>, if_this_changed: &Sources, then_this_would_need: &Targets) {
187     // Return early here so as not to construct the query, which is not cheap.
188     if if_this_changed.is_empty() {
189         for &(target_span, _, _, _) in then_this_would_need {
190             tcx.sess.span_err(
191                 target_span,
192                 "no `#[rustc_if_this_changed]` annotation detected");
193
194         }
195         return;
196     }
197     let query = tcx.dep_graph.query();
198     for &(_, source_def_id, ref source_dep_node) in if_this_changed {
199         let dependents = query.transitive_predecessors(source_dep_node);
200         for &(target_span, ref target_pass, _, ref target_dep_node) in then_this_would_need {
201             if !dependents.contains(&target_dep_node) {
202                 tcx.sess.span_err(
203                     target_span,
204                     &format!("no path from `{}` to `{}`",
205                              tcx.def_path_str(source_def_id),
206                              target_pass));
207             } else {
208                 tcx.sess.span_err(
209                     target_span,
210                     "OK");
211             }
212         }
213     }
214 }
215
216 fn dump_graph(tcx: TyCtxt<'_>) {
217     let path: String = env::var("RUST_DEP_GRAPH").unwrap_or_else(|_| "dep_graph".to_string());
218     let query = tcx.dep_graph.query();
219
220     let nodes = match env::var("RUST_DEP_GRAPH_FILTER") {
221         Ok(string) => {
222             // Expect one of: "-> target", "source -> target", or "source ->".
223             let edge_filter = EdgeFilter::new(&string).unwrap_or_else(|e| {
224                 bug!("invalid filter: {}", e)
225             });
226             let sources = node_set(&query, &edge_filter.source);
227             let targets = node_set(&query, &edge_filter.target);
228             filter_nodes(&query, &sources, &targets)
229         }
230         Err(_) => {
231             query.nodes()
232                  .into_iter()
233                  .collect()
234         }
235     };
236     let edges = filter_edges(&query, &nodes);
237
238     { // dump a .txt file with just the edges:
239         let txt_path = format!("{}.txt", path);
240         let mut file = File::create(&txt_path).unwrap();
241         for &(ref source, ref target) in &edges {
242             write!(file, "{:?} -> {:?}\n", source, target).unwrap();
243         }
244     }
245
246     { // dump a .dot file in graphviz format:
247         let dot_path = format!("{}.dot", path);
248         let mut v = Vec::new();
249         dot::render(&GraphvizDepGraph(nodes, edges), &mut v).unwrap();
250         fs::write(dot_path, v).unwrap();
251     }
252 }
253
254 pub struct GraphvizDepGraph<'q>(FxHashSet<&'q DepNode>,
255                                 Vec<(&'q DepNode, &'q DepNode)>);
256
257 impl<'a, 'q> dot::GraphWalk<'a> for GraphvizDepGraph<'q> {
258     type Node = &'q DepNode;
259     type Edge = (&'q DepNode, &'q DepNode);
260     fn nodes(&self) -> dot::Nodes<'_, &'q DepNode> {
261         let nodes: Vec<_> = self.0.iter().cloned().collect();
262         nodes.into()
263     }
264     fn edges(&self) -> dot::Edges<'_, (&'q DepNode, &'q DepNode)> {
265         self.1[..].into()
266     }
267     fn source(&self, edge: &(&'q DepNode, &'q DepNode)) -> &'q DepNode {
268         edge.0
269     }
270     fn target(&self, edge: &(&'q DepNode, &'q DepNode)) -> &'q DepNode {
271         edge.1
272     }
273 }
274
275 impl<'a, 'q> dot::Labeller<'a> for GraphvizDepGraph<'q> {
276     type Node = &'q DepNode;
277     type Edge = (&'q DepNode, &'q DepNode);
278     fn graph_id(&self) -> dot::Id<'_> {
279         dot::Id::new("DependencyGraph").unwrap()
280     }
281     fn node_id(&self, n: &&'q DepNode) -> dot::Id<'_> {
282         let s: String =
283             format!("{:?}", n).chars()
284                               .map(|c| if c == '_' || c.is_alphanumeric() { c } else { '_' })
285                               .collect();
286         debug!("n={:?} s={:?}", n, s);
287         dot::Id::new(s).unwrap()
288     }
289     fn node_label(&self, n: &&'q DepNode) -> dot::LabelText<'_> {
290         dot::LabelText::label(format!("{:?}", n))
291     }
292 }
293
294 // Given an optional filter like `"x,y,z"`, returns either `None` (no
295 // filter) or the set of nodes whose labels contain all of those
296 // substrings.
297 fn node_set<'q>(query: &'q DepGraphQuery, filter: &DepNodeFilter)
298                 -> Option<FxHashSet<&'q DepNode>>
299 {
300     debug!("node_set(filter={:?})", filter);
301
302     if filter.accepts_all() {
303         return None;
304     }
305
306     Some(query.nodes().into_iter().filter(|n| filter.test(n)).collect())
307 }
308
309 fn filter_nodes<'q>(query: &'q DepGraphQuery,
310                     sources: &Option<FxHashSet<&'q DepNode>>,
311                     targets: &Option<FxHashSet<&'q DepNode>>)
312                     -> FxHashSet<&'q DepNode>
313 {
314     if let &Some(ref sources) = sources {
315         if let &Some(ref targets) = targets {
316             walk_between(query, sources, targets)
317         } else {
318             walk_nodes(query, sources, OUTGOING)
319         }
320     } else if let &Some(ref targets) = targets {
321         walk_nodes(query, targets, INCOMING)
322     } else {
323         query.nodes().into_iter().collect()
324     }
325 }
326
327 fn walk_nodes<'q>(query: &'q DepGraphQuery,
328                   starts: &FxHashSet<&'q DepNode>,
329                   direction: Direction)
330                   -> FxHashSet<&'q DepNode>
331 {
332     let mut set = FxHashSet::default();
333     for &start in starts {
334         debug!("walk_nodes: start={:?} outgoing?={:?}", start, direction == OUTGOING);
335         if set.insert(start) {
336             let mut stack = vec![query.indices[start]];
337             while let Some(index) = stack.pop() {
338                 for (_, edge) in query.graph.adjacent_edges(index, direction) {
339                     let neighbor_index = edge.source_or_target(direction);
340                     let neighbor = query.graph.node_data(neighbor_index);
341                     if set.insert(neighbor) {
342                         stack.push(neighbor_index);
343                     }
344                 }
345             }
346         }
347     }
348     set
349 }
350
351 fn walk_between<'q>(query: &'q DepGraphQuery,
352                     sources: &FxHashSet<&'q DepNode>,
353                     targets: &FxHashSet<&'q DepNode>)
354                     -> FxHashSet<&'q DepNode>
355 {
356     // This is a bit tricky. We want to include a node only if it is:
357     // (a) reachable from a source and (b) will reach a target. And we
358     // have to be careful about cycles etc.  Luckily efficiency is not
359     // a big concern!
360
361     #[derive(Copy, Clone, PartialEq)]
362     enum State { Undecided, Deciding, Included, Excluded }
363
364     let mut node_states = vec![State::Undecided; query.graph.len_nodes()];
365
366     for &target in targets {
367         node_states[query.indices[target].0] = State::Included;
368     }
369
370     for source in sources.iter().map(|&n| query.indices[n]) {
371         recurse(query, &mut node_states, source);
372     }
373
374     return query.nodes()
375                 .into_iter()
376                 .filter(|&n| {
377                     let index = query.indices[n];
378                     node_states[index.0] == State::Included
379                 })
380                 .collect();
381
382     fn recurse(query: &DepGraphQuery,
383                node_states: &mut [State],
384                node: NodeIndex)
385                -> bool
386     {
387         match node_states[node.0] {
388             // known to reach a target
389             State::Included => return true,
390
391             // known not to reach a target
392             State::Excluded => return false,
393
394             // backedge, not yet known, say false
395             State::Deciding => return false,
396
397             State::Undecided => { }
398         }
399
400         node_states[node.0] = State::Deciding;
401
402         for neighbor_index in query.graph.successor_nodes(node) {
403             if recurse(query, node_states, neighbor_index) {
404                 node_states[node.0] = State::Included;
405             }
406         }
407
408         // if we didn't find a path to target, then set to excluded
409         if node_states[node.0] == State::Deciding {
410             node_states[node.0] = State::Excluded;
411             false
412         } else {
413             assert!(node_states[node.0] == State::Included);
414             true
415         }
416     }
417 }
418
419 fn filter_edges<'q>(query: &'q DepGraphQuery,
420                     nodes: &FxHashSet<&'q DepNode>)
421                     -> Vec<(&'q DepNode, &'q DepNode)>
422 {
423     query.edges()
424          .into_iter()
425          .filter(|&(source, target)| nodes.contains(source) && nodes.contains(target))
426          .collect()
427 }