1 // Copyright 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.
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 //! Debugging code to test the state of the dependency graph just
12 //! after it is loaded from disk and just after it has been saved.
13 //! For each node marked with `#[rustc_clean]` or `#[rustc_dirty]`,
14 //! we will check that a suitable node for that item either appears
15 //! or does not appear in the dep-graph, as appropriate:
17 //! - `#[rustc_dirty(label="TypeckTables", cfg="rev2")]` if we are
18 //! in `#[cfg(rev2)]`, then there MUST NOT be a node
19 //! `DepNode::TypeckTables(X)` where `X` is the def-id of the
21 //! - `#[rustc_clean(label="TypeckTables", cfg="rev2")]` same as above,
22 //! except that the node MUST exist.
24 //! Errors are reported if we are in the suitable configuration but
25 //! the required condition is not met.
27 //! The `#[rustc_metadata_dirty]` and `#[rustc_metadata_clean]` attributes
28 //! can be used to check the incremental compilation hash (ICH) values of
29 //! metadata exported in rlibs.
31 //! - If a node is marked with `#[rustc_metadata_clean(cfg="rev2")]` we
32 //! check that the metadata hash for that node is the same for "rev2"
33 //! it was for "rev1".
34 //! - If a node is marked with `#[rustc_metadata_dirty(cfg="rev2")]` we
35 //! check that the metadata hash for that node is *different* for "rev2"
36 //! than it was for "rev1".
38 //! Note that the metadata-testing attributes must never specify the
39 //! first revision. This would lead to a crash since there is no
40 //! previous revision to compare things to.
43 use super::directory::RetracedDefIdDirectory;
44 use super::load::DirtyNodes;
45 use rustc::dep_graph::{DepGraphQuery, DepNode};
47 use rustc::hir::def_id::DefId;
48 use rustc::hir::itemlikevisit::ItemLikeVisitor;
49 use syntax::ast::{self, Attribute, NestedMetaItem};
50 use rustc_data_structures::fx::{FxHashSet, FxHashMap};
52 use rustc::ty::TyCtxt;
55 use {ATTR_DIRTY, ATTR_CLEAN, ATTR_DIRTY_METADATA, ATTR_CLEAN_METADATA};
57 const LABEL: &'static str = "label";
58 const CFG: &'static str = "cfg";
60 pub fn check_dirty_clean_annotations<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
61 dirty_inputs: &DirtyNodes,
62 retraced: &RetracedDefIdDirectory) {
63 // can't add `#[rustc_dirty]` etc without opting in to this feature
64 if !tcx.sess.features.borrow().rustc_attrs {
68 let _ignore = tcx.dep_graph.in_ignore();
69 let dirty_inputs: FxHashSet<DepNode<DefId>> =
71 .filter_map(|d| retraced.map(d))
73 let query = tcx.dep_graph.query();
74 debug!("query-nodes: {:?}", query.nodes());
75 let krate = tcx.hir.krate();
76 krate.visit_all_item_likes(&mut DirtyCleanVisitor {
79 dirty_inputs: dirty_inputs,
83 pub struct DirtyCleanVisitor<'a, 'tcx:'a> {
84 tcx: TyCtxt<'a, 'tcx, 'tcx>,
85 query: &'a DepGraphQuery<DefId>,
86 dirty_inputs: FxHashSet<DepNode<DefId>>,
89 impl<'a, 'tcx> DirtyCleanVisitor<'a, 'tcx> {
90 fn dep_node(&self, attr: &Attribute, def_id: DefId) -> DepNode<DefId> {
91 for item in attr.meta_item_list().unwrap_or(&[]) {
92 if item.check_name(LABEL) {
93 let value = expect_associated_value(self.tcx, item);
94 match DepNode::from_label_string(&value.as_str(), def_id) {
95 Ok(def_id) => return def_id,
97 self.tcx.sess.span_fatal(
99 &format!("dep-node label `{}` not recognized", value));
105 self.tcx.sess.span_fatal(attr.span, "no `label` found");
108 fn dep_node_str(&self, dep_node: &DepNode<DefId>) -> DepNode<String> {
109 dep_node.map_def(|&def_id| Some(self.tcx.item_path_str(def_id))).unwrap()
112 fn assert_dirty(&self, item: &hir::Item, dep_node: DepNode<DefId>) {
113 debug!("assert_dirty({:?})", dep_node);
118 DepNode::HirBody(_) => {
119 // HIR nodes are inputs, so if we are asserting that the HIR node is
120 // dirty, we check the dirty input set.
121 if !self.dirty_inputs.contains(&dep_node) {
122 let dep_node_str = self.dep_node_str(&dep_node);
123 self.tcx.sess.span_err(
125 &format!("`{:?}` not found in dirty set, but should be dirty",
130 // Other kinds of nodes would be targets, so check if
131 // the dep-graph contains the node.
132 if self.query.contains_node(&dep_node) {
133 let dep_node_str = self.dep_node_str(&dep_node);
134 self.tcx.sess.span_err(
136 &format!("`{:?}` found in dep graph, but should be dirty", dep_node_str));
142 fn assert_clean(&self, item: &hir::Item, dep_node: DepNode<DefId>) {
143 debug!("assert_clean({:?})", dep_node);
148 DepNode::HirBody(_) => {
149 // For HIR nodes, check the inputs.
150 if self.dirty_inputs.contains(&dep_node) {
151 let dep_node_str = self.dep_node_str(&dep_node);
152 self.tcx.sess.span_err(
154 &format!("`{:?}` found in dirty-node set, but should be clean",
159 // Otherwise, check if the dep-node exists.
160 if !self.query.contains_node(&dep_node) {
161 let dep_node_str = self.dep_node_str(&dep_node);
162 self.tcx.sess.span_err(
164 &format!("`{:?}` not found in dep graph, but should be clean",
172 impl<'a, 'tcx> ItemLikeVisitor<'tcx> for DirtyCleanVisitor<'a, 'tcx> {
173 fn visit_item(&mut self, item: &'tcx hir::Item) {
174 let def_id = self.tcx.hir.local_def_id(item.id);
175 for attr in self.tcx.get_attrs(def_id).iter() {
176 if attr.check_name(ATTR_DIRTY) {
177 if check_config(self.tcx, attr) {
178 self.assert_dirty(item, self.dep_node(attr, def_id));
180 } else if attr.check_name(ATTR_CLEAN) {
181 if check_config(self.tcx, attr) {
182 self.assert_clean(item, self.dep_node(attr, def_id));
188 fn visit_trait_item(&mut self, _trait_item: &hir::TraitItem) {
191 fn visit_impl_item(&mut self, _impl_item: &hir::ImplItem) {
195 pub fn check_dirty_clean_metadata<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
196 prev_metadata_hashes: &FxHashMap<DefId, Fingerprint>,
197 current_metadata_hashes: &FxHashMap<DefId, Fingerprint>) {
198 if !tcx.sess.opts.debugging_opts.query_dep_graph {
202 tcx.dep_graph.with_ignore(||{
203 let krate = tcx.hir.krate();
204 krate.visit_all_item_likes(&mut DirtyCleanMetadataVisitor {
206 prev_metadata_hashes: prev_metadata_hashes,
207 current_metadata_hashes: current_metadata_hashes,
212 pub struct DirtyCleanMetadataVisitor<'a, 'tcx:'a, 'm> {
213 tcx: TyCtxt<'a, 'tcx, 'tcx>,
214 prev_metadata_hashes: &'m FxHashMap<DefId, Fingerprint>,
215 current_metadata_hashes: &'m FxHashMap<DefId, Fingerprint>,
218 impl<'a, 'tcx, 'm> ItemLikeVisitor<'tcx> for DirtyCleanMetadataVisitor<'a, 'tcx, 'm> {
219 fn visit_item(&mut self, item: &'tcx hir::Item) {
220 let def_id = self.tcx.hir.local_def_id(item.id);
222 for attr in self.tcx.get_attrs(def_id).iter() {
223 if attr.check_name(ATTR_DIRTY_METADATA) {
224 if check_config(self.tcx, attr) {
225 self.assert_state(false, def_id, item.span);
227 } else if attr.check_name(ATTR_CLEAN_METADATA) {
228 if check_config(self.tcx, attr) {
229 self.assert_state(true, def_id, item.span);
235 fn visit_trait_item(&mut self, _trait_item: &hir::TraitItem) {
238 fn visit_impl_item(&mut self, _impl_item: &hir::ImplItem) {
242 impl<'a, 'tcx, 'm> DirtyCleanMetadataVisitor<'a, 'tcx, 'm> {
244 fn assert_state(&self, should_be_clean: bool, def_id: DefId, span: Span) {
245 let item_path = self.tcx.item_path_str(def_id);
246 debug!("assert_state({})", item_path);
248 if let Some(&prev_hash) = self.prev_metadata_hashes.get(&def_id) {
249 let hashes_are_equal = prev_hash == self.current_metadata_hashes[&def_id];
251 if should_be_clean && !hashes_are_equal {
252 self.tcx.sess.span_err(
254 &format!("Metadata hash of `{}` is dirty, but should be clean",
258 let should_be_dirty = !should_be_clean;
259 if should_be_dirty && hashes_are_equal {
260 self.tcx.sess.span_err(
262 &format!("Metadata hash of `{}` is clean, but should be dirty",
266 self.tcx.sess.span_err(
268 &format!("Could not find previous metadata hash of `{}`",
274 /// Given a `#[rustc_dirty]` or `#[rustc_clean]` attribute, scan
275 /// for a `cfg="foo"` attribute and check whether we have a cfg
276 /// flag called `foo`.
277 fn check_config(tcx: TyCtxt, attr: &ast::Attribute) -> bool {
278 debug!("check_config(attr={:?})", attr);
279 let config = &tcx.sess.parse_sess.config;
280 debug!("check_config: config={:?}", config);
281 for item in attr.meta_item_list().unwrap_or(&[]) {
282 if item.check_name(CFG) {
283 let value = expect_associated_value(tcx, item);
284 debug!("check_config: searching for cfg {:?}", value);
285 return config.contains(&(value, None));
291 &format!("no cfg attribute"));
294 fn expect_associated_value(tcx: TyCtxt, item: &NestedMetaItem) -> ast::Name {
295 if let Some(value) = item.value_str() {
298 let msg = if let Some(name) = item.name() {
299 format!("associated value expected for `{}`", name)
301 "expected an associated value".to_string()
304 tcx.sess.span_fatal(item.span, &msg);