1 // Copyright 2012-2015 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! This pass is only used for the UNIT TESTS and DEBUGGING NEEDS
12 //! around dependency graph construction. It serves two purposes; it
13 //! will dump graphs in graphviz form to disk, and it searches for
14 //! `#[rustc_if_this_changed]` and `#[rustc_then_this_would_need]`
15 //! annotations. These annotations can be used to test whether paths
16 //! exist in the graph. These checks run after trans, so they view the
17 //! the final state of the dependency graph. Note that there are
18 //! similar assertions found in `persist::dirty_clean` which check the
19 //! **initial** state of the dependency graph, just after it has been
22 //! In this code, we report errors on each `rustc_if_this_changed`
23 //! annotation. If a path exists in all cases, then we would report
24 //! "all path(s) exist". Otherwise, we report: "no path to `foo`" for
25 //! each case where no path exists. `compile-fail` tests can then be
26 //! used to check when paths exist or do not.
28 //! The full form of the `rustc_if_this_changed` annotation is
29 //! `#[rustc_if_this_changed(id)]`. The `"id"` is optional and
30 //! defaults to `"id"` if omitted.
35 //! #[rustc_if_this_changed]
38 //! #[rustc_then_this_would_need("trans")] //~ ERROR no path from `foo`
41 //! #[rustc_then_this_would_need("trans")] //~ ERROR OK
42 //! fn baz() { foo(); }
46 use rustc
::dep_graph
::{DepGraphQuery, DepNode}
;
47 use rustc
::hir
::def_id
::DefId
;
48 use rustc
::ty
::TyCtxt
;
49 use rustc_data_structures
::fnv
::{FnvHashMap, FnvHashSet}
;
50 use rustc_data_structures
::graph
::{Direction, INCOMING, OUTGOING, NodeIndex}
;
52 use rustc
::hir
::intravisit
::Visitor
;
53 use graphviz
::IntoCow
;
58 use syntax
::attr
::AttrMetaMethods
;
59 use syntax
::codemap
::Span
;
60 use syntax
::parse
::token
::InternedString
;
62 const IF_THIS_CHANGED
: &'
static str = "rustc_if_this_changed";
63 const THEN_THIS_WOULD_NEED
: &'
static str = "rustc_then_this_would_need";
64 const ID
: &'
static str = "id";
66 pub fn assert_dep_graph(tcx
: &TyCtxt
) {
67 let _ignore
= tcx
.dep_graph
.in_ignore();
69 if tcx
.sess
.opts
.debugging_opts
.dump_dep_graph
{
73 // Find annotations supplied by user (if any).
74 let (if_this_changed
, then_this_would_need
) = {
75 let mut visitor
= IfThisChanged
{ tcx
: tcx
,
76 if_this_changed
: FnvHashMap(),
77 then_this_would_need
: FnvHashMap() };
78 tcx
.map
.krate().visit_all_items(&mut visitor
);
79 (visitor
.if_this_changed
, visitor
.then_this_would_need
)
82 if !if_this_changed
.is_empty() || !then_this_would_need
.is_empty() {
83 assert
!(tcx
.sess
.opts
.debugging_opts
.query_dep_graph
,
84 "cannot use the `#[{}]` or `#[{}]` annotations \
85 without supplying `-Z query-dep-graph`",
86 IF_THIS_CHANGED
, THEN_THIS_WOULD_NEED
);
90 check_paths(tcx
, &if_this_changed
, &then_this_would_need
);
94 FnvHashMap
<InternedString
,
95 FnvHashSet
<(Span
, DefId
, DepNode
<DefId
>)>>;
97 FnvHashMap
<InternedString
,
98 FnvHashSet
<(Span
, InternedString
, ast
::NodeId
, DepNode
<DefId
>)>>;
100 struct IfThisChanged
<'a
, 'tcx
:'a
> {
101 tcx
: &'a TyCtxt
<'tcx
>,
102 if_this_changed
: SourceHashMap
,
103 then_this_would_need
: TargetHashMap
,
106 impl<'a
, 'tcx
> IfThisChanged
<'a
, 'tcx
> {
107 fn process_attrs(&mut self, node_id
: ast
::NodeId
, def_id
: DefId
) {
108 for attr
in self.tcx
.get_attrs(def_id
).iter() {
109 if attr
.check_name(IF_THIS_CHANGED
) {
111 for meta_item
in attr
.meta_item_list().unwrap_or_default() {
112 match meta_item
.node
{
113 ast
::MetaItemKind
::Word(ref s
) if id
.is_none() => id
= Some(s
.clone()),
115 self.tcx
.sess
.span_err(
117 &format
!("unexpected meta-item {:?}", meta_item
.node
));
121 let id
= id
.unwrap_or(InternedString
::new(ID
));
122 self.if_this_changed
.entry(id
)
123 .or_insert(FnvHashSet())
124 .insert((attr
.span
, def_id
, DepNode
::Hir(def_id
)));
125 } else if attr
.check_name(THEN_THIS_WOULD_NEED
) {
126 let mut dep_node_interned
= None
;
128 for meta_item
in attr
.meta_item_list().unwrap_or_default() {
129 match meta_item
.node
{
130 ast
::MetaItemKind
::Word(ref s
) if dep_node_interned
.is_none() =>
131 dep_node_interned
= Some(s
.clone()),
132 ast
::MetaItemKind
::Word(ref s
) if id
.is_none() =>
133 id
= Some(s
.clone()),
135 self.tcx
.sess
.span_err(
137 &format
!("unexpected meta-item {:?}", meta_item
.node
));
141 let dep_node
= match dep_node_interned
{
143 match DepNode
::from_label_string(&n
[..], def_id
) {
146 self.tcx
.sess
.span_fatal(
148 &format
!("unrecognized DepNode variant {:?}", n
));
153 self.tcx
.sess
.span_fatal(
155 &format
!("missing DepNode variant"));
158 let id
= id
.unwrap_or(InternedString
::new(ID
));
159 self.then_this_would_need
161 .or_insert(FnvHashSet())
162 .insert((attr
.span
, dep_node_interned
.clone().unwrap(), node_id
, dep_node
));
168 impl<'a
, 'tcx
> Visitor
<'tcx
> for IfThisChanged
<'a
, 'tcx
> {
169 fn visit_item(&mut self, item
: &'tcx hir
::Item
) {
170 let def_id
= self.tcx
.map
.local_def_id(item
.id
);
171 self.process_attrs(item
.id
, def_id
);
175 fn check_paths(tcx
: &TyCtxt
,
176 if_this_changed
: &SourceHashMap
,
177 then_this_would_need
: &TargetHashMap
)
179 // Return early here so as not to construct the query, which is not cheap.
180 if if_this_changed
.is_empty() {
183 let query
= tcx
.dep_graph
.query();
184 for (id
, sources
) in if_this_changed
{
185 let targets
= match then_this_would_need
.get(id
) {
186 Some(targets
) => targets
,
188 for &(source_span
, _
, _
) in sources
.iter().take(1) {
191 &format
!("no targets for id `{}`", id
));
197 for &(_
, source_def_id
, source_dep_node
) in sources
{
198 let dependents
= query
.transitive_dependents(source_dep_node
);
199 for &(target_span
, ref target_pass
, _
, ref target_dep_node
) in targets
{
200 if !dependents
.contains(&target_dep_node
) {
203 &format
!("no path from `{}` to `{}`",
204 tcx
.item_path_str(source_def_id
),
216 fn dump_graph(tcx
: &TyCtxt
) {
217 let path
: String
= env
::var("RUST_DEP_GRAPH").unwrap_or_else(|_
| format
!("dep_graph"));
218 let query
= tcx
.dep_graph
.query();
220 let nodes
= match env
::var("RUST_DEP_GRAPH_FILTER") {
222 // Expect one of: "-> target", "source -> target", or "source ->".
223 let parts
: Vec
<_
> = string
.split("->").collect();
225 bug
!("Invalid RUST_DEP_GRAPH_FILTER: expected '[source] -> [target]'");
227 let sources
= node_set(&query
, &parts
[0]);
228 let targets
= node_set(&query
, &parts
[1]);
229 filter_nodes(&query
, &sources
, &targets
)
237 let edges
= filter_edges(&query
, &nodes
);
239 { // dump a .txt file with just the edges:
240 let txt_path
= format
!("{}.txt", path
);
241 let mut file
= File
::create(&txt_path
).unwrap();
242 for &(source
, target
) in &edges
{
243 write
!(file
, "{:?} -> {:?}\n", source
, target
).unwrap();
247 { // dump a .dot file in graphviz format:
248 let dot_path
= format
!("{}.dot", path
);
249 let mut v
= Vec
::new();
250 dot
::render(&GraphvizDepGraph(nodes
, edges
), &mut v
).unwrap();
251 File
::create(&dot_path
).and_then(|mut f
| f
.write_all(&v
)).unwrap();
255 pub struct GraphvizDepGraph(FnvHashSet
<DepNode
<DefId
>>,
256 Vec
<(DepNode
<DefId
>, DepNode
<DefId
>)>);
258 impl<'a
, 'tcx
> dot
::GraphWalk
<'a
> for GraphvizDepGraph
{
259 type Node
= DepNode
<DefId
>;
260 type Edge
= (DepNode
<DefId
>, DepNode
<DefId
>);
261 fn nodes(&self) -> dot
::Nodes
<DepNode
<DefId
>> {
262 let nodes
: Vec
<_
> = self.0.iter
().cloned().collect();
265 fn edges(&self) -> dot
::Edges
<(DepNode
<DefId
>, DepNode
<DefId
>)> {
266 self.1[..].into_cow()
268 fn source(&self, edge
: &(DepNode
<DefId
>, DepNode
<DefId
>)) -> DepNode
<DefId
> {
271 fn target(&self, edge
: &(DepNode
<DefId
>, DepNode
<DefId
>)) -> DepNode
<DefId
> {
276 impl<'a
, 'tcx
> dot
::Labeller
<'a
> for GraphvizDepGraph
{
277 type Node
= DepNode
<DefId
>;
278 type Edge
= (DepNode
<DefId
>, DepNode
<DefId
>);
279 fn graph_id(&self) -> dot
::Id
{
280 dot
::Id
::new("DependencyGraph").unwrap()
282 fn node_id(&self, n
: &DepNode
<DefId
>) -> dot
::Id
{
284 format
!("{:?}", n
).chars()
285 .map(|c
| if c
== '_'
|| c
.is_alphanumeric() { c }
else { '_' }
)
287 debug
!("n={:?} s={:?}", n
, s
);
288 dot
::Id
::new(s
).unwrap()
290 fn node_label(&self, n
: &DepNode
<DefId
>) -> dot
::LabelText
{
291 dot
::LabelText
::label(format
!("{:?}", n
))
295 // Given an optional filter like `"x,y,z"`, returns either `None` (no
296 // filter) or the set of nodes whose labels contain all of those
298 fn node_set(query
: &DepGraphQuery
<DefId
>, filter
: &str)
299 -> Option
<FnvHashSet
<DepNode
<DefId
>>>
301 debug
!("node_set(filter={:?})", filter
);
303 if filter
.trim().is_empty() {
307 let filters
: Vec
<&str> = filter
.split("&").map(|s
| s
.trim()).collect();
309 debug
!("node_set: filters={:?}", filters
);
314 let s
= format
!("{:?}", n
);
315 filters
.iter().all(|f
| s
.contains(f
))
320 fn filter_nodes(query
: &DepGraphQuery
<DefId
>,
321 sources
: &Option
<FnvHashSet
<DepNode
<DefId
>>>,
322 targets
: &Option
<FnvHashSet
<DepNode
<DefId
>>>)
323 -> FnvHashSet
<DepNode
<DefId
>>
325 if let &Some(ref sources
) = sources
{
326 if let &Some(ref targets
) = targets
{
327 walk_between(query
, sources
, targets
)
329 walk_nodes(query
, sources
, OUTGOING
)
331 } else if let &Some(ref targets
) = targets
{
332 walk_nodes(query
, targets
, INCOMING
)
334 query
.nodes().into_iter().collect()
338 fn walk_nodes(query
: &DepGraphQuery
<DefId
>,
339 starts
: &FnvHashSet
<DepNode
<DefId
>>,
340 direction
: Direction
)
341 -> FnvHashSet
<DepNode
<DefId
>>
343 let mut set
= FnvHashSet();
344 for start
in starts
{
345 debug
!("walk_nodes: start={:?} outgoing?={:?}", start
, direction
== OUTGOING
);
346 if set
.insert(*start
) {
347 let mut stack
= vec
![query
.indices
[start
]];
348 while let Some(index
) = stack
.pop() {
349 for (_
, edge
) in query
.graph
.adjacent_edges(index
, direction
) {
350 let neighbor_index
= edge
.source_or_target(direction
);
351 let neighbor
= query
.graph
.node_data(neighbor_index
);
352 if set
.insert(*neighbor
) {
353 stack
.push(neighbor_index
);
362 fn walk_between(query
: &DepGraphQuery
<DefId
>,
363 sources
: &FnvHashSet
<DepNode
<DefId
>>,
364 targets
: &FnvHashSet
<DepNode
<DefId
>>)
365 -> FnvHashSet
<DepNode
<DefId
>>
367 // This is a bit tricky. We want to include a node only if it is:
368 // (a) reachable from a source and (b) will reach a target. And we
369 // have to be careful about cycles etc. Luckily efficiency is not
372 #[derive(Copy, Clone, PartialEq)]
373 enum State { Undecided, Deciding, Included, Excluded }
375 let mut node_states
= vec
![State
::Undecided
; query
.graph
.len_nodes()];
377 for &target
in targets
{
378 node_states
[query
.indices
[&target
].0] = State
::Included
;
381 for source
in sources
.iter().map(|n
| query
.indices
[n
]) {
382 recurse(query
, &mut node_states
, source
);
388 let index
= query
.indices
[n
];
389 node_states
[index
.0] == State
::Included
393 fn recurse(query
: &DepGraphQuery
<DefId
>,
394 node_states
: &mut [State
],
398 match node_states
[node
.0] {
399 // known to reach a target
400 State
::Included
=> return true,
402 // known not to reach a target
403 State
::Excluded
=> return false,
405 // backedge, not yet known, say false
406 State
::Deciding
=> return false,
408 State
::Undecided
=> { }
411 node_states
[node
.0] = State
::Deciding
;
413 for neighbor_index
in query
.graph
.successor_nodes(node
) {
414 if recurse(query
, node_states
, neighbor_index
) {
415 node_states
[node
.0] = State
::Included
;
419 // if we didn't find a path to target, then set to excluded
420 if node_states
[node
.0] == State
::Deciding
{
421 node_states
[node
.0] = State
::Excluded
;
424 assert
!(node_states
[node
.0] == State
::Included
);
430 fn filter_edges(query
: &DepGraphQuery
<DefId
>,
431 nodes
: &FnvHashSet
<DepNode
<DefId
>>)
432 -> Vec
<(DepNode
<DefId
>, DepNode
<DefId
>)>
436 .filter(|&(source
, target
)| nodes
.contains(&source
) && nodes
.contains(&target
))