Skip to content

Commit 9413fc8

Browse files
committed
Add unit-testing framework for dep graph and -Z dump-dep-graph
1 parent 47f1494 commit 9413fc8

File tree

7 files changed

+296
-0
lines changed

7 files changed

+296
-0
lines changed

src/librustc/session/config.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ pub struct Options {
125125
pub parse_only: bool,
126126
pub no_trans: bool,
127127
pub treat_err_as_bug: bool,
128+
pub dump_dep_graph: bool,
128129
pub no_analysis: bool,
129130
pub debugging_opts: DebuggingOptions,
130131
pub prints: Vec<PrintRequest>,
@@ -234,6 +235,7 @@ pub fn basic_options() -> Options {
234235
parse_only: false,
235236
no_trans: false,
236237
treat_err_as_bug: false,
238+
dump_dep_graph: false,
237239
no_analysis: false,
238240
debugging_opts: basic_debugging_options(),
239241
prints: Vec::new(),
@@ -604,6 +606,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
604606
"run all passes except translation; no output"),
605607
treat_err_as_bug: bool = (false, parse_bool,
606608
"treat all errors that occur as bugs"),
609+
dump_dep_graph: bool = (false, parse_bool,
610+
"dump the dependency graph to $RUST_DEP_GRAPH (default: /tmp/dep_graph.gv)"),
607611
no_analysis: bool = (false, parse_bool,
608612
"parse and expand the source, but run no analysis"),
609613
extra_plugins: Vec<String> = (Vec::new(), parse_list,
@@ -925,6 +929,7 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {
925929
let parse_only = debugging_opts.parse_only;
926930
let no_trans = debugging_opts.no_trans;
927931
let treat_err_as_bug = debugging_opts.treat_err_as_bug;
932+
let dump_dep_graph = debugging_opts.dump_dep_graph;
928933
let no_analysis = debugging_opts.no_analysis;
929934

930935
if debugging_opts.debug_llvm {
@@ -1099,6 +1104,7 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options {
10991104
parse_only: parse_only,
11001105
no_trans: no_trans,
11011106
treat_err_as_bug: treat_err_as_bug,
1107+
dump_dep_graph: dump_dep_graph,
11021108
no_analysis: no_analysis,
11031109
debugging_opts: debugging_opts,
11041110
prints: prints,

src/librustc_trans/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#![feature(const_fn)]
3131
#![feature(custom_attribute)]
3232
#![allow(unused_attributes)]
33+
#![feature(into_cow)]
3334
#![feature(iter_arith)]
3435
#![feature(libc)]
3536
#![feature(path_relative_from)]
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
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.
4+
//
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.
10+
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. We report errors on each
17+
//! `rustc_if_this_changed` annotation. If a path exists in all
18+
//! cases, then we would report "all path(s) exist". Otherwise, we
19+
//! report: "no path to `foo`" for each case where no path exists.
20+
//! `compile-fail` tests can then be used to check when paths exist or
21+
//! do not.
22+
//!
23+
//! The full form of the `rustc_if_this_changed` annotation is
24+
//! `#[rustc_if_this_changed(id)]`. The `"id"` is optional and
25+
//! defaults to `"id"` if omitted.
26+
//!
27+
//! Example:
28+
//!
29+
//! ```
30+
//! #[rustc_if_this_changed]
31+
//! fn foo() { }
32+
//!
33+
//! #[rustc_then_this_would_need("trans")] //~ ERROR no path from `foo`
34+
//! fn bar() { }
35+
//! ```
36+
37+
use graphviz as dot;
38+
use rustc::dep_graph::{DepGraph, DepNode};
39+
use rustc::middle::ty;
40+
use rustc_data_structures::fnv::{FnvHashMap, FnvHashSet};
41+
use rustc_front::hir;
42+
use rustc_front::intravisit::Visitor;
43+
use std::borrow::IntoCow;
44+
use std::env;
45+
use std::fs::File;
46+
use std::io::Write;
47+
use std::rc::Rc;
48+
use syntax::ast;
49+
use syntax::attr::AttrMetaMethods;
50+
use syntax::codemap::Span;
51+
use syntax::parse::token::InternedString;
52+
53+
const IF_THIS_CHANGED: &'static str = "rustc_if_this_changed";
54+
const THEN_THIS_WOULD_NEED: &'static str = "rustc_then_this_would_need";
55+
const ID: &'static str = "id";
56+
57+
pub fn assert_dep_graph(tcx: &ty::ctxt) {
58+
let _ignore = tcx.dep_graph.in_ignore();
59+
60+
if tcx.sess.opts.dump_dep_graph {
61+
dump_graph(tcx);
62+
}
63+
64+
// Find annotations supplied by user (if any).
65+
let (if_this_changed, then_this_would_need) = {
66+
let mut visitor = IfThisChanged { tcx: tcx,
67+
if_this_changed: FnvHashMap(),
68+
then_this_would_need: FnvHashMap() };
69+
tcx.map.krate().visit_all_items(&mut visitor);
70+
(visitor.if_this_changed, visitor.then_this_would_need)
71+
};
72+
73+
// Check paths.
74+
check_paths(tcx, &if_this_changed, &then_this_would_need);
75+
}
76+
77+
type SourceHashMap = FnvHashMap<InternedString,
78+
FnvHashSet<(Span, DepNode)>>;
79+
type TargetHashMap = FnvHashMap<InternedString,
80+
FnvHashSet<(Span, InternedString, ast::NodeId, DepNode)>>;
81+
82+
struct IfThisChanged<'a, 'tcx:'a> {
83+
tcx: &'a ty::ctxt<'tcx>,
84+
if_this_changed: SourceHashMap,
85+
then_this_would_need: TargetHashMap,
86+
}
87+
88+
impl<'a, 'tcx> Visitor<'tcx> for IfThisChanged<'a, 'tcx> {
89+
fn visit_item(&mut self, item: &'tcx hir::Item) {
90+
let def_id = self.tcx.map.local_def_id(item.id);
91+
for attr in self.tcx.get_attrs(def_id).iter() {
92+
if attr.check_name(IF_THIS_CHANGED) {
93+
let mut id = None;
94+
for meta_item in attr.meta_item_list().unwrap_or_default() {
95+
match meta_item.node {
96+
ast::MetaWord(ref s) if id.is_none() => id = Some(s.clone()),
97+
_ => {
98+
self.tcx.sess.span_err(
99+
meta_item.span,
100+
&format!("unexpected meta-item {:?}", meta_item.node));
101+
}
102+
}
103+
}
104+
let id = id.unwrap_or(InternedString::new(ID));
105+
self.if_this_changed.entry(id)
106+
.or_insert(FnvHashSet())
107+
.insert((attr.span, DepNode::Hir(def_id)));
108+
} else if attr.check_name(THEN_THIS_WOULD_NEED) {
109+
let mut dep_node_interned = None;
110+
let mut id = None;
111+
for meta_item in attr.meta_item_list().unwrap_or_default() {
112+
match meta_item.node {
113+
ast::MetaWord(ref s) if dep_node_interned.is_none() =>
114+
dep_node_interned = Some(s.clone()),
115+
ast::MetaWord(ref s) if id.is_none() =>
116+
id = Some(s.clone()),
117+
_ => {
118+
self.tcx.sess.span_err(
119+
meta_item.span,
120+
&format!("unexpected meta-item {:?}", meta_item.node));
121+
}
122+
}
123+
}
124+
let dep_node_str = dep_node_interned.as_ref().map(|s| &**s);
125+
let dep_node = match dep_node_str {
126+
Some("BorrowCheck") => DepNode::BorrowCheck(def_id),
127+
Some("TransCrateItem") => DepNode::TransCrateItem(def_id),
128+
Some("TypeckItemType") => DepNode::TypeckItemType(def_id),
129+
Some("TypeckItemBody") => DepNode::TypeckItemBody(def_id),
130+
_ => {
131+
self.tcx.sess.span_fatal(
132+
attr.span,
133+
&format!("unrecognized pass {:?}", dep_node_str));
134+
}
135+
};
136+
let id = id.unwrap_or(InternedString::new(ID));
137+
self.then_this_would_need
138+
.entry(id)
139+
.or_insert(FnvHashSet())
140+
.insert((attr.span, dep_node_interned.clone().unwrap(), item.id, dep_node));
141+
}
142+
}
143+
}
144+
}
145+
146+
fn check_paths(tcx: &ty::ctxt,
147+
if_this_changed: &SourceHashMap,
148+
then_this_would_need: &TargetHashMap)
149+
{
150+
for (id, sources) in if_this_changed {
151+
let targets = match then_this_would_need.get(id) {
152+
Some(targets) => targets,
153+
None => {
154+
for &(source_span, _) in sources.iter().take(1) {
155+
tcx.sess.span_err(
156+
source_span,
157+
&format!("no targets for id `{}`", id));
158+
}
159+
continue;
160+
}
161+
};
162+
163+
for &(source_span, ref source_dep_node) in sources {
164+
let dependents = tcx.dep_graph.dependents(source_dep_node.clone());
165+
for &(_, ref target_pass, target_node_id, ref target_dep_node) in targets {
166+
if !dependents.contains(&target_dep_node) {
167+
let target_def_id = tcx.map.local_def_id(target_node_id);
168+
tcx.sess.span_err(
169+
source_span,
170+
&format!("no path to {} for `{}`",
171+
target_pass,
172+
tcx.item_path_str(target_def_id)));
173+
}
174+
}
175+
}
176+
}
177+
}
178+
179+
fn dump_graph(tcx: &ty::ctxt) {
180+
let path: String = env::var("RUST_DEP_GRAPH").unwrap_or_else(|_| format!("/tmp/dep_graph.dot"));
181+
let mut v = Vec::new();
182+
dot::render(&GraphvizDepGraph(tcx.dep_graph.clone()), &mut v).unwrap();
183+
File::create(&path).and_then(|mut f| f.write_all(&v)).unwrap();
184+
}
185+
186+
pub struct GraphvizDepGraph(Rc<DepGraph>);
187+
188+
impl<'a, 'tcx> dot::GraphWalk<'a, DepNode, (DepNode, DepNode)> for GraphvizDepGraph {
189+
fn nodes(&self) -> dot::Nodes<DepNode> {
190+
self.0.nodes().into_cow()
191+
}
192+
fn edges(&self) -> dot::Edges<(DepNode, DepNode)> {
193+
self.0.edges().into_cow()
194+
}
195+
fn source(&self, edge: &(DepNode, DepNode)) -> DepNode {
196+
edge.0.clone()
197+
}
198+
fn target(&self, edge: &(DepNode, DepNode)) -> DepNode {
199+
edge.1.clone()
200+
}
201+
}
202+
203+
impl<'a, 'tcx> dot::Labeller<'a, DepNode, (DepNode, DepNode)> for GraphvizDepGraph {
204+
fn graph_id(&self) -> dot::Id {
205+
dot::Id::new("DependencyGraph").unwrap()
206+
}
207+
fn node_id(&self, n: &DepNode) -> dot::Id {
208+
let s: String =
209+
format!("{:?}", n).chars()
210+
.map(|c| if c == '_' || c.is_alphanumeric() { c } else { '_' })
211+
.collect();
212+
debug!("n={:?} s={:?}", n, s);
213+
dot::Id::new(s).unwrap()
214+
}
215+
fn node_label(&self, n: &DepNode) -> dot::LabelText {
216+
dot::LabelText::label(format!("{:?}", n))
217+
}
218+
}

src/librustc_trans/trans/base.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3135,6 +3135,8 @@ pub fn trans_crate<'tcx>(tcx: &ty::ctxt<'tcx>,
31353135
llmod: shared_ccx.metadata_llmod(),
31363136
};
31373137

