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 fingerprints computed for query results.
12 //! For each node marked with `#[rustc_clean]` or `#[rustc_dirty]`,
13 //! we will compare the fingerprint from the current and from the previous
14 //! compilation session as appropriate:
16 //! - `#[rustc_clean(cfg="rev2", except="TypeckTables")]` if we are
17 //! in `#[cfg(rev2)]`, then the fingerprints associated with
18 //! `DepNode::TypeckTables(X)` must be DIFFERENT (`X` is the def-id of the
20 //! - `#[rustc_clean(cfg="rev2")]` same as above, except that the
21 //! fingerprints must be the SAME (along with all other fingerprints).
23 //! Errors are reported if we are in the suitable configuration but
24 //! the required condition is not met.
27 use std::collections::HashSet;
28 use std::iter::FromIterator;
30 use rustc::dep_graph::{DepNode, label_strs};
32 use rustc::hir::{Item_ as HirItem, ImplItemKind, TraitItemKind};
33 use rustc::hir::map::Node as HirNode;
34 use rustc::hir::def_id::DefId;
35 use rustc::hir::itemlikevisit::ItemLikeVisitor;
36 use rustc::hir::intravisit;
37 use rustc::ich::{ATTR_DIRTY, ATTR_CLEAN};
38 use syntax::ast::{self, Attribute, NestedMetaItem};
39 use rustc_data_structures::fx::FxHashSet;
41 use rustc::ty::TyCtxt;
43 const EXCEPT: &str = "except";
44 const LABEL: &str = "label";
45 const CFG: &str = "cfg";
47 // Base and Extra labels to build up the labels
49 /// For typedef, constants, and statics
50 const BASE_CONST: &[&str] = &[
51 label_strs::TypeOfItem,
54 /// DepNodes for functions + methods
55 const BASE_FN: &[&str] = &[
56 // Callers will depend on the signature of these items, so we better test
57 label_strs::FnSignature,
58 label_strs::GenericsOfItem,
59 label_strs::PredicatesOfItem,
60 label_strs::TypeOfItem,
62 // And a big part of compilation (that we eventually want to cache) is type inference
64 label_strs::TypeckTables,
67 /// DepNodes for Hir, which is pretty much everything
68 const BASE_HIR: &[&str] = &[
69 // Hir and HirBody should be computed for all nodes
74 /// `impl` implementation of struct/trait
75 const BASE_IMPL: &[&str] = &[
76 label_strs::AssociatedItemDefIds,
77 label_strs::GenericsOfItem,
78 label_strs::ImplTraitRef,
81 /// DepNodes for MirValidated/Optimized, which is relevant in "executable"
82 /// code, i.e. functions+methods
83 const BASE_MIR: &[&str] = &[
84 label_strs::MirOptimized,
85 label_strs::MirValidated,
88 /// Struct, Enum and Union DepNodes
90 /// Note that changing the type of a field does not change the type of the struct or enum, but
91 /// adding/removing fields or changing a fields name or visibility does.
92 const BASE_STRUCT: &[&str] = &[
93 label_strs::GenericsOfItem,
94 label_strs::PredicatesOfItem,
95 label_strs::TypeOfItem,
98 /// Trait Definition DepNodes
99 const BASE_TRAIT_DEF: &[&str] = &[
100 label_strs::AssociatedItemDefIds,
101 label_strs::GenericsOfItem,
102 label_strs::ObjectSafety,
103 label_strs::PredicatesOfItem,
104 label_strs::SpecializationGraph,
105 label_strs::TraitDefOfItem,
106 label_strs::TraitImpls,
109 /// extra DepNodes for methods (+fn)
110 const EXTRA_ASSOCIATED: &[&str] = &[
111 label_strs::AssociatedItems,
114 const EXTRA_TRAIT: &[&str] = &[
115 label_strs::TraitOfItem,
118 // Fully Built Labels
120 const LABELS_CONST: &[&[&str]] = &[
125 /// Constant/Typedef in an impl
126 const LABELS_CONST_IN_IMPL: &[&[&str]] = &[
132 /// Trait-Const/Typedef DepNodes
133 const LABELS_CONST_IN_TRAIT: &[&[&str]] = &[
141 const LABELS_FN: &[&[&str]] = &[
148 const LABELS_FN_IN_IMPL: &[&[&str]] = &[
155 /// Trait-Method DepNodes
156 const LABELS_FN_IN_TRAIT: &[&[&str]] = &[
164 /// For generic cases like inline-assemply/mod/etc
165 const LABELS_HIR_ONLY: &[&[&str]] = &[
170 const LABELS_IMPL: &[&[&str]] = &[
175 /// Abstract Data Type (Struct, Enum, Unions) DepNodes
176 const LABELS_ADT: &[&[&str]] = &[
181 /// Trait Definition DepNodes
183 const LABELS_TRAIT: &[&[&str]] = &[
189 // FIXME: Struct/Enum/Unions Fields (there is currently no way to attach these)
191 // Fields are kind of separate from their containers, as they can change independently from
192 // them. We should at least check
194 // TypeOfItem for these.
196 type Labels = HashSet<String>;
198 /// Represents the requested configuration by rustc_clean/dirty
205 fn from_clean_labels(labels: Labels) -> Assertion {
208 dirty: Labels::new(),
212 fn from_dirty_labels(labels: Labels) -> Assertion {
214 clean: Labels::new(),
220 pub fn check_dirty_clean_annotations<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
221 // can't add `#[rustc_dirty]` etc without opting in to this feature
222 if !tcx.features().rustc_attrs {
226 tcx.dep_graph.with_ignore(|| {
227 let krate = tcx.hir.krate();
228 let mut dirty_clean_visitor = DirtyCleanVisitor {
230 checked_attrs: FxHashSet(),
232 krate.visit_all_item_likes(&mut dirty_clean_visitor);
234 let mut all_attrs = FindAllAttrs {
236 attr_names: vec![ATTR_DIRTY, ATTR_CLEAN],
239 intravisit::walk_crate(&mut all_attrs, krate);
241 // Note that we cannot use the existing "unused attribute"-infrastructure
242 // here, since that is running before trans. This is also the reason why
243 // all trans-specific attributes are `Whitelisted` in syntax::feature_gate.
244 all_attrs.report_unchecked_attrs(&dirty_clean_visitor.checked_attrs);
248 pub struct DirtyCleanVisitor<'a, 'tcx:'a> {
249 tcx: TyCtxt<'a, 'tcx, 'tcx>,
250 checked_attrs: FxHashSet<ast::AttrId>,
253 impl<'a, 'tcx> DirtyCleanVisitor<'a, 'tcx> {
255 /// Possibly "deserialize" the attribute into a clean/dirty assertion
256 fn assertion_maybe(&mut self, item_id: ast::NodeId, attr: &Attribute)
259 let is_clean = if attr.check_name(ATTR_DIRTY) {
261 } else if attr.check_name(ATTR_CLEAN) {
264 // skip: not rustc_clean/dirty
267 if !check_config(self.tcx, attr) {
268 // skip: not the correct `cfg=`
271 let assertion = if let Some(labels) = self.labels(attr) {
273 Assertion::from_clean_labels(labels)
275 Assertion::from_dirty_labels(labels)
278 self.assertion_auto(item_id, attr, is_clean)
283 /// Get the "auto" assertion on pre-validated attr, along with the `except` labels
284 fn assertion_auto(&mut self, item_id: ast::NodeId, attr: &Attribute, is_clean: bool)
287 let (name, mut auto) = self.auto_labels(item_id, attr);
288 let except = self.except(attr);
289 for e in except.iter() {
292 "`except` specified DepNodes that can not be affected for \"{}\": \"{}\"",
296 self.tcx.sess.span_fatal(attr.span, &msg);
312 fn labels(&self, attr: &Attribute) -> Option<Labels> {
313 for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
314 if item.check_name(LABEL) {
315 let value = expect_associated_value(self.tcx, &item);
316 return Some(self.resolve_labels(&item, value.as_str().as_ref()));
322 /// `except=` attribute value
323 fn except(&self, attr: &Attribute) -> Labels {
324 for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
325 if item.check_name(EXCEPT) {
326 let value = expect_associated_value(self.tcx, &item);
327 return self.resolve_labels(&item, value.as_str().as_ref());
330 // if no `label` or `except` is given, only the node's group are asserted
334 /// Return all DepNode labels that should be asserted for this item.
335 /// index=0 is the "name" used for error messages
336 fn auto_labels(&mut self, item_id: ast::NodeId, attr: &Attribute) -> (&'static str, Labels) {
337 let node = self.tcx.hir.get(item_id);
338 let (name, labels) = match node {
339 HirNode::NodeItem(item) => {
341 // note: these are in the same order as hir::Item_;
342 // FIXME(michaelwoerister): do commented out ones
344 // // An `extern crate` item, with optional original crate name,
345 // HirItem::ItemExternCrate(..), // intentionally no assertions
347 // // `use foo::bar::*;` or `use foo::bar::baz as quux;`
348 // HirItem::ItemUse(..), // intentionally no assertions
351 HirItem::ItemStatic(..) => ("ItemStatic", LABELS_CONST),
354 HirItem::ItemConst(..) => ("ItemConst", LABELS_CONST),
356 // A function declaration
357 HirItem::ItemFn(..) => ("ItemFn", LABELS_FN),
360 HirItem::ItemMod(..) =>("ItemMod", LABELS_HIR_ONLY),
362 // // An external module
363 HirItem::ItemForeignMod(..) => ("ItemForeignMod", LABELS_HIR_ONLY),
365 // Module-level inline assembly (from global_asm!)
366 HirItem::ItemGlobalAsm(..) => ("ItemGlobalAsm", LABELS_HIR_ONLY),
368 // A type alias, e.g. `type Foo = Bar<u8>`
369 HirItem::ItemTy(..) => ("ItemTy", LABELS_HIR_ONLY),
371 // An enum definition, e.g. `enum Foo<A, B> {C<A>, D<B>}`
372 HirItem::ItemEnum(..) => ("ItemEnum", LABELS_ADT),
374 // A struct definition, e.g. `struct Foo<A> {x: A}`
375 HirItem::ItemStruct(..) => ("ItemStruct", LABELS_ADT),
377 // A union definition, e.g. `union Foo<A, B> {x: A, y: B}`
378 HirItem::ItemUnion(..) => ("ItemUnion", LABELS_ADT),
380 // Represents a Trait Declaration
381 // FIXME(michaelwoerister): trait declaration is buggy because sometimes some of
382 // the depnodes don't exist (because they legitametely didn't need to be
385 // michaelwoerister and vitiral came up with a possible solution,
386 // to just do this before every query
388 // ::rustc::ty::maps::plumbing::force_from_dep_node(tcx, dep_node)
391 // However, this did not seem to work effectively and more bugs were hit.
392 // Nebie @vitiral gave up :)
394 //HirItem::ItemTrait(..) => ("ItemTrait", LABELS_TRAIT),
396 // An implementation, eg `impl<A> Trait for Foo { .. }`
397 HirItem::ItemImpl(..) => ("ItemImpl", LABELS_IMPL),
399 _ => self.tcx.sess.span_fatal(
402 "clean/dirty auto-assertions not yet defined for NodeItem.node={:?}",
408 HirNode::NodeTraitItem(item) => {
410 TraitItemKind::Method(..) => ("NodeTraitItem", LABELS_FN_IN_TRAIT),
411 TraitItemKind::Const(..) => ("NodeTraitConst", LABELS_CONST_IN_TRAIT),
412 TraitItemKind::Type(..) => ("NodeTraitType", LABELS_CONST_IN_TRAIT),
415 HirNode::NodeImplItem(item) => {
417 ImplItemKind::Method(..) => ("NodeImplItem", LABELS_FN_IN_IMPL),
418 ImplItemKind::Const(..) => ("NodeImplConst", LABELS_CONST_IN_IMPL),
419 ImplItemKind::Type(..) => ("NodeImplType", LABELS_CONST_IN_IMPL),
422 _ => self.tcx.sess.span_fatal(
425 "clean/dirty auto-assertions not yet defined for {:?}",
430 let labels = Labels::from_iter(
431 labels.iter().flat_map(|s| s.iter().map(|l| l.to_string()))
436 fn resolve_labels(&self, item: &NestedMetaItem, value: &str) -> Labels {
437 let mut out: Labels = HashSet::new();
438 for label in value.split(',') {
439 let label = label.trim();
440 if DepNode::has_label_string(label) {
441 if out.contains(label) {
442 self.tcx.sess.span_fatal(
444 &format!("dep-node label `{}` is repeated", label));
446 out.insert(label.to_string());
448 self.tcx.sess.span_fatal(
450 &format!("dep-node label `{}` not recognized", label));
456 fn dep_nodes(&self, labels: &Labels, def_id: DefId) -> Vec<DepNode> {
457 let mut out = Vec::with_capacity(labels.len());
458 let def_path_hash = self.tcx.def_path_hash(def_id);
459 for label in labels.iter() {
460 match DepNode::from_label_string(label, def_path_hash) {
461 Ok(dep_node) => out.push(dep_node),
462 Err(()) => unreachable!(),
468 fn dep_node_str(&self, dep_node: &DepNode) -> String {
469 if let Some(def_id) = dep_node.extract_def_id(self.tcx) {
472 self.tcx.item_path_str(def_id))
474 format!("{:?}({:?})", dep_node.kind, dep_node.hash)
478 fn assert_dirty(&self, item_span: Span, dep_node: DepNode) {
479 debug!("assert_dirty({:?})", dep_node);
481 let dep_node_index = self.tcx.dep_graph.dep_node_index_of(&dep_node);
482 let current_fingerprint = self.tcx.dep_graph.fingerprint_of(dep_node_index);
483 let prev_fingerprint = self.tcx.dep_graph.prev_fingerprint_of(&dep_node);
485 if Some(current_fingerprint) == prev_fingerprint {
486 let dep_node_str = self.dep_node_str(&dep_node);
487 self.tcx.sess.span_err(
489 &format!("`{}` should be dirty but is not", dep_node_str));
493 fn assert_clean(&self, item_span: Span, dep_node: DepNode) {
494 debug!("assert_clean({:?})", dep_node);
496 let dep_node_index = self.tcx.dep_graph.dep_node_index_of(&dep_node);
497 let current_fingerprint = self.tcx.dep_graph.fingerprint_of(dep_node_index);
498 let prev_fingerprint = self.tcx.dep_graph.prev_fingerprint_of(&dep_node);
500 if Some(current_fingerprint) != prev_fingerprint {
501 let dep_node_str = self.dep_node_str(&dep_node);
502 self.tcx.sess.span_err(
504 &format!("`{}` should be clean but is not", dep_node_str));
508 fn check_item(&mut self, item_id: ast::NodeId, item_span: Span) {
509 let def_id = self.tcx.hir.local_def_id(item_id);
510 for attr in self.tcx.get_attrs(def_id).iter() {
511 let assertion = match self.assertion_maybe(item_id, attr) {
515 self.checked_attrs.insert(attr.id);
516 for dep_node in self.dep_nodes(&assertion.clean, def_id) {
517 self.assert_clean(item_span, dep_node);
519 for dep_node in self.dep_nodes(&assertion.dirty, def_id) {
520 self.assert_dirty(item_span, dep_node);
526 impl<'a, 'tcx> ItemLikeVisitor<'tcx> for DirtyCleanVisitor<'a, 'tcx> {
527 fn visit_item(&mut self, item: &'tcx hir::Item) {
528 self.check_item(item.id, item.span);
531 fn visit_trait_item(&mut self, item: &hir::TraitItem) {
532 self.check_item(item.id, item.span);
535 fn visit_impl_item(&mut self, item: &hir::ImplItem) {
536 self.check_item(item.id, item.span);
540 /// Given a `#[rustc_dirty]` or `#[rustc_clean]` attribute, scan
541 /// for a `cfg="foo"` attribute and check whether we have a cfg
542 /// flag called `foo`.
544 /// Also make sure that the `label` and `except` fields do not
546 fn check_config(tcx: TyCtxt, attr: &Attribute) -> bool {
547 debug!("check_config(attr={:?})", attr);
548 let config = &tcx.sess.parse_sess.config;
549 debug!("check_config: config={:?}", config);
550 let (mut cfg, mut except, mut label) = (None, false, false);
551 for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
552 if item.check_name(CFG) {
553 let value = expect_associated_value(tcx, &item);
554 debug!("check_config: searching for cfg {:?}", value);
555 cfg = Some(config.contains(&(value, None)));
557 if item.check_name(LABEL) {
560 if item.check_name(EXCEPT) {
568 "must specify only one of: `label`, `except`"
573 None => tcx.sess.span_fatal(
581 fn expect_associated_value(tcx: TyCtxt, item: &NestedMetaItem) -> ast::Name {
582 if let Some(value) = item.value_str() {
585 let msg = if let Some(name) = item.name() {
586 format!("associated value expected for `{}`", name)
588 "expected an associated value".to_string()
591 tcx.sess.span_fatal(item.span, &msg);
595 // A visitor that collects all #[rustc_dirty]/#[rustc_clean] attributes from
596 // the HIR. It is used to verfiy that we really ran checks for all annotated
598 pub struct FindAllAttrs<'a, 'tcx:'a> {
599 tcx: TyCtxt<'a, 'tcx, 'tcx>,
600 attr_names: Vec<&'static str>,
601 found_attrs: Vec<&'tcx Attribute>,
604 impl<'a, 'tcx> FindAllAttrs<'a, 'tcx> {
606 fn is_active_attr(&mut self, attr: &Attribute) -> bool {
607 for attr_name in &self.attr_names {
608 if attr.check_name(attr_name) && check_config(self.tcx, attr) {
616 fn report_unchecked_attrs(&self, checked_attrs: &FxHashSet<ast::AttrId>) {
617 for attr in &self.found_attrs {
618 if !checked_attrs.contains(&attr.id) {
619 self.tcx.sess.span_err(attr.span, &format!("found unchecked \
620 #[rustc_dirty]/#[rustc_clean] attribute"));
626 impl<'a, 'tcx> intravisit::Visitor<'tcx> for FindAllAttrs<'a, 'tcx> {
627 fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
628 intravisit::NestedVisitorMap::All(&self.tcx.hir)
631 fn visit_attribute(&mut self, attr: &'tcx Attribute) {
632 if self.is_active_attr(attr) {
633 self.found_attrs.push(attr);