]> git.lizzy.rs Git - rust.git/blob - src/librustc_incremental/persist/dirty_clean.rs
Auto merge of #57770 - Zoxc:no-hash-query, r=michaelwoerister
[rust.git] / src / librustc_incremental / persist / dirty_clean.rs
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:
5 //!
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 def-id of the
9 //!   current node).
10 //! - `#[rustc_clean(cfg="rev2")]` same as above, except that the
11 //!   fingerprints must be the SAME (along with all other fingerprints).
12 //!
13 //! Errors are reported if we are in the suitable configuration but
14 //! the required condition is not met.
15 //!
16
17 use std::iter::FromIterator;
18 use std::vec::Vec;
19 use rustc::dep_graph::{DepNode, label_strs};
20 use rustc::hir;
21 use rustc::hir::{ItemKind as HirItem, ImplItemKind, TraitItemKind};
22 use rustc::hir::Node as HirNode;
23 use rustc::hir::def_id::DefId;
24 use rustc::hir::itemlikevisit::ItemLikeVisitor;
25 use rustc::hir::intravisit;
26 use rustc::ich::{ATTR_DIRTY, ATTR_CLEAN};
27 use syntax::ast::{self, Attribute, NestedMetaItem};
28 use rustc_data_structures::fx::FxHashSet;
29 use syntax_pos::Span;
30 use rustc::ty::TyCtxt;
31
32 const EXCEPT: &str = "except";
33 const LABEL: &str = "label";
34 const CFG: &str = "cfg";
35
36 // Base and Extra labels to build up the labels
37
38 /// For typedef, constants, and statics
39 const BASE_CONST: &[&str] = &[
40     label_strs::TypeOfItem,
41 ];
42
43 /// DepNodes for functions + methods
44 const BASE_FN: &[&str] = &[
45     // Callers will depend on the signature of these items, so we better test
46     label_strs::FnSignature,
47     label_strs::GenericsOfItem,
48     label_strs::PredicatesOfItem,
49     label_strs::TypeOfItem,
50
51     // And a big part of compilation (that we eventually want to cache) is type inference
52     // information:
53     label_strs::TypeckTables,
54 ];
55
56 /// DepNodes for Hir, which is pretty much everything
57 const BASE_HIR: &[&str] = &[
58     // Hir and HirBody should be computed for all nodes
59     label_strs::Hir,
60     label_strs::HirBody,
61 ];
62
63 /// `impl` implementation of struct/trait
64 const BASE_IMPL: &[&str] = &[
65     label_strs::AssociatedItemDefIds,
66     label_strs::GenericsOfItem,
67     label_strs::ImplTraitRef,
68 ];
69
70 /// DepNodes for MirBuilt/Optimized, which is relevant in "executable"
71 /// code, i.e., functions+methods
72 const BASE_MIR: &[&str] = &[
73     label_strs::MirOptimized,
74     label_strs::MirBuilt,
75 ];
76
77 /// Struct, Enum and Union DepNodes
78 ///
79 /// Note that changing the type of a field does not change the type of the struct or enum, but
80 /// adding/removing fields or changing a fields name or visibility does.
81 const BASE_STRUCT: &[&str] = &[
82     label_strs::GenericsOfItem,
83     label_strs::PredicatesOfItem,
84     label_strs::TypeOfItem,
85 ];
86
87 /// Trait Definition DepNodes
88 const BASE_TRAIT_DEF: &[&str] = &[
89     label_strs::AssociatedItemDefIds,
90     label_strs::GenericsOfItem,
91     label_strs::ObjectSafety,
92     label_strs::PredicatesOfItem,
93     label_strs::SpecializationGraph,
94     label_strs::TraitDefOfItem,
95     label_strs::TraitImpls,
96 ];
97
98 /// extra DepNodes for methods (+fn)
99 const EXTRA_ASSOCIATED: &[&str] = &[
100     label_strs::AssociatedItems,
101 ];
102
103 const EXTRA_TRAIT: &[&str] = &[
104     label_strs::TraitOfItem,
105 ];
106
107 // Fully Built Labels
108
109 const LABELS_CONST: &[&[&str]] = &[
110     BASE_HIR,
111     BASE_CONST,
112 ];
113
114 /// Constant/Typedef in an impl
115 const LABELS_CONST_IN_IMPL: &[&[&str]] = &[
116     BASE_HIR,
117     BASE_CONST,
118     EXTRA_ASSOCIATED,
119 ];
120
121 /// Trait-Const/Typedef DepNodes
122 const LABELS_CONST_IN_TRAIT: &[&[&str]] = &[
123     BASE_HIR,
124     BASE_CONST,
125     EXTRA_ASSOCIATED,
126     EXTRA_TRAIT,
127 ];
128
129 /// Function DepNode
130 const LABELS_FN: &[&[&str]] = &[
131     BASE_HIR,
132     BASE_MIR,
133     BASE_FN,
134 ];
135
136 /// Method DepNodes
137 const LABELS_FN_IN_IMPL: &[&[&str]] = &[
138     BASE_HIR,
139     BASE_MIR,
140     BASE_FN,
141     EXTRA_ASSOCIATED,
142 ];
143
144 /// Trait-Method DepNodes
145 const LABELS_FN_IN_TRAIT: &[&[&str]] = &[
146     BASE_HIR,
147     BASE_MIR,
148     BASE_FN,
149     EXTRA_ASSOCIATED,
150     EXTRA_TRAIT,
151 ];
152
153 /// For generic cases like inline-assembly/mod/etc
154 const LABELS_HIR_ONLY: &[&[&str]] = &[
155     BASE_HIR,
156 ];
157
158 /// Impl DepNodes
159 const LABELS_IMPL: &[&[&str]] = &[
160     BASE_HIR,
161     BASE_IMPL,
162 ];
163
164 /// Abstract Data Type (Struct, Enum, Unions) DepNodes
165 const LABELS_ADT: &[&[&str]] = &[
166     BASE_HIR,
167     BASE_STRUCT,
168 ];
169
170 /// Trait Definition DepNodes
171 #[allow(dead_code)]
172 const LABELS_TRAIT: &[&[&str]] = &[
173     BASE_HIR,
174     BASE_TRAIT_DEF,
175 ];
176
177
178 // FIXME: Struct/Enum/Unions Fields (there is currently no way to attach these)
179 //
180 // Fields are kind of separate from their containers, as they can change independently from
181 // them. We should at least check
182 //
183 //     TypeOfItem for these.
184
185 type Labels = FxHashSet<String>;
186
187 /// Represents the requested configuration by rustc_clean/dirty
188 struct Assertion {
189     clean: Labels,
190     dirty: Labels,
191 }
192
193 impl Assertion {
194     fn from_clean_labels(labels: Labels) -> Assertion {
195         Assertion {
196             clean: labels,
197             dirty: Labels::default(),
198         }
199     }
200
201     fn from_dirty_labels(labels: Labels) -> Assertion {
202         Assertion {
203             clean: Labels::default(),
204             dirty: labels,
205         }
206     }
207 }
208
209 pub fn check_dirty_clean_annotations<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
210     // can't add `#[rustc_dirty]` etc without opting in to this feature
211     if !tcx.features().rustc_attrs {
212         return;
213     }
214
215     tcx.dep_graph.with_ignore(|| {
216         let krate = tcx.hir().krate();
217         let mut dirty_clean_visitor = DirtyCleanVisitor {
218             tcx,
219             checked_attrs: Default::default(),
220         };
221         krate.visit_all_item_likes(&mut dirty_clean_visitor);
222
223         let mut all_attrs = FindAllAttrs {
224             tcx,
225             attr_names: vec![ATTR_DIRTY, ATTR_CLEAN],
226             found_attrs: vec![],
227         };
228         intravisit::walk_crate(&mut all_attrs, krate);
229
230         // Note that we cannot use the existing "unused attribute"-infrastructure
231         // here, since that is running before codegen. This is also the reason why
232         // all codegen-specific attributes are `Whitelisted` in syntax::feature_gate.
233         all_attrs.report_unchecked_attrs(&dirty_clean_visitor.checked_attrs);
234     })
235 }
236
237 pub struct DirtyCleanVisitor<'a, 'tcx:'a> {
238     tcx: TyCtxt<'a, 'tcx, 'tcx>,
239     checked_attrs: FxHashSet<ast::AttrId>,
240 }
241
242 impl<'a, 'tcx> DirtyCleanVisitor<'a, 'tcx> {
243
244     /// Possibly "deserialize" the attribute into a clean/dirty assertion
245     fn assertion_maybe(&mut self, item_id: ast::NodeId, attr: &Attribute)
246         -> Option<Assertion>
247     {
248         let is_clean = if attr.check_name(ATTR_DIRTY) {
249             false
250         } else if attr.check_name(ATTR_CLEAN) {
251             true
252         } else {
253             // skip: not rustc_clean/dirty
254             return None
255         };
256         if !check_config(self.tcx, attr) {
257             // skip: not the correct `cfg=`
258             return None;
259         }
260         let assertion = if let Some(labels) = self.labels(attr) {
261             if is_clean {
262                 Assertion::from_clean_labels(labels)
263             } else {
264                 Assertion::from_dirty_labels(labels)
265             }
266         } else {
267             self.assertion_auto(item_id, attr, is_clean)
268         };
269         Some(assertion)
270     }
271
272     /// Get the "auto" assertion on pre-validated attr, along with the `except` labels
273     fn assertion_auto(&mut self, item_id: ast::NodeId, attr: &Attribute, is_clean: bool)
274         -> Assertion
275     {
276         let (name, mut auto) = self.auto_labels(item_id, attr);
277         let except = self.except(attr);
278         for e in except.iter() {
279             if !auto.remove(e) {
280                 let msg = format!(
281                     "`except` specified DepNodes that can not be affected for \"{}\": \"{}\"",
282                     name,
283                     e
284                 );
285                 self.tcx.sess.span_fatal(attr.span, &msg);
286             }
287         }
288         if is_clean {
289             Assertion {
290                 clean: auto,
291                 dirty: except,
292             }
293         } else {
294             Assertion {
295                 clean: except,
296                 dirty: auto,
297             }
298         }
299     }
300
301     fn labels(&self, attr: &Attribute) -> Option<Labels> {
302         for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
303             if item.check_name(LABEL) {
304                 let value = expect_associated_value(self.tcx, &item);
305                 return Some(self.resolve_labels(&item, value.as_str().as_ref()));
306             }
307         }
308         None
309     }
310
311     /// `except=` attribute value
312     fn except(&self, attr: &Attribute) -> Labels {
313         for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
314             if item.check_name(EXCEPT) {
315                 let value = expect_associated_value(self.tcx, &item);
316                 return self.resolve_labels(&item, value.as_str().as_ref());
317             }
318         }
319         // if no `label` or `except` is given, only the node's group are asserted
320         Labels::default()
321     }
322
323     /// Return all DepNode labels that should be asserted for this item.
324     /// index=0 is the "name" used for error messages
325     fn auto_labels(&mut self, item_id: ast::NodeId, attr: &Attribute) -> (&'static str, Labels) {
326         let node = self.tcx.hir().get(item_id);
327         let (name, labels) = match node {
328             HirNode::Item(item) => {
329                 match item.node {
330                     // note: these are in the same order as hir::Item_;
331                     // FIXME(michaelwoerister): do commented out ones
332
333                     // // An `extern crate` item, with optional original crate name,
334                     // HirItem::ExternCrate(..),  // intentionally no assertions
335
336                     // // `use foo::bar::*;` or `use foo::bar::baz as quux;`
337                     // HirItem::Use(..),  // intentionally no assertions
338
339                     // A `static` item
340                     HirItem::Static(..) => ("ItemStatic", LABELS_CONST),
341
342                     // A `const` item
343                     HirItem::Const(..) => ("ItemConst", LABELS_CONST),
344
345                     // A function declaration
346                     HirItem::Fn(..) => ("ItemFn", LABELS_FN),
347
348                     // // A module
349                     HirItem::Mod(..) =>("ItemMod", LABELS_HIR_ONLY),
350
351                     // // An external module
352                     HirItem::ForeignMod(..) => ("ItemForeignMod", LABELS_HIR_ONLY),
353
354                     // Module-level inline assembly (from global_asm!)
355                     HirItem::GlobalAsm(..) => ("ItemGlobalAsm", LABELS_HIR_ONLY),
356
357                     // A type alias, e.g., `type Foo = Bar<u8>`
358                     HirItem::Ty(..) => ("ItemTy", LABELS_HIR_ONLY),
359
360                     // An enum definition, e.g., `enum Foo<A, B> {C<A>, D<B>}`
361                     HirItem::Enum(..) => ("ItemEnum", LABELS_ADT),
362
363                     // A struct definition, e.g., `struct Foo<A> {x: A}`
364                     HirItem::Struct(..) => ("ItemStruct", LABELS_ADT),
365
366                     // A union definition, e.g., `union Foo<A, B> {x: A, y: B}`
367                     HirItem::Union(..) => ("ItemUnion", LABELS_ADT),
368
369                     // Represents a Trait Declaration
370                     // FIXME(michaelwoerister): trait declaration is buggy because sometimes some of
371                     // the depnodes don't exist (because they legitametely didn't need to be
372                     // calculated)
373                     //
374                     // michaelwoerister and vitiral came up with a possible solution,
375                     // to just do this before every query
376                     // ```
377                     // ::rustc::ty::query::plumbing::force_from_dep_node(tcx, dep_node)
378                     // ```
379                     //
380                     // However, this did not seem to work effectively and more bugs were hit.
381                     // Nebie @vitiral gave up :)
382                     //
383                     //HirItem::Trait(..) => ("ItemTrait", LABELS_TRAIT),
384
385                     // An implementation, eg `impl<A> Trait for Foo { .. }`
386                     HirItem::Impl(..) => ("ItemKind::Impl", LABELS_IMPL),
387
388                     _ => self.tcx.sess.span_fatal(
389                         attr.span,
390                         &format!(
391                             "clean/dirty auto-assertions not yet defined \
392                              for Node::Item.node={:?}",
393                             item.node
394                         )
395                     ),
396                 }
397             },
398             HirNode::TraitItem(item) => {
399                 match item.node {
400                     TraitItemKind::Method(..) => ("Node::TraitItem", LABELS_FN_IN_TRAIT),
401                     TraitItemKind::Const(..) => ("NodeTraitConst", LABELS_CONST_IN_TRAIT),
402                     TraitItemKind::Type(..) => ("NodeTraitType", LABELS_CONST_IN_TRAIT),
403                 }
404             },
405             HirNode::ImplItem(item) => {
406                 match item.node {
407                     ImplItemKind::Method(..) => ("Node::ImplItem", LABELS_FN_IN_IMPL),
408                     ImplItemKind::Const(..) => ("NodeImplConst", LABELS_CONST_IN_IMPL),
409                     ImplItemKind::Type(..) => ("NodeImplType", LABELS_CONST_IN_IMPL),
410                     ImplItemKind::Existential(..) => ("NodeImplType", LABELS_CONST_IN_IMPL),
411                 }
412             },
413             _ => self.tcx.sess.span_fatal(
414                 attr.span,
415                 &format!(
416                     "clean/dirty auto-assertions not yet defined for {:?}",
417                     node
418                 )
419             ),
420         };
421         let labels = Labels::from_iter(
422             labels.iter().flat_map(|s| s.iter().map(|l| l.to_string()))
423         );
424         (name, labels)
425     }
426
427     fn resolve_labels(&self, item: &NestedMetaItem, value: &str) -> Labels {
428         let mut out = Labels::default();
429         for label in value.split(',') {
430             let label = label.trim();
431             if DepNode::has_label_string(label) {
432                 if out.contains(label) {
433                     self.tcx.sess.span_fatal(
434                         item.span,
435                         &format!("dep-node label `{}` is repeated", label));
436                 }
437                 out.insert(label.to_string());
438             } else {
439                 self.tcx.sess.span_fatal(
440                     item.span,
441                     &format!("dep-node label `{}` not recognized", label));
442             }
443         }
444         out
445     }
446
447     fn dep_nodes<'l>(
448         &self,
449         labels: &'l Labels,
450         def_id: DefId
451     ) -> impl Iterator<Item = DepNode> + 'l {
452         let def_path_hash = self.tcx.def_path_hash(def_id);
453         labels
454             .iter()
455             .map(move |label| {
456                 match DepNode::from_label_string(label, def_path_hash) {
457                     Ok(dep_node) => dep_node,
458                     Err(()) => unreachable!(),
459                 }
460             })
461     }
462
463     fn dep_node_str(&self, dep_node: &DepNode) -> String {
464         if let Some(def_id) = dep_node.extract_def_id(self.tcx) {
465             format!("{:?}({})",
466                     dep_node.kind,
467                     self.tcx.item_path_str(def_id))
468         } else {
469             format!("{:?}({:?})", dep_node.kind, dep_node.hash)
470         }
471     }
472
473     fn assert_dirty(&self, item_span: Span, dep_node: DepNode) {
474         debug!("assert_dirty({:?})", dep_node);
475
476         let dep_node_index = self.tcx.dep_graph.dep_node_index_of(&dep_node);
477         let current_fingerprint = self.tcx.dep_graph.fingerprint_of(dep_node_index);
478         let prev_fingerprint = self.tcx.dep_graph.prev_fingerprint_of(&dep_node);
479
480         if Some(current_fingerprint) == prev_fingerprint {
481             let dep_node_str = self.dep_node_str(&dep_node);
482             self.tcx.sess.span_err(
483                 item_span,
484                 &format!("`{}` should be dirty but is not", dep_node_str));
485         }
486     }
487
488     fn assert_clean(&self, item_span: Span, dep_node: DepNode) {
489         debug!("assert_clean({:?})", dep_node);
490
491         let dep_node_index = self.tcx.dep_graph.dep_node_index_of(&dep_node);
492         let current_fingerprint = self.tcx.dep_graph.fingerprint_of(dep_node_index);
493         let prev_fingerprint = self.tcx.dep_graph.prev_fingerprint_of(&dep_node);
494
495         if Some(current_fingerprint) != prev_fingerprint {
496             let dep_node_str = self.dep_node_str(&dep_node);
497             self.tcx.sess.span_err(
498                 item_span,
499                 &format!("`{}` should be clean but is not", dep_node_str));
500         }
501     }
502
503     fn check_item(&mut self, item_id: ast::NodeId, item_span: Span) {
504         let def_id = self.tcx.hir().local_def_id(item_id);
505         for attr in self.tcx.get_attrs(def_id).iter() {
506             let assertion = match self.assertion_maybe(item_id, attr) {
507                 Some(a) => a,
508                 None => continue,
509             };
510             self.checked_attrs.insert(attr.id);
511             for dep_node in self.dep_nodes(&assertion.clean, def_id) {
512                 self.assert_clean(item_span, dep_node);
513             }
514             for dep_node in self.dep_nodes(&assertion.dirty, def_id) {
515                 self.assert_dirty(item_span, dep_node);
516             }
517         }
518     }
519 }
520
521 impl<'a, 'tcx> ItemLikeVisitor<'tcx> for DirtyCleanVisitor<'a, 'tcx> {
522     fn visit_item(&mut self, item: &'tcx hir::Item) {
523         self.check_item(item.id, item.span);
524     }
525
526     fn visit_trait_item(&mut self, item: &hir::TraitItem) {
527         self.check_item(item.id, item.span);
528     }
529
530     fn visit_impl_item(&mut self, item: &hir::ImplItem) {
531         self.check_item(item.id, item.span);
532     }
533 }
534
535 /// Given a `#[rustc_dirty]` or `#[rustc_clean]` attribute, scan
536 /// for a `cfg="foo"` attribute and check whether we have a cfg
537 /// flag called `foo`.
538 ///
539 /// Also make sure that the `label` and `except` fields do not
540 /// both exist.
541 fn check_config(tcx: TyCtxt<'_, '_, '_>, attr: &Attribute) -> bool {
542     debug!("check_config(attr={:?})", attr);
543     let config = &tcx.sess.parse_sess.config;
544     debug!("check_config: config={:?}", config);
545     let (mut cfg, mut except, mut label) = (None, false, false);
546     for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
547         if item.check_name(CFG) {
548             let value = expect_associated_value(tcx, &item);
549             debug!("check_config: searching for cfg {:?}", value);
550             cfg = Some(config.contains(&(value, None)));
551         }
552         if item.check_name(LABEL) {
553             label = true;
554         }
555         if item.check_name(EXCEPT) {
556             except = true;
557         }
558     }
559
560     if label && except {
561         tcx.sess.span_fatal(
562             attr.span,
563             "must specify only one of: `label`, `except`"
564         );
565     }
566
567     match cfg {
568         None => tcx.sess.span_fatal(
569             attr.span,
570             "no cfg attribute"
571         ),
572         Some(c) => c,
573     }
574 }
575
576 fn expect_associated_value(tcx: TyCtxt<'_, '_, '_>, item: &NestedMetaItem) -> ast::Name {
577     if let Some(value) = item.value_str() {
578         value
579     } else {
580         let msg = if let Some(name) = item.name() {
581             format!("associated value expected for `{}`", name)
582         } else {
583             "expected an associated value".to_string()
584         };
585
586         tcx.sess.span_fatal(item.span, &msg);
587     }
588 }
589
590 // A visitor that collects all #[rustc_dirty]/#[rustc_clean] attributes from
591 // the HIR. It is used to verfiy that we really ran checks for all annotated
592 // nodes.
593 pub struct FindAllAttrs<'a, 'tcx:'a> {
594     tcx: TyCtxt<'a, 'tcx, 'tcx>,
595     attr_names: Vec<&'static str>,
596     found_attrs: Vec<&'tcx Attribute>,
597 }
598
599 impl<'a, 'tcx> FindAllAttrs<'a, 'tcx> {
600
601     fn is_active_attr(&mut self, attr: &Attribute) -> bool {
602         for attr_name in &self.attr_names {
603             if attr.check_name(attr_name) && check_config(self.tcx, attr) {
604                 return true;
605             }
606         }
607
608         false
609     }
610
611     fn report_unchecked_attrs(&self, checked_attrs: &FxHashSet<ast::AttrId>) {
612         for attr in &self.found_attrs {
613             if !checked_attrs.contains(&attr.id) {
614                 self.tcx.sess.span_err(attr.span, &format!("found unchecked \
615                     #[rustc_dirty]/#[rustc_clean] attribute"));
616             }
617         }
618     }
619 }
620
621 impl<'a, 'tcx> intravisit::Visitor<'tcx> for FindAllAttrs<'a, 'tcx> {
622     fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
623         intravisit::NestedVisitorMap::All(&self.tcx.hir())
624     }
625
626     fn visit_attribute(&mut self, attr: &'tcx Attribute) {
627         if self.is_active_attr(attr) {
628             self.found_attrs.push(attr);
629         }
630     }
631 }