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_tables_of")]` if we are
7 //! in `#[cfg(rev2)]`, then the fingerprints associated with
8 //! `DepNode::typeck_tables_of(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 rustc
::ty
::TyCtxt
;
27 use rustc_data_structures
::fingerprint
::Fingerprint
;
28 use rustc_data_structures
::fx
::FxHashSet
;
29 use syntax
::ast
::{self, Attribute, NestedMetaItem}
;
30 use syntax
::symbol
::{Symbol, sym}
;
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] = &[
44 /// DepNodes for functions + methods
45 const BASE_FN
: &[&str] = &[
46 // Callers will depend on the signature of these items, so we better test
48 label_strs
::generics_of
,
49 label_strs
::predicates_of
,
52 // And a big part of compilation (that we eventually want to cache) is type inference
54 label_strs
::typeck_tables_of
,
57 /// DepNodes for Hir, which is pretty much everything
58 const BASE_HIR
: &[&str] = &[
59 // Hir and HirBody should be computed for all nodes
64 /// `impl` implementation of struct/trait
65 const BASE_IMPL
: &[&str] = &[
66 label_strs
::associated_item_def_ids
,
67 label_strs
::generics_of
,
68 label_strs
::impl_trait_ref
,
71 /// DepNodes for mir_built/Optimized, which is relevant in "executable"
72 /// code, i.e., functions+methods
73 const BASE_MIR
: &[&str] = &[
74 label_strs
::optimized_mir
,
75 label_strs
::promoted_mir
,
76 label_strs
::mir_built
,
79 /// Struct, Enum and Union DepNodes
81 /// Note that changing the type of a field does not change the type of the struct or enum, but
82 /// adding/removing fields or changing a fields name or visibility does.
83 const BASE_STRUCT
: &[&str] = &[
84 label_strs
::generics_of
,
85 label_strs
::predicates_of
,
89 /// Trait definition `DepNode`s.
90 const BASE_TRAIT_DEF
: &[&str] = &[
91 label_strs
::associated_item_def_ids
,
92 label_strs
::generics_of
,
93 label_strs
::is_object_safe
,
94 label_strs
::predicates_of
,
95 label_strs
::specialization_graph_of
,
96 label_strs
::trait_def
,
97 label_strs
::trait_impls_of
,
100 /// Extra `DepNode`s for functions and methods.
101 const EXTRA_ASSOCIATED
: &[&str] = &[
102 label_strs
::associated_item
,
105 const EXTRA_TRAIT
: &[&str] = &[
106 label_strs
::trait_of_item
,
109 // Fully Built Labels
111 const LABELS_CONST
: &[&[&str]] = &[
116 /// Constant/Typedef in an impl
117 const LABELS_CONST_IN_IMPL
: &[&[&str]] = &[
123 /// Trait-Const/Typedef DepNodes
124 const LABELS_CONST_IN_TRAIT
: &[&[&str]] = &[
131 /// Function `DepNode`s.
132 const LABELS_FN
: &[&[&str]] = &[
138 /// Method `DepNode`s.
139 const LABELS_FN_IN_IMPL
: &[&[&str]] = &[
146 /// Trait method `DepNode`s.
147 const LABELS_FN_IN_TRAIT
: &[&[&str]] = &[
155 /// For generic cases like inline-assembly, modules, etc.
156 const LABELS_HIR_ONLY
: &[&[&str]] = &[
161 const LABELS_IMPL
: &[&[&str]] = &[
166 /// Abstract data type (struct, enum, union) `DepNode`s.
167 const LABELS_ADT
: &[&[&str]] = &[
172 /// Trait definition `DepNode`s.
174 const LABELS_TRAIT
: &[&[&str]] = &[
180 // FIXME: Struct/Enum/Unions Fields (there is currently no way to attach these)
182 // Fields are kind of separate from their containers, as they can change independently from
183 // them. We should at least check
185 // type_of for these.
187 type Labels
= FxHashSet
<String
>;
189 /// Represents the requested configuration by rustc_clean/dirty
196 fn from_clean_labels(labels
: Labels
) -> Assertion
{
199 dirty
: Labels
::default(),
203 fn from_dirty_labels(labels
: Labels
) -> Assertion
{
205 clean
: Labels
::default(),
211 pub fn check_dirty_clean_annotations(tcx
: TyCtxt
<'_
>) {
212 // can't add `#[rustc_dirty]` etc without opting in to this feature
213 if !tcx
.features().rustc_attrs
{
217 tcx
.dep_graph
.with_ignore(|| {
218 let krate
= tcx
.hir().krate();
219 let mut dirty_clean_visitor
= DirtyCleanVisitor
{
221 checked_attrs
: Default
::default(),
223 krate
.visit_all_item_likes(&mut dirty_clean_visitor
);
225 let mut all_attrs
= FindAllAttrs
{
227 attr_names
: vec
![ATTR_DIRTY
, ATTR_CLEAN
],
230 intravisit
::walk_crate(&mut all_attrs
, krate
);
232 // Note that we cannot use the existing "unused attribute"-infrastructure
233 // here, since that is running before codegen. This is also the reason why
234 // all codegen-specific attributes are `Whitelisted` in syntax::feature_gate.
235 all_attrs
.report_unchecked_attrs(&dirty_clean_visitor
.checked_attrs
);
239 pub struct DirtyCleanVisitor
<'tcx
> {
241 checked_attrs
: FxHashSet
<ast
::AttrId
>,
244 impl DirtyCleanVisitor
<'tcx
> {
245 /// Possibly "deserialize" the attribute into a clean/dirty assertion
246 fn assertion_maybe(&mut self, item_id
: hir
::HirId
, attr
: &Attribute
)
249 let is_clean
= if attr
.check_name(ATTR_DIRTY
) {
251 } else if attr
.check_name(ATTR_CLEAN
) {
254 // skip: not rustc_clean/dirty
257 if !check_config(self.tcx
, attr
) {
258 // skip: not the correct `cfg=`
261 let assertion
= if let Some(labels
) = self.labels(attr
) {
263 Assertion
::from_clean_labels(labels
)
265 Assertion
::from_dirty_labels(labels
)
268 self.assertion_auto(item_id
, attr
, is_clean
)
273 /// Gets the "auto" assertion on pre-validated attr, along with the `except` labels.
274 fn assertion_auto(&mut self, item_id
: hir
::HirId
, attr
: &Attribute
, is_clean
: bool
)
277 let (name
, mut auto) = self.auto_labels(item_id
, attr
);
278 let except
= self.except(attr
);
279 for e
in except
.iter() {
282 "`except` specified DepNodes that can not be affected for \"{}\": \"{}\"",
286 self.tcx
.sess
.span_fatal(attr
.span
, &msg
);
302 fn labels(&self, attr
: &Attribute
) -> Option
<Labels
> {
303 for item
in attr
.meta_item_list().unwrap_or_else(Vec
::new
) {
304 if item
.check_name(LABEL
) {
305 let value
= expect_associated_value(self.tcx
, &item
);
306 return Some(self.resolve_labels(&item
, &value
.as_str()));
312 /// `except=` attribute value
313 fn except(&self, attr
: &Attribute
) -> Labels
{
314 for item
in attr
.meta_item_list().unwrap_or_else(Vec
::new
) {
315 if item
.check_name(EXCEPT
) {
316 let value
= expect_associated_value(self.tcx
, &item
);
317 return self.resolve_labels(&item
, &value
.as_str());
320 // if no `label` or `except` is given, only the node's group are asserted
324 /// Return all DepNode labels that should be asserted for this item.
325 /// index=0 is the "name" used for error messages
326 fn auto_labels(&mut self, item_id
: hir
::HirId
, attr
: &Attribute
) -> (&'
static str, Labels
) {
327 let node
= self.tcx
.hir().get(item_id
);
328 let (name
, labels
) = match node
{
329 HirNode
::Item(item
) => {
331 // note: these are in the same order as hir::Item_;
332 // FIXME(michaelwoerister): do commented out ones
334 // // An `extern crate` item, with optional original crate name,
335 // HirItem::ExternCrate(..), // intentionally no assertions
337 // // `use foo::bar::*;` or `use foo::bar::baz as quux;`
338 // HirItem::Use(..), // intentionally no assertions
341 HirItem
::Static(..) => ("ItemStatic", LABELS_CONST
),
344 HirItem
::Const(..) => ("ItemConst", LABELS_CONST
),
346 // A function declaration
347 HirItem
::Fn(..) => ("ItemFn", LABELS_FN
),
350 HirItem
::Mod(..) =>("ItemMod", LABELS_HIR_ONLY
),
352 // // An external module
353 HirItem
::ForeignMod(..) => ("ItemForeignMod", LABELS_HIR_ONLY
),
355 // Module-level inline assembly (from global_asm!)
356 HirItem
::GlobalAsm(..) => ("ItemGlobalAsm", LABELS_HIR_ONLY
),
358 // A type alias, e.g., `type Foo = Bar<u8>`
359 HirItem
::TyAlias(..) => ("ItemTy", LABELS_HIR_ONLY
),
361 // An enum definition, e.g., `enum Foo<A, B> {C<A>, D<B>}`
362 HirItem
::Enum(..) => ("ItemEnum", LABELS_ADT
),
364 // A struct definition, e.g., `struct Foo<A> {x: A}`
365 HirItem
::Struct(..) => ("ItemStruct", LABELS_ADT
),
367 // A union definition, e.g., `union Foo<A, B> {x: A, y: B}`
368 HirItem
::Union(..) => ("ItemUnion", LABELS_ADT
),
370 // Represents a Trait Declaration
371 // FIXME(michaelwoerister): trait declaration is buggy because sometimes some of
372 // the depnodes don't exist (because they legitametely didn't need to be
375 // michaelwoerister and vitiral came up with a possible solution,
376 // to just do this before every query
378 // ::rustc::ty::query::plumbing::force_from_dep_node(tcx, dep_node)
381 // However, this did not seem to work effectively and more bugs were hit.
382 // Nebie @vitiral gave up :)
384 //HirItem::Trait(..) => ("ItemTrait", LABELS_TRAIT),
386 // An implementation, eg `impl<A> Trait for Foo { .. }`
387 HirItem
::Impl(..) => ("ItemKind::Impl", LABELS_IMPL
),
389 _
=> self.tcx
.sess
.span_fatal(
392 "clean/dirty auto-assertions not yet defined \
393 for Node::Item.node={:?}",
399 HirNode
::TraitItem(item
) => {
401 TraitItemKind
::Method(..) => ("Node::TraitItem", LABELS_FN_IN_TRAIT
),
402 TraitItemKind
::Const(..) => ("NodeTraitConst", LABELS_CONST_IN_TRAIT
),
403 TraitItemKind
::Type(..) => ("NodeTraitType", LABELS_CONST_IN_TRAIT
),
406 HirNode
::ImplItem(item
) => {
408 ImplItemKind
::Method(..) => ("Node::ImplItem", LABELS_FN_IN_IMPL
),
409 ImplItemKind
::Const(..) => ("NodeImplConst", LABELS_CONST_IN_IMPL
),
410 ImplItemKind
::TyAlias(..) => ("NodeImplType", LABELS_CONST_IN_IMPL
),
411 ImplItemKind
::OpaqueTy(..) => ("NodeImplType", LABELS_CONST_IN_IMPL
),
414 _
=> self.tcx
.sess
.span_fatal(
417 "clean/dirty auto-assertions not yet defined for {:?}",
422 let labels
= Labels
::from_iter(
423 labels
.iter().flat_map(|s
| s
.iter().map(|l
| l
.to_string()))
428 fn resolve_labels(&self, item
: &NestedMetaItem
, value
: &str) -> Labels
{
429 let mut out
= Labels
::default();
430 for label
in value
.split('
,'
) {
431 let label
= label
.trim();
432 if DepNode
::has_label_string(label
) {
433 if out
.contains(label
) {
434 self.tcx
.sess
.span_fatal(
436 &format
!("dep-node label `{}` is repeated", label
));
438 out
.insert(label
.to_string());
440 self.tcx
.sess
.span_fatal(
442 &format
!("dep-node label `{}` not recognized", label
));
452 ) -> impl Iterator
<Item
= DepNode
> + 'l
{
453 let def_path_hash
= self.tcx
.def_path_hash(def_id
);
457 match DepNode
::from_label_string(label
, def_path_hash
) {
458 Ok(dep_node
) => dep_node
,
459 Err(()) => unreachable
!(),
464 fn dep_node_str(&self, dep_node
: &DepNode
) -> String
{
465 if let Some(def_id
) = dep_node
.extract_def_id(self.tcx
) {
468 self.tcx
.def_path_str(def_id
))
470 format
!("{:?}({:?})", dep_node
.kind
, dep_node
.hash
)
474 fn assert_dirty(&self, item_span
: Span
, dep_node
: DepNode
) {
475 debug
!("assert_dirty({:?})", dep_node
);
477 let current_fingerprint
= self.get_fingerprint(&dep_node
);
478 let prev_fingerprint
= self.tcx
.dep_graph
.prev_fingerprint_of(&dep_node
);
480 if current_fingerprint
== prev_fingerprint
{
481 let dep_node_str
= self.dep_node_str(&dep_node
);
482 self.tcx
.sess
.span_err(
484 &format
!("`{}` should be dirty but is not", dep_node_str
));
488 fn get_fingerprint(&self, dep_node
: &DepNode
) -> Option
<Fingerprint
> {
489 if self.tcx
.dep_graph
.dep_node_exists(dep_node
) {
490 let dep_node_index
= self.tcx
.dep_graph
.dep_node_index_of(dep_node
);
491 Some(self.tcx
.dep_graph
.fingerprint_of(dep_node_index
))
497 fn assert_clean(&self, item_span
: Span
, dep_node
: DepNode
) {
498 debug
!("assert_clean({:?})", dep_node
);
500 let current_fingerprint
= self.get_fingerprint(&dep_node
);
501 let prev_fingerprint
= self.tcx
.dep_graph
.prev_fingerprint_of(&dep_node
);
503 // if the node wasn't previously evaluated and now is (or vice versa),
504 // then the node isn't actually clean or dirty.
505 if (current_fingerprint
== None
) ^
(prev_fingerprint
== None
) {
509 if current_fingerprint
!= prev_fingerprint
{
510 let dep_node_str
= self.dep_node_str(&dep_node
);
511 self.tcx
.sess
.span_err(
513 &format
!("`{}` should be clean but is not", dep_node_str
));
517 fn check_item(&mut self, item_id
: hir
::HirId
, item_span
: Span
) {
518 let def_id
= self.tcx
.hir().local_def_id(item_id
);
519 for attr
in self.tcx
.get_attrs(def_id
).iter() {
520 let assertion
= match self.assertion_maybe(item_id
, attr
) {
524 self.checked_attrs
.insert(attr
.id
);
525 for dep_node
in self.dep_nodes(&assertion
.clean
, def_id
) {
526 self.assert_clean(item_span
, dep_node
);
528 for dep_node
in self.dep_nodes(&assertion
.dirty
, def_id
) {
529 self.assert_dirty(item_span
, dep_node
);
535 impl ItemLikeVisitor
<'tcx
> for DirtyCleanVisitor
<'tcx
> {
536 fn visit_item(&mut self, item
: &'tcx hir
::Item
) {
537 self.check_item(item
.hir_id
, item
.span
);
540 fn visit_trait_item(&mut self, item
: &hir
::TraitItem
) {
541 self.check_item(item
.hir_id
, item
.span
);
544 fn visit_impl_item(&mut self, item
: &hir
::ImplItem
) {
545 self.check_item(item
.hir_id
, item
.span
);
549 /// Given a `#[rustc_dirty]` or `#[rustc_clean]` attribute, scan
550 /// for a `cfg="foo"` attribute and check whether we have a cfg
551 /// flag called `foo`.
553 /// Also make sure that the `label` and `except` fields do not
555 fn check_config(tcx
: TyCtxt
<'_
>, attr
: &Attribute
) -> bool
{
556 debug
!("check_config(attr={:?})", attr
);
557 let config
= &tcx
.sess
.parse_sess
.config
;
558 debug
!("check_config: config={:?}", config
);
559 let (mut cfg
, mut except
, mut label
) = (None
, false, false);
560 for item
in attr
.meta_item_list().unwrap_or_else(Vec
::new
) {
561 if item
.check_name(CFG
) {
562 let value
= expect_associated_value(tcx
, &item
);
563 debug
!("check_config: searching for cfg {:?}", value
);
564 cfg
= Some(config
.contains(&(value
, None
)));
566 if item
.check_name(LABEL
) {
569 if item
.check_name(EXCEPT
) {
577 "must specify only one of: `label`, `except`"
582 None
=> tcx
.sess
.span_fatal(
590 fn expect_associated_value(tcx
: TyCtxt
<'_
>, item
: &NestedMetaItem
) -> ast
::Name
{
591 if let Some(value
) = item
.value_str() {
594 let msg
= if let Some(ident
) = item
.ident() {
595 format
!("associated value expected for `{}`", ident
)
597 "expected an associated value".to_string()
600 tcx
.sess
.span_fatal(item
.span(), &msg
);
604 // A visitor that collects all #[rustc_dirty]/#[rustc_clean] attributes from
605 // the HIR. It is used to verfiy that we really ran checks for all annotated
607 pub struct FindAllAttrs
<'tcx
> {
609 attr_names
: Vec
<Symbol
>,
610 found_attrs
: Vec
<&'tcx Attribute
>,
613 impl FindAllAttrs
<'tcx
> {
614 fn is_active_attr(&mut self, attr
: &Attribute
) -> bool
{
615 for attr_name
in &self.attr_names
{
616 if attr
.check_name(*attr_name
) && check_config(self.tcx
, attr
) {
624 fn report_unchecked_attrs(&self, checked_attrs
: &FxHashSet
<ast
::AttrId
>) {
625 for attr
in &self.found_attrs
{
626 if !checked_attrs
.contains(&attr
.id
) {
627 self.tcx
.sess
.span_err(attr
.span
, &format
!("found unchecked \
628 `#[rustc_dirty]` / `#[rustc_clean]` attribute"));
634 impl intravisit
::Visitor
<'tcx
> for FindAllAttrs
<'tcx
> {
635 fn nested_visit_map
<'this
>(&'this
mut self) -> intravisit
::NestedVisitorMap
<'this
, 'tcx
> {
636 intravisit
::NestedVisitorMap
::All(&self.tcx
.hir())
639 fn visit_attribute(&mut self, attr
: &'tcx Attribute
) {
640 if self.is_active_attr(attr
) {
641 self.found_attrs
.push(attr
);