3138+
assert_dep_graph::assert_dep_graph(tcx);
3139+
31383140
CrateTranslation {
31393141
modules: modules,
31403142
metadata_module: metadata_module,

src/librustc_trans/trans/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mod macros;
2020

2121
mod adt;
2222
mod asm;
23+
mod assert_dep_graph;
2324
mod attributes;
2425
mod base;
2526
mod basic_block;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2012-2014 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.
4+
//
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.
10+
11+
// Test that two unrelated functions have no trans dependency.
12+
13+
#![feature(rustc_attrs)]
14+
#![allow(unused_attributes)]
15+
#![allow(dead_code)]
16+
17+
fn main() { }
18+
19+
mod x {
20+
#[rustc_if_this_changed]
21+
//~^ ERROR no path to TypeckItemBody for `z::z`
22+
//~| ERROR no path to TransCrateItem for `z::z`
23+
pub fn x() { }
24+
}
25+
26+
mod y {
27+
use x;
28+
29+
// These dependencies SHOULD exist:
30+
#[rustc_then_this_would_need(TypeckItemBody)]
31+
#[rustc_then_this_would_need(TransCrateItem)]
32+
pub fn y() {
33+
x::x();
34+
}
35+
}
36+
37+
mod z {
38+
use y;
39+
40+
// These are expected to yield errors, because changes to `x`
41+
// affect the BODY of `y`, but not its signature.
42+
#[rustc_then_this_would_need(TypeckItemBody)]
43+
#[rustc_then_this_would_need(TransCrateItem)]
44+
pub fn z() {
45+
y::y();
46+
}
47+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2012-2014 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.
4+
//
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.
10+
11+
// Test that two unrelated functions have no trans dependency.
12+
13+
#![feature(rustc_attrs)]
14+
#![allow(unused_attributes)]
15+
#![allow(dead_code)]
16+
17+
#[rustc_if_this_changed] //~ ERROR no path to TransCrateItem for `bar`
18+
fn main() { }
19+
20+
#[rustc_then_this_would_need(TransCrateItem)]
21+
fn bar() { }

0 commit comments

Comments
 (0)