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
::dep_graph
::debug
::{DepNodeFilter, EdgeFilter}
;
48 use rustc
::hir
::def_id
::DefId
;
49 use rustc
::ty
::TyCtxt
;
50 use rustc_data_structures
::fnv
::{FnvHashMap, FnvHashSet}
;
51 use rustc_data_structures
::graph
::{Direction, INCOMING, OUTGOING, NodeIndex}
;
53 use rustc
::hir
::intravisit
::Visitor
;
54 use graphviz
::IntoCow
;
59 use syntax
::attr
::AttrMetaMethods
;
60 use syntax
::parse
::token
::InternedString
;
63 const IF_THIS_CHANGED
: &'
static str = "rustc_if_this_changed";
64 const THEN_THIS_WOULD_NEED
: &'
static str = "rustc_then_this_would_need";
65 const ID
: &'
static str = "id";
67 pub fn assert_dep_graph
<'a
, 'tcx
>(tcx
: TyCtxt
<'a
, 'tcx
, 'tcx
>) {
68 let _ignore
= tcx
.dep_graph
.in_ignore();
70 if tcx
.sess
.opts
.debugging_opts
.dump_dep_graph
{
74 // if the `rustc_attrs` feature is not enabled, then the
75 // attributes we are interested in cannot be present anyway, so
77 if !tcx
.sess
.features
.borrow().rustc_attrs
{
81 // Find annotations supplied by user (if any).
82 let (if_this_changed
, then_this_would_need
) = {
83 let mut visitor
= IfThisChanged
{ tcx
: tcx
,
84 if_this_changed
: FnvHashMap(),
85 then_this_would_need
: FnvHashMap() };
86 tcx
.map
.krate().visit_all_items(&mut visitor
);
87 (visitor
.if_this_changed
, visitor
.then_this_would_need
)
90 if !if_this_changed
.is_empty() || !then_this_would_need
.is_empty() {
91 assert
!(tcx
.sess
.opts
.debugging_opts
.query_dep_graph
,
92 "cannot use the `#[{}]` or `#[{}]` annotations \
93 without supplying `-Z query-dep-graph`",
94 IF_THIS_CHANGED
, THEN_THIS_WOULD_NEED
);
98 check_paths(tcx
, &if_this_changed
, &then_this_would_need
);
102 FnvHashMap
<InternedString
,
103 FnvHashSet
<(Span
, DefId
, DepNode
<DefId
>)>>;
105 FnvHashMap
<InternedString
,
106 FnvHashSet
<(Span
, InternedString
, ast
::NodeId
, DepNode
<DefId
>)>>;
108 struct IfThisChanged
<'a
, 'tcx
:'a
> {
109 tcx
: TyCtxt
<'a
, 'tcx
, 'tcx
>,
110 if_this_changed
: SourceHashMap
,
111 then_this_would_need
: TargetHashMap
,
114 impl<'a
, 'tcx
> IfThisChanged
<'a
, 'tcx
> {
115 fn process_attrs(&mut self, node_id
: ast
::NodeId
, def_id
: DefId
) {
116 for attr
in self.tcx
.get_attrs(def_id
).iter() {
117 if attr
.check_name(IF_THIS_CHANGED
) {
119 for meta_item
in attr
.meta_item_list().unwrap_or_default() {
120 if meta_item
.is_word() && id
.is_none() {
121 id
= Some(meta_item
.name().clone());
123 // FIXME better-encapsulate meta_item (don't directly access `node`)
124 span_bug
!(meta_item
.span(), "unexpected meta-item {:?}", meta_item
.node
)
127 let id
= id
.unwrap_or(InternedString
::new(ID
));
128 self.if_this_changed
.entry(id
)
129 .or_insert(FnvHashSet())
130 .insert((attr
.span
, def_id
, DepNode
::Hir(def_id
)));
131 } else if attr
.check_name(THEN_THIS_WOULD_NEED
) {
132 let mut dep_node_interned
= None
;
134 for meta_item
in attr
.meta_item_list().unwrap_or_default() {
135 if meta_item
.is_word() && dep_node_interned
.is_none() {
136 dep_node_interned
= Some(meta_item
.name().clone());
137 } else if meta_item
.is_word() && id
.is_none() {
138 id
= Some(meta_item
.name().clone());
140 // FIXME better-encapsulate meta_item (don't directly access `node`)
141 span_bug
!(meta_item
.span(), "unexpected meta-item {:?}", meta_item
.node
)
144 let dep_node
= match dep_node_interned
{
146 match DepNode
::from_label_string(&n
[..], def_id
) {
149 self.tcx
.sess
.span_fatal(
151 &format
!("unrecognized DepNode variant {:?}", n
));
156 self.tcx
.sess
.span_fatal(
158 &format
!("missing DepNode variant"));
161 let id
= id
.unwrap_or(InternedString
::new(ID
));
162 self.then_this_would_need
164 .or_insert(FnvHashSet())
165 .insert((attr
.span
, dep_node_interned
.clone().unwrap(), node_id
, dep_node
));
171 impl<'a
, 'tcx
> Visitor
<'tcx
> for IfThisChanged
<'a
, 'tcx
> {
172 fn visit_item(&mut self, item
: &'tcx hir
::Item
) {
173 let def_id
= self.tcx
.map
.local_def_id(item
.id
);
174 self.process_attrs(item
.id
, def_id
);
178 fn check_paths
<'a
, 'tcx
>(tcx
: TyCtxt
<'a
, 'tcx
, 'tcx
>,
179 if_this_changed
: &SourceHashMap
,
180 then_this_would_need
: &TargetHashMap
)
182 // Return early here so as not to construct the query, which is not cheap.
183 if if_this_changed
.is_empty() {
186 let query
= tcx
.dep_graph
.query();
187 for (id
, sources
) in if_this_changed
{
188 let targets
= match then_this_would_need
.get(id
) {
189 Some(targets
) => targets
,
191 for &(source_span
, _
, _
) in sources
.iter().take(1) {
194 &format
!("no targets for id `{}`", id
));
200 for &(_
, source_def_id
, ref source_dep_node
) in sources
{
201 let dependents
= query
.transitive_successors(source_dep_node
);
202 for &(target_span
, ref target_pass
, _
, ref target_dep_node
) in targets
{
203 if !dependents
.contains(&target_dep_node
) {
206 &format
!("no path from `{}` to `{}`",
207 tcx
.item_path_str(source_def_id
),
219 fn dump_graph(tcx
: TyCtxt
) {
220 let path
: String
= env
::var("RUST_DEP_GRAPH").unwrap_or_else(|_
| format
!("dep_graph"));
221 let query
= tcx
.dep_graph
.query();
223 let nodes
= match env
::var("RUST_DEP_GRAPH_FILTER") {
225 // Expect one of: "-> target", "source -> target", or "source ->".
226 let edge_filter
= EdgeFilter
::new(&string
).unwrap_or_else(|e
| {
227 bug
!("invalid filter: {}", e
)
229 let sources
= node_set(&query
, &edge_filter
.source
);
230 let targets
= node_set(&query
, &edge_filter
.target
);
231 filter_nodes(&query
, &sources
, &targets
)
239 let edges
= filter_edges(&query
, &nodes
);
241 { // dump a .txt file with just the edges:
242 let txt_path
= format
!("{}.txt", path
);
243 let mut file
= File
::create(&txt_path
).unwrap();
244 for &(ref source
, ref target
) in &edges
{
245 write
!(file
, "{:?} -> {:?}\n", source
, target
).unwrap();
249 { // dump a .dot file in graphviz format:
250 let dot_path
= format
!("{}.dot", path
);
251 let mut v
= Vec
::new();
252 dot
::render(&GraphvizDepGraph(nodes
, edges
), &mut v
).unwrap();
253 File
::create(&dot_path
).and_then(|mut f
| f
.write_all(&v
)).unwrap();
257 pub struct GraphvizDepGraph
<'q
>(FnvHashSet
<&'q DepNode
<DefId
>>,
258 Vec
<(&'q DepNode
<DefId
>, &'q DepNode
<DefId
>)>);
260 impl<'a
, 'tcx
, 'q
> dot
::GraphWalk
<'a
> for GraphvizDepGraph
<'q
> {
261 type Node
= &'q DepNode
<DefId
>;
262 type Edge
= (&'q DepNode
<DefId
>, &'q DepNode
<DefId
>);
263 fn nodes(&self) -> dot
::Nodes
<&'q DepNode
<DefId
>> {
264 let nodes
: Vec
<_
> = self.0.iter
().cloned().collect();
267 fn edges(&self) -> dot
::Edges
<(&'q DepNode
<DefId
>, &'q DepNode
<DefId
>)> {
268 self.1[..].into_cow()
270 fn source(&self, edge
: &(&'q DepNode
<DefId
>, &'q DepNode
<DefId
>)) -> &'q DepNode
<DefId
> {
273 fn target(&self, edge
: &(&'q DepNode
<DefId
>, &'q DepNode
<DefId
>)) -> &'q DepNode
<DefId
> {
278 impl<'a
, 'tcx
, 'q
> dot
::Labeller
<'a
> for GraphvizDepGraph
<'q
> {
279 type Node
= &'q DepNode
<DefId
>;
280 type Edge
= (&'q DepNode
<DefId
>, &'q DepNode
<DefId
>);
281 fn graph_id(&self) -> dot
::Id
{
282 dot
::Id
::new("DependencyGraph").unwrap()
284 fn node_id(&self, n
: &&'q DepNode
<DefId
>) -> dot
::Id
{
286 format
!("{:?}", n
).chars()
287 .map(|c
| if c
== '_'
|| c
.is_alphanumeric() { c }
else { '_' }
)
289 debug
!("n={:?} s={:?}", n
, s
);
290 dot
::Id
::new(s
).unwrap()
292 fn node_label(&self, n
: &&'q DepNode
<DefId
>) -> dot
::LabelText
{
293 dot
::LabelText
::label(format
!("{:?}", n
))
297 // Given an optional filter like `"x,y,z"`, returns either `None` (no
298 // filter) or the set of nodes whose labels contain all of those
300 fn node_set
<'q
>(query
: &'q DepGraphQuery
<DefId
>, filter
: &DepNodeFilter
)
301 -> Option
<FnvHashSet
<&'q DepNode
<DefId
>>>
303 debug
!("node_set(filter={:?})", filter
);
305 if filter
.accepts_all() {
309 Some(query
.nodes().into_iter().filter(|n
| filter
.test(n
)).collect())
312 fn filter_nodes
<'q
>(query
: &'q DepGraphQuery
<DefId
>,
313 sources
: &Option
<FnvHashSet
<&'q DepNode
<DefId
>>>,
314 targets
: &Option
<FnvHashSet
<&'q DepNode
<DefId
>>>)
315 -> FnvHashSet
<&'q DepNode
<DefId
>>
317 if let &Some(ref sources
) = sources
{
318 if let &Some(ref targets
) = targets
{
319 walk_between(query
, sources
, targets
)
321 walk_nodes(query
, sources
, OUTGOING
)
323 } else if let &Some(ref targets
) = targets
{
324 walk_nodes(query
, targets
, INCOMING
)
326 query
.nodes().into_iter().collect()
330 fn walk_nodes
<'q
>(query
: &'q DepGraphQuery
<DefId
>,
331 starts
: &FnvHashSet
<&'q DepNode
<DefId
>>,
332 direction
: Direction
)
333 -> FnvHashSet
<&'q DepNode
<DefId
>>
335 let mut set
= FnvHashSet();
336 for &start
in starts
{
337 debug
!("walk_nodes: start={:?} outgoing?={:?}", start
, direction
== OUTGOING
);
338 if set
.insert(start
) {
339 let mut stack
= vec
![query
.indices
[start
]];
340 while let Some(index
) = stack
.pop() {
341 for (_
, edge
) in query
.graph
.adjacent_edges(index
, direction
) {
342 let neighbor_index
= edge
.source_or_target(direction
);
343 let neighbor
= query
.graph
.node_data(neighbor_index
);
344 if set
.insert(neighbor
) {
345 stack
.push(neighbor_index
);
354 fn walk_between
<'q
>(query
: &'q DepGraphQuery
<DefId
>,
355 sources
: &FnvHashSet
<&'q DepNode
<DefId
>>,
356 targets
: &FnvHashSet
<&'q DepNode
<DefId
>>)
357 -> FnvHashSet
<&'q DepNode
<DefId
>>
359 // This is a bit tricky. We want to include a node only if it is:
360 // (a) reachable from a source and (b) will reach a target. And we
361 // have to be careful about cycles etc. Luckily efficiency is not
364 #[derive(Copy, Clone, PartialEq)]
365 enum State { Undecided, Deciding, Included, Excluded }
367 let mut node_states
= vec
![State
::Undecided
; query
.graph
.len_nodes()];
369 for &target
in targets
{
370 node_states
[query
.indices
[target
].0] = State
::Included
;
373 for source
in sources
.iter().map(|&n
| query
.indices
[n
]) {
374 recurse(query
, &mut node_states
, source
);
380 let index
= query
.indices
[n
];
381 node_states
[index
.0] == State
::Included
385 fn recurse(query
: &DepGraphQuery
<DefId
>,
386 node_states
: &mut [State
],
390 match node_states
[node
.0] {
391 // known to reach a target
392 State
::Included
=> return true,
394 // known not to reach a target
395 State
::Excluded
=> return false,
397 // backedge, not yet known, say false
398 State
::Deciding
=> return false,
400 State
::Undecided
=> { }
403 node_states
[node
.0] = State
::Deciding
;
405 for neighbor_index
in query
.graph
.successor_nodes(node
) {
406 if recurse(query
, node_states
, neighbor_index
) {
407 node_states
[node
.0] = State
::Included
;
411 // if we didn't find a path to target, then set to excluded
412 if node_states
[node
.0] == State
::Deciding
{
413 node_states
[node
.0] = State
::Excluded
;
416 assert
!(node_states
[node
.0] == State
::Included
);
422 fn filter_edges
<'q
>(query
: &'q DepGraphQuery
<DefId
>,
423 nodes
: &FnvHashSet
<&'q DepNode
<DefId
>>)
424 -> Vec
<(&'q DepNode
<DefId
>, &'q DepNode
<DefId
>)>
428 .filter(|&(source
, target
)| nodes
.contains(source
) && nodes
.contains(target
))