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="typeck")]` if we are
7 //! in `#[cfg(rev2)]`, then the fingerprints associated with
8 //! `DepNode::typeck(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 rustc_ast::{self as ast, Attribute, NestedMetaItem};
17 use rustc_data_structures::fingerprint::Fingerprint;
18 use rustc_data_structures::fx::FxHashSet;
20 use rustc_hir::def_id::{DefId, LocalDefId};
21 use rustc_hir::intravisit;
22 use rustc_hir::itemlikevisit::ItemLikeVisitor;
23 use rustc_hir::Node as HirNode;
24 use rustc_hir::{ImplItemKind, ItemKind as HirItem, TraitItemKind};
25 use rustc_middle::dep_graph::{label_strs, DepNode, DepNodeExt};
26 use rustc_middle::hir::map::Map;
27 use rustc_middle::ty::TyCtxt;
28 use rustc_span::symbol::{sym, Symbol};
30 use std::iter::FromIterator;
33 const EXCEPT: Symbol = sym::except;
34 const LABEL: Symbol = sym::label;
35 const CFG: Symbol = sym::cfg;
37 // Base and Extra labels to build up the labels
39 /// For typedef, constants, and statics
40 const BASE_CONST: &[&str] = &[label_strs::type_of];
42 /// DepNodes for functions + methods
43 const BASE_FN: &[&str] = &[
44 // Callers will depend on the signature of these items, so we better test
46 label_strs::generics_of,
47 label_strs::predicates_of,
49 // And a big part of compilation (that we eventually want to cache) is type inference
54 /// DepNodes for Hir, which is pretty much everything
55 const BASE_HIR: &[&str] = &[
56 // hir_owner and hir_owner_nodes should be computed for all nodes
57 label_strs::hir_owner,
58 label_strs::hir_owner_nodes,
61 /// `impl` implementation of struct/trait
62 const BASE_IMPL: &[&str] =
63 &[label_strs::associated_item_def_ids, label_strs::generics_of, label_strs::impl_trait_ref];
65 /// DepNodes for mir_built/Optimized, which is relevant in "executable"
66 /// code, i.e., functions+methods
67 const BASE_MIR: &[&str] = &[label_strs::optimized_mir, label_strs::promoted_mir];
69 /// Struct, Enum and Union DepNodes
71 /// Note that changing the type of a field does not change the type of the struct or enum, but
72 /// adding/removing fields or changing a fields name or visibility does.
73 const BASE_STRUCT: &[&str] =
74 &[label_strs::generics_of, label_strs::predicates_of, label_strs::type_of];
76 /// Trait definition `DepNode`s.
77 const BASE_TRAIT_DEF: &[&str] = &[
78 label_strs::associated_item_def_ids,
79 label_strs::generics_of,
80 label_strs::object_safety_violations,
81 label_strs::predicates_of,
82 label_strs::specialization_graph_of,
83 label_strs::trait_def,
84 label_strs::trait_impls_of,
87 /// Extra `DepNode`s for functions and methods.
88 const EXTRA_ASSOCIATED: &[&str] = &[label_strs::associated_item];
90 const EXTRA_TRAIT: &[&str] = &[label_strs::trait_of_item];
94 const LABELS_CONST: &[&[&str]] = &[BASE_HIR, BASE_CONST];
96 /// Constant/Typedef in an impl
97 const LABELS_CONST_IN_IMPL: &[&[&str]] = &[BASE_HIR, BASE_CONST, EXTRA_ASSOCIATED];
99 /// Trait-Const/Typedef DepNodes
100 const LABELS_CONST_IN_TRAIT: &[&[&str]] = &[BASE_HIR, BASE_CONST, EXTRA_ASSOCIATED, EXTRA_TRAIT];
102 /// Function `DepNode`s.
103 const LABELS_FN: &[&[&str]] = &[BASE_HIR, BASE_MIR, BASE_FN];
105 /// Method `DepNode`s.
106 const LABELS_FN_IN_IMPL: &[&[&str]] = &[BASE_HIR, BASE_MIR, BASE_FN, EXTRA_ASSOCIATED];
108 /// Trait method `DepNode`s.
109 const LABELS_FN_IN_TRAIT: &[&[&str]] =
110 &[BASE_HIR, BASE_MIR, BASE_FN, EXTRA_ASSOCIATED, EXTRA_TRAIT];
112 /// For generic cases like inline-assembly, modules, etc.
113 const LABELS_HIR_ONLY: &[&[&str]] = &[BASE_HIR];
116 const LABELS_IMPL: &[&[&str]] = &[BASE_HIR, BASE_IMPL];
118 /// Abstract data type (struct, enum, union) `DepNode`s.
119 const LABELS_ADT: &[&[&str]] = &[BASE_HIR, BASE_STRUCT];
121 /// Trait definition `DepNode`s.
123 const LABELS_TRAIT: &[&[&str]] = &[BASE_HIR, BASE_TRAIT_DEF];
125 // FIXME: Struct/Enum/Unions Fields (there is currently no way to attach these)
127 // Fields are kind of separate from their containers, as they can change independently from
128 // them. We should at least check
130 // type_of for these.
132 type Labels = FxHashSet<String>;
134 /// Represents the requested configuration by rustc_clean/dirty
141 fn from_clean_labels(labels: Labels) -> Assertion {
142 Assertion { clean: labels, dirty: Labels::default() }
145 fn from_dirty_labels(labels: Labels) -> Assertion {
146 Assertion { clean: Labels::default(), dirty: labels }
150 pub fn check_dirty_clean_annotations(tcx: TyCtxt<'_>) {
151 // can't add `#[rustc_dirty]` etc without opting in to this feature
152 if !tcx.features().rustc_attrs {
156 tcx.dep_graph.with_ignore(|| {
157 let krate = tcx.hir().krate();
158 let mut dirty_clean_visitor = DirtyCleanVisitor { tcx, checked_attrs: Default::default() };
159 krate.visit_all_item_likes(&mut dirty_clean_visitor);
161 let mut all_attrs = FindAllAttrs {
163 attr_names: &[sym::rustc_dirty, sym::rustc_clean],
166 intravisit::walk_crate(&mut all_attrs, krate);
168 // Note that we cannot use the existing "unused attribute"-infrastructure
169 // here, since that is running before codegen. This is also the reason why
170 // all codegen-specific attributes are `AssumedUsed` in rustc_ast::feature_gate.
171 all_attrs.report_unchecked_attrs(&dirty_clean_visitor.checked_attrs);
175 pub struct DirtyCleanVisitor<'tcx> {
177 checked_attrs: FxHashSet<ast::AttrId>,
180 impl DirtyCleanVisitor<'tcx> {
181 /// Possibly "deserialize" the attribute into a clean/dirty assertion
182 fn assertion_maybe(&mut self, item_id: LocalDefId, attr: &Attribute) -> Option<Assertion> {
183 let is_clean = if self.tcx.sess.check_name(attr, sym::rustc_dirty) {
185 } else if self.tcx.sess.check_name(attr, sym::rustc_clean) {
188 // skip: not rustc_clean/dirty
191 if !check_config(self.tcx, attr) {
192 // skip: not the correct `cfg=`
195 let assertion = if let Some(labels) = self.labels(attr) {
197 Assertion::from_clean_labels(labels)
199 Assertion::from_dirty_labels(labels)
202 self.assertion_auto(item_id, attr, is_clean)
207 /// Gets the "auto" assertion on pre-validated attr, along with the `except` labels.
214 let (name, mut auto) = self.auto_labels(item_id, attr);
215 let except = self.except(attr);
216 for e in except.iter() {
219 "`except` specified DepNodes that can not be affected for \"{}\": \"{}\"",
222 self.tcx.sess.span_fatal(attr.span, &msg);
226 Assertion { clean: auto, dirty: except }
228 Assertion { clean: except, dirty: auto }
232 fn labels(&self, attr: &Attribute) -> Option<Labels> {
233 for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
234 if item.has_name(LABEL) {
235 let value = expect_associated_value(self.tcx, &item);
236 return Some(self.resolve_labels(&item, value));
242 /// `except=` attribute value
243 fn except(&self, attr: &Attribute) -> Labels {
244 for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
245 if item.has_name(EXCEPT) {
246 let value = expect_associated_value(self.tcx, &item);
247 return self.resolve_labels(&item, value);
250 // if no `label` or `except` is given, only the node's group are asserted
254 /// Return all DepNode labels that should be asserted for this item.
255 /// index=0 is the "name" used for error messages
256 fn auto_labels(&mut self, item_id: LocalDefId, attr: &Attribute) -> (&'static str, Labels) {
257 let hir_id = self.tcx.hir().local_def_id_to_hir_id(item_id);
258 let node = self.tcx.hir().get(hir_id);
259 let (name, labels) = match node {
260 HirNode::Item(item) => {
262 // note: these are in the same order as hir::Item_;
263 // FIXME(michaelwoerister): do commented out ones
265 // // An `extern crate` item, with optional original crate name,
266 // HirItem::ExternCrate(..), // intentionally no assertions
268 // // `use foo::bar::*;` or `use foo::bar::baz as quux;`
269 // HirItem::Use(..), // intentionally no assertions
272 HirItem::Static(..) => ("ItemStatic", LABELS_CONST),
275 HirItem::Const(..) => ("ItemConst", LABELS_CONST),
277 // A function declaration
278 HirItem::Fn(..) => ("ItemFn", LABELS_FN),
281 HirItem::Mod(..) => ("ItemMod", LABELS_HIR_ONLY),
283 // // An external module
284 HirItem::ForeignMod { .. } => ("ItemForeignMod", LABELS_HIR_ONLY),
286 // Module-level inline assembly (from global_asm!)
287 HirItem::GlobalAsm(..) => ("ItemGlobalAsm", LABELS_HIR_ONLY),
289 // A type alias, e.g., `type Foo = Bar<u8>`
290 HirItem::TyAlias(..) => ("ItemTy", LABELS_HIR_ONLY),
292 // An enum definition, e.g., `enum Foo<A, B> {C<A>, D<B>}`
293 HirItem::Enum(..) => ("ItemEnum", LABELS_ADT),
295 // A struct definition, e.g., `struct Foo<A> {x: A}`
296 HirItem::Struct(..) => ("ItemStruct", LABELS_ADT),
298 // A union definition, e.g., `union Foo<A, B> {x: A, y: B}`
299 HirItem::Union(..) => ("ItemUnion", LABELS_ADT),
301 // Represents a Trait Declaration
302 // FIXME(michaelwoerister): trait declaration is buggy because sometimes some of
303 // the depnodes don't exist (because they legitimately didn't need to be
306 // michaelwoerister and vitiral came up with a possible solution,
307 // to just do this before every query
309 // ::rustc_middle::ty::query::plumbing::force_from_dep_node(tcx, dep_node)
312 // However, this did not seem to work effectively and more bugs were hit.
313 // Nebie @vitiral gave up :)
315 //HirItem::Trait(..) => ("ItemTrait", LABELS_TRAIT),
317 // An implementation, eg `impl<A> Trait for Foo { .. }`
318 HirItem::Impl { .. } => ("ItemKind::Impl", LABELS_IMPL),
320 _ => self.tcx.sess.span_fatal(
323 "clean/dirty auto-assertions not yet defined \
324 for Node::Item.node={:?}",
330 HirNode::TraitItem(item) => match item.kind {
331 TraitItemKind::Fn(..) => ("Node::TraitItem", LABELS_FN_IN_TRAIT),
332 TraitItemKind::Const(..) => ("NodeTraitConst", LABELS_CONST_IN_TRAIT),
333 TraitItemKind::Type(..) => ("NodeTraitType", LABELS_CONST_IN_TRAIT),
335 HirNode::ImplItem(item) => match item.kind {
336 ImplItemKind::Fn(..) => ("Node::ImplItem", LABELS_FN_IN_IMPL),
337 ImplItemKind::Const(..) => ("NodeImplConst", LABELS_CONST_IN_IMPL),
338 ImplItemKind::TyAlias(..) => ("NodeImplType", LABELS_CONST_IN_IMPL),
340 _ => self.tcx.sess.span_fatal(
342 &format!("clean/dirty auto-assertions not yet defined for {:?}", node),
346 Labels::from_iter(labels.iter().flat_map(|s| s.iter().map(|l| (*l).to_string())));
350 fn resolve_labels(&self, item: &NestedMetaItem, value: Symbol) -> Labels {
351 let mut out = Labels::default();
352 for label in value.as_str().split(',') {
353 let label = label.trim();
354 if DepNode::has_label_string(label) {
355 if out.contains(label) {
356 self.tcx.sess.span_fatal(
358 &format!("dep-node label `{}` is repeated", label),
361 out.insert(label.to_string());
365 .span_fatal(item.span(), &format!("dep-node label `{}` not recognized", label));
375 ) -> impl Iterator<Item = DepNode> + 'l {
376 let def_path_hash = self.tcx.def_path_hash(def_id);
377 labels.iter().map(move |label| match DepNode::from_label_string(label, def_path_hash) {
378 Ok(dep_node) => dep_node,
379 Err(()) => unreachable!("label: {}", label),
383 fn dep_node_str(&self, dep_node: &DepNode) -> String {
384 if let Some(def_id) = dep_node.extract_def_id(self.tcx) {
385 format!("{:?}({})", dep_node.kind, self.tcx.def_path_str(def_id))
387 format!("{:?}({:?})", dep_node.kind, dep_node.hash)
391 fn assert_dirty(&self, item_span: Span, dep_node: DepNode) {
392 debug!("assert_dirty({:?})", dep_node);
394 let current_fingerprint = self.get_fingerprint(&dep_node);
395 let prev_fingerprint = self.tcx.dep_graph.prev_fingerprint_of(&dep_node);
397 if current_fingerprint == prev_fingerprint {
398 let dep_node_str = self.dep_node_str(&dep_node);
401 .span_err(item_span, &format!("`{}` should be dirty but is not", dep_node_str));
405 fn get_fingerprint(&self, dep_node: &DepNode) -> Option<Fingerprint> {
406 if self.tcx.dep_graph.dep_node_exists(dep_node) {
407 let dep_node_index = self.tcx.dep_graph.dep_node_index_of(dep_node);
408 Some(self.tcx.dep_graph.fingerprint_of(dep_node_index))
414 fn assert_clean(&self, item_span: Span, dep_node: DepNode) {
415 debug!("assert_clean({:?})", dep_node);
417 let current_fingerprint = self.get_fingerprint(&dep_node);
418 let prev_fingerprint = self.tcx.dep_graph.prev_fingerprint_of(&dep_node);
420 // if the node wasn't previously evaluated and now is (or vice versa),
421 // then the node isn't actually clean or dirty.
422 if (current_fingerprint == None) ^ (prev_fingerprint == None) {
426 if current_fingerprint != prev_fingerprint {
427 let dep_node_str = self.dep_node_str(&dep_node);
430 .span_err(item_span, &format!("`{}` should be clean but is not", dep_node_str));
434 fn check_item(&mut self, item_id: LocalDefId, item_span: Span) {
435 for attr in self.tcx.get_attrs(item_id.to_def_id()).iter() {
436 let assertion = match self.assertion_maybe(item_id, attr) {
440 self.checked_attrs.insert(attr.id);
441 for dep_node in self.dep_nodes(&assertion.clean, item_id.to_def_id()) {
442 self.assert_clean(item_span, dep_node);
444 for dep_node in self.dep_nodes(&assertion.dirty, item_id.to_def_id()) {
445 self.assert_dirty(item_span, dep_node);
451 impl ItemLikeVisitor<'tcx> for DirtyCleanVisitor<'tcx> {
452 fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) {
453 self.check_item(item.def_id, item.span);
456 fn visit_trait_item(&mut self, item: &hir::TraitItem<'_>) {
457 self.check_item(item.def_id, item.span);
460 fn visit_impl_item(&mut self, item: &hir::ImplItem<'_>) {
461 self.check_item(item.def_id, item.span);
464 fn visit_foreign_item(&mut self, item: &hir::ForeignItem<'_>) {
465 self.check_item(item.def_id, item.span);
469 /// Given a `#[rustc_dirty]` or `#[rustc_clean]` attribute, scan
470 /// for a `cfg="foo"` attribute and check whether we have a cfg
471 /// flag called `foo`.
473 /// Also make sure that the `label` and `except` fields do not
475 fn check_config(tcx: TyCtxt<'_>, attr: &Attribute) -> bool {
476 debug!("check_config(attr={:?})", attr);
477 let config = &tcx.sess.parse_sess.config;
478 debug!("check_config: config={:?}", config);
479 let (mut cfg, mut except, mut label) = (None, false, false);
480 for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
481 if item.has_name(CFG) {
482 let value = expect_associated_value(tcx, &item);
483 debug!("check_config: searching for cfg {:?}", value);
484 cfg = Some(config.contains(&(value, None)));
486 if item.has_name(LABEL) {
489 if item.has_name(EXCEPT) {
495 tcx.sess.span_fatal(attr.span, "must specify only one of: `label`, `except`");
499 None => tcx.sess.span_fatal(attr.span, "no cfg attribute"),
504 fn expect_associated_value(tcx: TyCtxt<'_>, item: &NestedMetaItem) -> Symbol {
505 if let Some(value) = item.value_str() {
508 let msg = if let Some(ident) = item.ident() {
509 format!("associated value expected for `{}`", ident)
511 "expected an associated value".to_string()
514 tcx.sess.span_fatal(item.span(), &msg);
518 // A visitor that collects all #[rustc_dirty]/#[rustc_clean] attributes from
519 // the HIR. It is used to verify that we really ran checks for all annotated
521 pub struct FindAllAttrs<'a, 'tcx> {
523 attr_names: &'a [Symbol],
524 found_attrs: Vec<&'tcx Attribute>,
527 impl FindAllAttrs<'_, 'tcx> {
528 fn is_active_attr(&mut self, attr: &Attribute) -> bool {
529 for attr_name in self.attr_names {
530 if self.tcx.sess.check_name(attr, *attr_name) && check_config(self.tcx, attr) {
538 fn report_unchecked_attrs(&self, checked_attrs: &FxHashSet<ast::AttrId>) {
539 for attr in &self.found_attrs {
540 if !checked_attrs.contains(&attr.id) {
541 self.tcx.sess.span_err(
543 "found unchecked `#[rustc_dirty]` / `#[rustc_clean]` attribute",
550 impl intravisit::Visitor<'tcx> for FindAllAttrs<'_, 'tcx> {
551 type Map = Map<'tcx>;
553 fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
554 intravisit::NestedVisitorMap::All(self.tcx.hir())
557 fn visit_attribute(&mut self, attr: &'tcx Attribute) {
558 if self.is_active_attr(attr) {
559 self.found_attrs.push(attr);