//! Debugging code to test fingerprints computed for query results. For each node marked with //! `#[rustc_clean]` we will compare the fingerprint from the current and from the previous //! compilation session as appropriate: //! //! - `#[rustc_clean(cfg="rev2", except="typeck")]` if we are //! in `#[cfg(rev2)]`, then the fingerprints associated with //! `DepNode::typeck(X)` must be DIFFERENT (`X` is the `DefId` of the //! current node). //! - `#[rustc_clean(cfg="rev2")]` same as above, except that the //! fingerprints must be the SAME (along with all other fingerprints). //! //! - `#[rustc_clean(cfg="rev2", loaded_from_disk='typeck")]` asserts that //! the query result for `DepNode::typeck(X)` was actually //! loaded from disk (not just marked green). This can be useful //! to ensure that a test is actually exercising the deserialization //! logic for a particular query result. This can be combined with //! `except` //! //! Errors are reported if we are in the suitable configuration but //! the required condition is not met. use rustc_ast::{self as ast, Attribute, NestedMetaItem}; use rustc_data_structures::fx::FxHashSet; use rustc_hir::def_id::LocalDefId; use rustc_hir::intravisit; use rustc_hir::Node as HirNode; use rustc_hir::{ImplItemKind, ItemKind as HirItem, TraitItemKind}; use rustc_middle::dep_graph::{label_strs, DepNode, DepNodeExt}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; use rustc_span::symbol::{sym, Symbol}; use rustc_span::Span; const LOADED_FROM_DISK: Symbol = sym::loaded_from_disk; const EXCEPT: Symbol = sym::except; const CFG: Symbol = sym::cfg; // Base and Extra labels to build up the labels /// For typedef, constants, and statics const BASE_CONST: &[&str] = &[label_strs::type_of]; /// DepNodes for functions + methods const BASE_FN: &[&str] = &[ // Callers will depend on the signature of these items, so we better test label_strs::fn_sig, label_strs::generics_of, label_strs::predicates_of, label_strs::type_of, // And a big part of compilation (that we eventually want to cache) is type inference // information: label_strs::typeck, ]; /// DepNodes for Hir, which is pretty much everything const BASE_HIR: &[&str] = &[ // hir_owner and hir_owner_nodes should be computed for all nodes label_strs::hir_owner, label_strs::hir_owner_nodes, ]; /// `impl` implementation of struct/trait const BASE_IMPL: &[&str] = &[label_strs::associated_item_def_ids, label_strs::generics_of, label_strs::impl_trait_ref]; /// DepNodes for mir_built/Optimized, which is relevant in "executable" /// code, i.e., functions+methods const BASE_MIR: &[&str] = &[label_strs::optimized_mir, label_strs::promoted_mir]; /// Struct, Enum and Union DepNodes /// /// Note that changing the type of a field does not change the type of the struct or enum, but /// adding/removing fields or changing a fields name or visibility does. const BASE_STRUCT: &[&str] = &[label_strs::generics_of, label_strs::predicates_of, label_strs::type_of]; /// Trait definition `DepNode`s. /// Extra `DepNode`s for functions and methods. const EXTRA_ASSOCIATED: &[&str] = &[label_strs::associated_item]; const EXTRA_TRAIT: &[&str] = &[]; // Fully Built Labels const LABELS_CONST: &[&[&str]] = &[BASE_HIR, BASE_CONST]; /// Constant/Typedef in an impl const LABELS_CONST_IN_IMPL: &[&[&str]] = &[BASE_HIR, BASE_CONST, EXTRA_ASSOCIATED]; /// Trait-Const/Typedef DepNodes const LABELS_CONST_IN_TRAIT: &[&[&str]] = &[BASE_HIR, BASE_CONST, EXTRA_ASSOCIATED, EXTRA_TRAIT]; /// Function `DepNode`s. const LABELS_FN: &[&[&str]] = &[BASE_HIR, BASE_MIR, BASE_FN]; /// Method `DepNode`s. const LABELS_FN_IN_IMPL: &[&[&str]] = &[BASE_HIR, BASE_MIR, BASE_FN, EXTRA_ASSOCIATED]; /// Trait method `DepNode`s. const LABELS_FN_IN_TRAIT: &[&[&str]] = &[BASE_HIR, BASE_MIR, BASE_FN, EXTRA_ASSOCIATED, EXTRA_TRAIT]; /// For generic cases like inline-assembly, modules, etc. const LABELS_HIR_ONLY: &[&[&str]] = &[BASE_HIR]; /// Impl `DepNode`s. const LABELS_TRAIT: &[&[&str]] = &[ BASE_HIR, &[label_strs::associated_item_def_ids, label_strs::predicates_of, label_strs::generics_of], ]; /// Impl `DepNode`s. const LABELS_IMPL: &[&[&str]] = &[BASE_HIR, BASE_IMPL]; /// Abstract data type (struct, enum, union) `DepNode`s. const LABELS_ADT: &[&[&str]] = &[BASE_HIR, BASE_STRUCT]; // FIXME: Struct/Enum/Unions Fields (there is currently no way to attach these) // // Fields are kind of separate from their containers, as they can change independently from // them. We should at least check // // type_of for these. type Labels = FxHashSet; /// Represents the requested configuration by rustc_clean/dirty struct Assertion { clean: Labels, dirty: Labels, loaded_from_disk: Labels, } pub fn check_dirty_clean_annotations(tcx: TyCtxt<'_>) { if !tcx.sess.opts.unstable_opts.query_dep_graph { return; } // can't add `#[rustc_clean]` etc without opting in to this feature if !tcx.features().rustc_attrs { return; } tcx.dep_graph.with_ignore(|| { let mut dirty_clean_visitor = DirtyCleanVisitor { tcx, checked_attrs: Default::default() }; let crate_items = tcx.hir_crate_items(()); for id in crate_items.items() { dirty_clean_visitor.check_item(id.owner_id.def_id); } for id in crate_items.trait_items() { dirty_clean_visitor.check_item(id.owner_id.def_id); } for id in crate_items.impl_items() { dirty_clean_visitor.check_item(id.owner_id.def_id); } for id in crate_items.foreign_items() { dirty_clean_visitor.check_item(id.owner_id.def_id); } let mut all_attrs = FindAllAttrs { tcx, found_attrs: vec![] }; tcx.hir().walk_attributes(&mut all_attrs); // Note that we cannot use the existing "unused attribute"-infrastructure // here, since that is running before codegen. This is also the reason why // all codegen-specific attributes are `AssumedUsed` in rustc_ast::feature_gate. all_attrs.report_unchecked_attrs(dirty_clean_visitor.checked_attrs); }) } pub struct DirtyCleanVisitor<'tcx> { tcx: TyCtxt<'tcx>, checked_attrs: FxHashSet, } impl<'tcx> DirtyCleanVisitor<'tcx> { /// Possibly "deserialize" the attribute into a clean/dirty assertion fn assertion_maybe(&mut self, item_id: LocalDefId, attr: &Attribute) -> Option { assert!(attr.has_name(sym::rustc_clean)); if !check_config(self.tcx, attr) { // skip: not the correct `cfg=` return None; } let assertion = self.assertion_auto(item_id, attr); Some(assertion) } /// Gets the "auto" assertion on pre-validated attr, along with the `except` labels. fn assertion_auto(&mut self, item_id: LocalDefId, attr: &Attribute) -> Assertion { let (name, mut auto) = self.auto_labels(item_id, attr); let except = self.except(attr); let loaded_from_disk = self.loaded_from_disk(attr); for e in except.iter() { if !auto.remove(e) { let msg = format!( "`except` specified DepNodes that can not be affected for \"{}\": \"{}\"", name, e ); self.tcx.sess.span_fatal(attr.span, &msg); } } Assertion { clean: auto, dirty: except, loaded_from_disk } } /// `loaded_from_disk=` attribute value fn loaded_from_disk(&self, attr: &Attribute) -> Labels { for item in attr.meta_item_list().unwrap_or_else(Vec::new) { if item.has_name(LOADED_FROM_DISK) { let value = expect_associated_value(self.tcx, &item); return self.resolve_labels(&item, value); } } // If `loaded_from_disk=` is not specified, don't assert anything Labels::default() } /// `except=` attribute value fn except(&self, attr: &Attribute) -> Labels { for item in attr.meta_item_list().unwrap_or_else(Vec::new) { if item.has_name(EXCEPT) { let value = expect_associated_value(self.tcx, &item); return self.resolve_labels(&item, value); } } // if no `label` or `except` is given, only the node's group are asserted Labels::default() } /// Return all DepNode labels that should be asserted for this item. /// index=0 is the "name" used for error messages fn auto_labels(&mut self, item_id: LocalDefId, attr: &Attribute) -> (&'static str, Labels) { let node = self.tcx.hir().get_by_def_id(item_id); let (name, labels) = match node { HirNode::Item(item) => { match item.kind { // note: these are in the same order as hir::Item_; // FIXME(michaelwoerister): do commented out ones // // An `extern crate` item, with optional original crate name, // HirItem::ExternCrate(..), // intentionally no assertions // // `use foo::bar::*;` or `use foo::bar::baz as quux;` // HirItem::Use(..), // intentionally no assertions // A `static` item HirItem::Static(..) => ("ItemStatic", LABELS_CONST), // A `const` item HirItem::Const(..) => ("ItemConst", LABELS_CONST), // A function declaration HirItem::Fn(..) => ("ItemFn", LABELS_FN), // // A module HirItem::Mod(..) => ("ItemMod", LABELS_HIR_ONLY), // // An external module HirItem::ForeignMod { .. } => ("ItemForeignMod", LABELS_HIR_ONLY), // Module-level inline assembly (from global_asm!) HirItem::GlobalAsm(..) => ("ItemGlobalAsm", LABELS_HIR_ONLY), // A type alias, e.g., `type Foo = Bar` HirItem::TyAlias(..) => ("ItemTy", LABELS_HIR_ONLY), // An enum definition, e.g., `enum Foo {C, D}` HirItem::Enum(..) => ("ItemEnum", LABELS_ADT), // A struct definition, e.g., `struct Foo {x: A}` HirItem::Struct(..) => ("ItemStruct", LABELS_ADT), // A union definition, e.g., `union Foo {x: A, y: B}` HirItem::Union(..) => ("ItemUnion", LABELS_ADT), // Represents a Trait Declaration HirItem::Trait(..) => ("ItemTrait", LABELS_TRAIT), // An implementation, eg `impl Trait for Foo { .. }` HirItem::Impl { .. } => ("ItemKind::Impl", LABELS_IMPL), _ => self.tcx.sess.span_fatal( attr.span, &format!( "clean/dirty auto-assertions not yet defined \ for Node::Item.node={:?}", item.kind ), ), } } HirNode::TraitItem(item) => match item.kind { TraitItemKind::Fn(..) => ("Node::TraitItem", LABELS_FN_IN_TRAIT), TraitItemKind::Const(..) => ("NodeTraitConst", LABELS_CONST_IN_TRAIT), TraitItemKind::Type(..) => ("NodeTraitType", LABELS_CONST_IN_TRAIT), }, HirNode::ImplItem(item) => match item.kind { ImplItemKind::Fn(..) => ("Node::ImplItem", LABELS_FN_IN_IMPL), ImplItemKind::Const(..) => ("NodeImplConst", LABELS_CONST_IN_IMPL), ImplItemKind::Type(..) => ("NodeImplType", LABELS_CONST_IN_IMPL), }, _ => self.tcx.sess.span_fatal( attr.span, &format!("clean/dirty auto-assertions not yet defined for {:?}", node), ), }; let labels = Labels::from_iter(labels.iter().flat_map(|s| s.iter().map(|l| (*l).to_string()))); (name, labels) } fn resolve_labels(&self, item: &NestedMetaItem, value: Symbol) -> Labels { let mut out = Labels::default(); for label in value.as_str().split(',') { let label = label.trim(); if DepNode::has_label_string(label) { if out.contains(label) { self.tcx.sess.span_fatal( item.span(), &format!("dep-node label `{}` is repeated", label), ); } out.insert(label.to_string()); } else { self.tcx .sess .span_fatal(item.span(), &format!("dep-node label `{}` not recognized", label)); } } out } fn dep_node_str(&self, dep_node: &DepNode) -> String { if let Some(def_id) = dep_node.extract_def_id(self.tcx) { format!("{:?}({})", dep_node.kind, self.tcx.def_path_str(def_id)) } else { format!("{:?}({:?})", dep_node.kind, dep_node.hash) } } fn assert_dirty(&self, item_span: Span, dep_node: DepNode) { debug!("assert_dirty({:?})", dep_node); if self.tcx.dep_graph.is_green(&dep_node) { let dep_node_str = self.dep_node_str(&dep_node); self.tcx .sess .span_err(item_span, &format!("`{}` should be dirty but is not", dep_node_str)); } } fn assert_clean(&self, item_span: Span, dep_node: DepNode) { debug!("assert_clean({:?})", dep_node); if self.tcx.dep_graph.is_red(&dep_node) { let dep_node_str = self.dep_node_str(&dep_node); self.tcx .sess .span_err(item_span, &format!("`{}` should be clean but is not", dep_node_str)); } } fn assert_loaded_from_disk(&self, item_span: Span, dep_node: DepNode) { debug!("assert_loaded_from_disk({:?})", dep_node); if !self.tcx.dep_graph.debug_was_loaded_from_disk(dep_node) { let dep_node_str = self.dep_node_str(&dep_node); self.tcx.sess.span_err( item_span, &format!("`{}` should have been loaded from disk but it was not", dep_node_str), ); } } fn check_item(&mut self, item_id: LocalDefId) { let item_span = self.tcx.def_span(item_id.to_def_id()); let def_path_hash = self.tcx.def_path_hash(item_id.to_def_id()); for attr in self.tcx.get_attrs(item_id.to_def_id(), sym::rustc_clean) { let Some(assertion) = self.assertion_maybe(item_id, attr) else { continue; }; self.checked_attrs.insert(attr.id); for label in assertion.clean { let dep_node = DepNode::from_label_string(self.tcx, &label, def_path_hash).unwrap(); self.assert_clean(item_span, dep_node); } for label in assertion.dirty { let dep_node = DepNode::from_label_string(self.tcx, &label, def_path_hash).unwrap(); self.assert_dirty(item_span, dep_node); } for label in assertion.loaded_from_disk { let dep_node = DepNode::from_label_string(self.tcx, &label, def_path_hash).unwrap(); self.assert_loaded_from_disk(item_span, dep_node); } } } } /// Given a `#[rustc_clean]` attribute, scan for a `cfg="foo"` attribute and check whether we have /// a cfg flag called `foo`. fn check_config(tcx: TyCtxt<'_>, attr: &Attribute) -> bool { debug!("check_config(attr={:?})", attr); let config = &tcx.sess.parse_sess.config; debug!("check_config: config={:?}", config); let mut cfg = None; for item in attr.meta_item_list().unwrap_or_else(Vec::new) { if item.has_name(CFG) { let value = expect_associated_value(tcx, &item); debug!("check_config: searching for cfg {:?}", value); cfg = Some(config.contains(&(value, None))); } else if !(item.has_name(EXCEPT) || item.has_name(LOADED_FROM_DISK)) { tcx.sess.span_err(attr.span, &format!("unknown item `{}`", item.name_or_empty())); } } match cfg { None => tcx.sess.span_fatal(attr.span, "no cfg attribute"), Some(c) => c, } } fn expect_associated_value(tcx: TyCtxt<'_>, item: &NestedMetaItem) -> Symbol { if let Some(value) = item.value_str() { value } else { let msg = if let Some(ident) = item.ident() { format!("associated value expected for `{}`", ident) } else { "expected an associated value".to_string() }; tcx.sess.span_fatal(item.span(), &msg); } } /// A visitor that collects all `#[rustc_clean]` attributes from /// the HIR. It is used to verify that we really ran checks for all annotated /// nodes. pub struct FindAllAttrs<'tcx> { tcx: TyCtxt<'tcx>, found_attrs: Vec<&'tcx Attribute>, } impl<'tcx> FindAllAttrs<'tcx> { fn is_active_attr(&mut self, attr: &Attribute) -> bool { if attr.has_name(sym::rustc_clean) && check_config(self.tcx, attr) { return true; } false } fn report_unchecked_attrs(&self, mut checked_attrs: FxHashSet) { for attr in &self.found_attrs { if !checked_attrs.contains(&attr.id) { self.tcx.sess.span_err(attr.span, "found unchecked `#[rustc_clean]` attribute"); checked_attrs.insert(attr.id); } } } } impl<'tcx> intravisit::Visitor<'tcx> for FindAllAttrs<'tcx> { type NestedFilter = nested_filter::All; fn nested_visit_map(&mut self) -> Self::Map { self.tcx.hir() } fn visit_attribute(&mut self, attr: &'tcx Attribute) { if self.is_active_attr(attr) { self.found_attrs.push(attr); } } }