1 //! Debugging code to test fingerprints computed for query results.
2 //! For each node marked with `#[rustc_clean]` or `#[rustc_dirty]`,
3 //! we will compare the fingerprint from the current and from the previous
4 //! compilation session as appropriate:
6 //! - `#[rustc_clean(cfg="rev2", except="TypeckTables")]` if we are
7 //! in `#[cfg(rev2)]`, then the fingerprints associated with
8 //! `DepNode::TypeckTables(X)` must be DIFFERENT (`X` is the `DefId` of the
10 //! - `#[rustc_clean(cfg="rev2")]` same as above, except that the
11 //! fingerprints must be the SAME (along with all other fingerprints).
13 //! Errors are reported if we are in the suitable configuration but
14 //! the required condition is not met.
16 use std::iter::FromIterator;
18 use rustc::dep_graph::{DepNode, label_strs};
20 use rustc::hir::{ItemKind as HirItem, ImplItemKind, TraitItemKind};
21 use rustc::hir::Node as HirNode;
22 use rustc::hir::def_id::DefId;
23 use rustc::hir::itemlikevisit::ItemLikeVisitor;
24 use rustc::hir::intravisit;
25 use rustc::ich::{ATTR_DIRTY, ATTR_CLEAN};
26 use syntax::ast::{self, Attribute, NestedMetaItem};
27 use rustc_data_structures::fx::FxHashSet;
29 use rustc::ty::TyCtxt;
31 const EXCEPT: &str = "except";
32 const LABEL: &str = "label";
33 const CFG: &str = "cfg";
35 // Base and Extra labels to build up the labels
37 /// For typedef, constants, and statics
38 const BASE_CONST: &[&str] = &[
42 /// DepNodes for functions + methods
43 const BASE_FN: &[&str] = &[
44 // Callers will depend on the signature of these items, so we better test
45 label_strs::FnSignature,
46 label_strs::generics_of,
47 label_strs::predicates_of,
50 // And a big part of compilation (that we eventually want to cache) is type inference
52 label_strs::TypeckTables,
55 /// DepNodes for Hir, which is pretty much everything
56 const BASE_HIR: &[&str] = &[
57 // Hir and HirBody should be computed for all nodes
62 /// `impl` implementation of struct/trait
63 const BASE_IMPL: &[&str] = &[
64 label_strs::AssociatedItemDefIds,
65 label_strs::generics_of,
66 label_strs::ImplTraitRef,
69 /// DepNodes for MirBuilt/Optimized, which is relevant in "executable"
70 /// code, i.e., functions+methods
71 const BASE_MIR: &[&str] = &[
72 label_strs::MirOptimized,
76 /// Struct, Enum and Union DepNodes
78 /// Note that changing the type of a field does not change the type of the struct or enum, but
79 /// adding/removing fields or changing a fields name or visibility does.
80 const BASE_STRUCT: &[&str] = &[
81 label_strs::generics_of,
82 label_strs::predicates_of,
86 /// Trait definition `DepNode`s.
87 const BASE_TRAIT_DEF: &[&str] = &[
88 label_strs::AssociatedItemDefIds,
89 label_strs::generics_of,
90 label_strs::ObjectSafety,
91 label_strs::predicates_of,
92 label_strs::SpecializationGraph,
93 label_strs::TraitDefOfItem,
94 label_strs::TraitImpls,
97 /// Extra `DepNode`s for functions and methods.
98 const EXTRA_ASSOCIATED: &[&str] = &[
99 label_strs::AssociatedItems,
102 const EXTRA_TRAIT: &[&str] = &[
103 label_strs::TraitOfItem,
106 // Fully Built Labels
108 const LABELS_CONST: &[&[&str]] = &[
113 /// Constant/Typedef in an impl
114 const LABELS_CONST_IN_IMPL: &[&[&str]] = &[
120 /// Trait-Const/Typedef DepNodes
121 const LABELS_CONST_IN_TRAIT: &[&[&str]] = &[
128 /// Function `DepNode`s.
129 const LABELS_FN: &[&[&str]] = &[
135 /// Method `DepNode`s.
136 const LABELS_FN_IN_IMPL: &[&[&str]] = &[
143 /// Trait method `DepNode`s.
144 const LABELS_FN_IN_TRAIT: &[&[&str]] = &[
152 /// For generic cases like inline-assembly, modules, etc.
153 const LABELS_HIR_ONLY: &[&[&str]] = &[
158 const LABELS_IMPL: &[&[&str]] = &[
163 /// Abstract data type (struct, enum, union) `DepNode`s.
164 const LABELS_ADT: &[&[&str]] = &[
169 /// Trait definition `DepNode`s.
171 const LABELS_TRAIT: &[&[&str]] = &[
177 // FIXME: Struct/Enum/Unions Fields (there is currently no way to attach these)
179 // Fields are kind of separate from their containers, as they can change independently from
180 // them. We should at least check
182 // type_of for these.
184 type Labels = FxHashSet<String>;
186 /// Represents the requested configuration by rustc_clean/dirty
193 fn from_clean_labels(labels: Labels) -> Assertion {
196 dirty: Labels::default(),
200 fn from_dirty_labels(labels: Labels) -> Assertion {
202 clean: Labels::default(),
208 pub fn check_dirty_clean_annotations<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
209 // can't add `#[rustc_dirty]` etc without opting in to this feature
210 if !tcx.features().rustc_attrs {
214 tcx.dep_graph.with_ignore(|| {
215 let krate = tcx.hir().krate();
216 let mut dirty_clean_visitor = DirtyCleanVisitor {
218 checked_attrs: Default::default(),
220 krate.visit_all_item_likes(&mut dirty_clean_visitor);
222 let mut all_attrs = FindAllAttrs {
224 attr_names: vec![ATTR_DIRTY, ATTR_CLEAN],
227 intravisit::walk_crate(&mut all_attrs, krate);
229 // Note that we cannot use the existing "unused attribute"-infrastructure
230 // here, since that is running before codegen. This is also the reason why
231 // all codegen-specific attributes are `Whitelisted` in syntax::feature_gate.
232 all_attrs.report_unchecked_attrs(&dirty_clean_visitor.checked_attrs);
236 pub struct DirtyCleanVisitor<'a, 'tcx:'a> {
237 tcx: TyCtxt<'a, 'tcx, 'tcx>,
238 checked_attrs: FxHashSet<ast::AttrId>,
241 impl<'a, 'tcx> DirtyCleanVisitor<'a, 'tcx> {
243 /// Possibly "deserialize" the attribute into a clean/dirty assertion
244 fn assertion_maybe(&mut self, item_id: hir::HirId, attr: &Attribute)
247 let is_clean = if attr.check_name(ATTR_DIRTY) {
249 } else if attr.check_name(ATTR_CLEAN) {
252 // skip: not rustc_clean/dirty
255 if !check_config(self.tcx, attr) {
256 // skip: not the correct `cfg=`
259 let assertion = if let Some(labels) = self.labels(attr) {
261 Assertion::from_clean_labels(labels)
263 Assertion::from_dirty_labels(labels)
266 self.assertion_auto(item_id, attr, is_clean)
271 /// Gets the "auto" assertion on pre-validated attr, along with the `except` labels.
272 fn assertion_auto(&mut self, item_id: hir::HirId, attr: &Attribute, is_clean: bool)
275 let (name, mut auto) = self.auto_labels(item_id, attr);
276 let except = self.except(attr);
277 for e in except.iter() {
280 "`except` specified DepNodes that can not be affected for \"{}\": \"{}\"",
284 self.tcx.sess.span_fatal(attr.span, &msg);
300 fn labels(&self, attr: &Attribute) -> Option<Labels> {
301 for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
302 if item.check_name(LABEL) {
303 let value = expect_associated_value(self.tcx, &item);
304 return Some(self.resolve_labels(&item, value.as_str().as_ref()));
310 /// `except=` attribute value
311 fn except(&self, attr: &Attribute) -> Labels {
312 for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
313 if item.check_name(EXCEPT) {
314 let value = expect_associated_value(self.tcx, &item);
315 return self.resolve_labels(&item, value.as_str().as_ref());
318 // if no `label` or `except` is given, only the node's group are asserted
322 /// Return all DepNode labels that should be asserted for this item.
323 /// index=0 is the "name" used for error messages
324 fn auto_labels(&mut self, item_id: hir::HirId, attr: &Attribute) -> (&'static str, Labels) {
325 let node = self.tcx.hir().get_by_hir_id(item_id);
326 let (name, labels) = match node {
327 HirNode::Item(item) => {
329 // note: these are in the same order as hir::Item_;
330 // FIXME(michaelwoerister): do commented out ones
332 // // An `extern crate` item, with optional original crate name,
333 // HirItem::ExternCrate(..), // intentionally no assertions
335 // // `use foo::bar::*;` or `use foo::bar::baz as quux;`
336 // HirItem::Use(..), // intentionally no assertions
339 HirItem::Static(..) => ("ItemStatic", LABELS_CONST),
342 HirItem::Const(..) => ("ItemConst", LABELS_CONST),
344 // A function declaration
345 HirItem::Fn(..) => ("ItemFn", LABELS_FN),
348 HirItem::Mod(..) =>("ItemMod", LABELS_HIR_ONLY),
350 // // An external module
351 HirItem::ForeignMod(..) => ("ItemForeignMod", LABELS_HIR_ONLY),
353 // Module-level inline assembly (from global_asm!)
354 HirItem::GlobalAsm(..) => ("ItemGlobalAsm", LABELS_HIR_ONLY),
356 // A type alias, e.g., `type Foo = Bar<u8>`
357 HirItem::Ty(..) => ("ItemTy", LABELS_HIR_ONLY),
359 // An enum definition, e.g., `enum Foo<A, B> {C<A>, D<B>}`
360 HirItem::Enum(..) => ("ItemEnum", LABELS_ADT),
362 // A struct definition, e.g., `struct Foo<A> {x: A}`
363 HirItem::Struct(..) => ("ItemStruct", LABELS_ADT),
365 // A union definition, e.g., `union Foo<A, B> {x: A, y: B}`
366 HirItem::Union(..) => ("ItemUnion", LABELS_ADT),
368 // Represents a Trait Declaration
369 // FIXME(michaelwoerister): trait declaration is buggy because sometimes some of
370 // the depnodes don't exist (because they legitametely didn't need to be
373 // michaelwoerister and vitiral came up with a possible solution,
374 // to just do this before every query
376 // ::rustc::ty::query::plumbing::force_from_dep_node(tcx, dep_node)
379 // However, this did not seem to work effectively and more bugs were hit.
380 // Nebie @vitiral gave up :)
382 //HirItem::Trait(..) => ("ItemTrait", LABELS_TRAIT),
384 // An implementation, eg `impl<A> Trait for Foo { .. }`
385 HirItem::Impl(..) => ("ItemKind::Impl", LABELS_IMPL),
387 _ => self.tcx.sess.span_fatal(
390 "clean/dirty auto-assertions not yet defined \
391 for Node::Item.node={:?}",
397 HirNode::TraitItem(item) => {
399 TraitItemKind::Method(..) => ("Node::TraitItem", LABELS_FN_IN_TRAIT),
400 TraitItemKind::Const(..) => ("NodeTraitConst", LABELS_CONST_IN_TRAIT),
401 TraitItemKind::Type(..) => ("NodeTraitType", LABELS_CONST_IN_TRAIT),
404 HirNode::ImplItem(item) => {
406 ImplItemKind::Method(..) => ("Node::ImplItem", LABELS_FN_IN_IMPL),
407 ImplItemKind::Const(..) => ("NodeImplConst", LABELS_CONST_IN_IMPL),
408 ImplItemKind::Type(..) => ("NodeImplType", LABELS_CONST_IN_IMPL),
409 ImplItemKind::Existential(..) => ("NodeImplType", LABELS_CONST_IN_IMPL),
412 _ => self.tcx.sess.span_fatal(
415 "clean/dirty auto-assertions not yet defined for {:?}",
420 let labels = Labels::from_iter(
421 labels.iter().flat_map(|s| s.iter().map(|l| l.to_string()))
426 fn resolve_labels(&self, item: &NestedMetaItem, value: &str) -> Labels {
427 let mut out = Labels::default();
428 for label in value.split(',') {
429 let label = label.trim();
430 if DepNode::has_label_string(label) {
431 if out.contains(label) {
432 self.tcx.sess.span_fatal(
434 &format!("dep-node label `{}` is repeated", label));
436 out.insert(label.to_string());
438 self.tcx.sess.span_fatal(
440 &format!("dep-node label `{}` not recognized", label));
450 ) -> impl Iterator<Item = DepNode> + 'l {
451 let def_path_hash = self.tcx.def_path_hash(def_id);
455 match DepNode::from_label_string(label, def_path_hash) {
456 Ok(dep_node) => dep_node,
457 Err(()) => unreachable!(),
462 fn dep_node_str(&self, dep_node: &DepNode) -> String {
463 if let Some(def_id) = dep_node.extract_def_id(self.tcx) {
466 self.tcx.def_path_str(def_id))
468 format!("{:?}({:?})", dep_node.kind, dep_node.hash)
472 fn assert_dirty(&self, item_span: Span, dep_node: DepNode) {
473 debug!("assert_dirty({:?})", dep_node);
475 let dep_node_index = self.tcx.dep_graph.dep_node_index_of(&dep_node);
476 let current_fingerprint = self.tcx.dep_graph.fingerprint_of(dep_node_index);
477 let prev_fingerprint = self.tcx.dep_graph.prev_fingerprint_of(&dep_node);
479 if Some(current_fingerprint) == prev_fingerprint {
480 let dep_node_str = self.dep_node_str(&dep_node);
481 self.tcx.sess.span_err(
483 &format!("`{}` should be dirty but is not", dep_node_str));
487 fn assert_clean(&self, item_span: Span, dep_node: DepNode) {
488 debug!("assert_clean({:?})", dep_node);
490 let dep_node_index = self.tcx.dep_graph.dep_node_index_of(&dep_node);
491 let current_fingerprint = self.tcx.dep_graph.fingerprint_of(dep_node_index);
492 let prev_fingerprint = self.tcx.dep_graph.prev_fingerprint_of(&dep_node);
494 if Some(current_fingerprint) != prev_fingerprint {
495 let dep_node_str = self.dep_node_str(&dep_node);
496 self.tcx.sess.span_err(
498 &format!("`{}` should be clean but is not", dep_node_str));
502 fn check_item(&mut self, item_id: hir::HirId, item_span: Span) {
503 let def_id = self.tcx.hir().local_def_id_from_hir_id(item_id);
504 for attr in self.tcx.get_attrs(def_id).iter() {
505 let assertion = match self.assertion_maybe(item_id, attr) {
509 self.checked_attrs.insert(attr.id);
510 for dep_node in self.dep_nodes(&assertion.clean, def_id) {
511 self.assert_clean(item_span, dep_node);
513 for dep_node in self.dep_nodes(&assertion.dirty, def_id) {
514 self.assert_dirty(item_span, dep_node);
520 impl<'a, 'tcx> ItemLikeVisitor<'tcx> for DirtyCleanVisitor<'a, 'tcx> {
521 fn visit_item(&mut self, item: &'tcx hir::Item) {
522 self.check_item(item.hir_id, item.span);
525 fn visit_trait_item(&mut self, item: &hir::TraitItem) {
526 self.check_item(item.hir_id, item.span);
529 fn visit_impl_item(&mut self, item: &hir::ImplItem) {
530 self.check_item(item.hir_id, item.span);
534 /// Given a `#[rustc_dirty]` or `#[rustc_clean]` attribute, scan
535 /// for a `cfg="foo"` attribute and check whether we have a cfg
536 /// flag called `foo`.
538 /// Also make sure that the `label` and `except` fields do not
540 fn check_config(tcx: TyCtxt<'_, '_, '_>, attr: &Attribute) -> bool {
541 debug!("check_config(attr={:?})", attr);
542 let config = &tcx.sess.parse_sess.config;
543 debug!("check_config: config={:?}", config);
544 let (mut cfg, mut except, mut label) = (None, false, false);
545 for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
546 if item.check_name(CFG) {
547 let value = expect_associated_value(tcx, &item);
548 debug!("check_config: searching for cfg {:?}", value);
549 cfg = Some(config.contains(&(value, None)));
551 if item.check_name(LABEL) {
554 if item.check_name(EXCEPT) {
562 "must specify only one of: `label`, `except`"
567 None => tcx.sess.span_fatal(
575 fn expect_associated_value(tcx: TyCtxt<'_, '_, '_>, item: &NestedMetaItem) -> ast::Name {
576 if let Some(value) = item.value_str() {
579 let msg = if let Some(name) = item.ident_str() {
580 format!("associated value expected for `{}`", name)
582 "expected an associated value".to_string()
585 tcx.sess.span_fatal(item.span(), &msg);
589 // A visitor that collects all #[rustc_dirty]/#[rustc_clean] attributes from
590 // the HIR. It is used to verfiy that we really ran checks for all annotated
592 pub struct FindAllAttrs<'a, 'tcx:'a> {
593 tcx: TyCtxt<'a, 'tcx, 'tcx>,
594 attr_names: Vec<&'static str>,
595 found_attrs: Vec<&'tcx Attribute>,
598 impl<'a, 'tcx> FindAllAttrs<'a, 'tcx> {
600 fn is_active_attr(&mut self, attr: &Attribute) -> bool {
601 for attr_name in &self.attr_names {
602 if attr.check_name(attr_name) && check_config(self.tcx, attr) {
610 fn report_unchecked_attrs(&self, checked_attrs: &FxHashSet<ast::AttrId>) {
611 for attr in &self.found_attrs {
612 if !checked_attrs.contains(&attr.id) {
613 self.tcx.sess.span_err(attr.span, &format!("found unchecked \
614 #[rustc_dirty]/#[rustc_clean] attribute"));
620 impl<'a, 'tcx> intravisit::Visitor<'tcx> for FindAllAttrs<'a, 'tcx> {
621 fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
622 intravisit::NestedVisitorMap::All(&self.tcx.hir())
625 fn visit_attribute(&mut self, attr: &'tcx Attribute) {
626 if self.is_active_attr(attr) {
627 self.found_attrs.push(attr);