]> git.lizzy.rs Git - rust.git/blob - src/librustc_incremental/persist/dirty_clean.rs
6b5e19ca49b76eccb1facbec1ec124eaf42ea69f
[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 `DefId` 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 use std::iter::FromIterator;
17 use std::vec::Vec;
18 use rustc::dep_graph::{DepNode, label_strs};
19 use rustc::hir;
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;
28 use syntax_pos::Span;
29 use rustc::ty::TyCtxt;
30
31 const EXCEPT: &str = "except";
32 const LABEL: &str = "label";
33 const CFG: &str = "cfg";
34
35 // Base and Extra labels to build up the labels
36
37 /// For typedef, constants, and statics
38 const BASE_CONST: &[&str] = &[
39     label_strs::type_of,
40 ];
41
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,
48     label_strs::type_of,
49
50     // And a big part of compilation (that we eventually want to cache) is type inference
51     // information:
52     label_strs::TypeckTables,
53 ];
54
55 /// DepNodes for Hir, which is pretty much everything
56 const BASE_HIR: &[&str] = &[
57     // Hir and HirBody should be computed for all nodes
58     label_strs::Hir,
59     label_strs::HirBody,
60 ];
61
62 /// `impl` implementation of struct/trait
63 const BASE_IMPL: &[&str] = &[
64     label_strs::AssociatedItemDefIds,
65     label_strs::generics_of,
66     label_strs::ImplTraitRef,
67 ];
68
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,
73     label_strs::MirBuilt,
74 ];
75
76 /// Struct, Enum and Union DepNodes
77 ///
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,
83     label_strs::type_of,
84 ];
85
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,
95 ];
96
97 /// Extra `DepNode`s for functions and methods.
98 const EXTRA_ASSOCIATED: &[&str] = &[
99     label_strs::AssociatedItems,
100 ];
101
102 const EXTRA_TRAIT: &[&str] = &[
103     label_strs::TraitOfItem,
104 ];
105
106 // Fully Built Labels
107
108 const LABELS_CONST: &[&[&str]] = &[
109     BASE_HIR,
110     BASE_CONST,
111 ];
112
113 /// Constant/Typedef in an impl
114 const LABELS_CONST_IN_IMPL: &[&[&str]] = &[
115     BASE_HIR,
116     BASE_CONST,
117     EXTRA_ASSOCIATED,
118 ];
119
120 /// Trait-Const/Typedef DepNodes
121 const LABELS_CONST_IN_TRAIT: &[&[&str]] = &[
122     BASE_HIR,
123     BASE_CONST,
124     EXTRA_ASSOCIATED,
125     EXTRA_TRAIT,
126 ];
127
128 /// Function `DepNode`s.
129 const LABELS_FN: &[&[&str]] = &[
130     BASE_HIR,
131     BASE_MIR,
132     BASE_FN,
133 ];
134
135 /// Method `DepNode`s.
136 const LABELS_FN_IN_IMPL: &[&[&str]] = &[
137     BASE_HIR,
138     BASE_MIR,
139     BASE_FN,
140     EXTRA_ASSOCIATED,
141 ];
142
143 /// Trait method `DepNode`s.
144 const LABELS_FN_IN_TRAIT: &[&[&str]] = &[
145     BASE_HIR,
146     BASE_MIR,
147     BASE_FN,
148     EXTRA_ASSOCIATED,
149     EXTRA_TRAIT,
150 ];
151
152 /// For generic cases like inline-assembly, modules, etc.
153 const LABELS_HIR_ONLY: &[&[&str]] = &[
154     BASE_HIR,
155 ];
156
157 /// Impl `DepNode`s.
158 const LABELS_IMPL: &[&[&str]] = &[
159     BASE_HIR,
160     BASE_IMPL,
161 ];
162
163 /// Abstract data type (struct, enum, union) `DepNode`s.
164 const LABELS_ADT: &[&[&str]] = &[
165     BASE_HIR,
166     BASE_STRUCT,
167 ];
168
169 /// Trait definition `DepNode`s.
170 #[allow(dead_code)]
171 const LABELS_TRAIT: &[&[&str]] = &[
172     BASE_HIR,
173     BASE_TRAIT_DEF,
174 ];
175
176
177 // FIXME: Struct/Enum/Unions Fields (there is currently no way to attach these)
178 //
179 // Fields are kind of separate from their containers, as they can change independently from
180 // them. We should at least check
181 //
182 //     type_of for these.
183
184 type Labels = FxHashSet<String>;
185
186 /// Represents the requested configuration by rustc_clean/dirty
187 struct Assertion {
188     clean: Labels,
189     dirty: Labels,
190 }
191
192 impl Assertion {
193     fn from_clean_labels(labels: Labels) -> Assertion {
194         Assertion {
195             clean: labels,
196             dirty: Labels::default(),
197         }
198     }
199
200     fn from_dirty_labels(labels: Labels) -> Assertion {
201         Assertion {
202             clean: Labels::default(),
203             dirty: labels,
204         }
205     }
206 }
207
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 {
211         return;
212     }
213
214     tcx.dep_graph.with_ignore(|| {
215         let krate = tcx.hir().krate();
216         let mut dirty_clean_visitor = DirtyCleanVisitor {
217             tcx,
218             checked_attrs: Default::default(),
219         };
220         krate.visit_all_item_likes(&mut dirty_clean_visitor);
221
222         let mut all_attrs = FindAllAttrs {
223             tcx,
224             attr_names: vec![ATTR_DIRTY, ATTR_CLEAN],
225             found_attrs: vec![],
226         };
227         intravisit::walk_crate(&mut all_attrs, krate);
228
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);
233     })
234 }
235
236 pub struct DirtyCleanVisitor<'a, 'tcx:'a> {
237     tcx: TyCtxt<'a, 'tcx, 'tcx>,
238     checked_attrs: FxHashSet<ast::AttrId>,
239 }
240
241 impl<'a, 'tcx> DirtyCleanVisitor<'a, 'tcx> {
242
243     /// Possibly "deserialize" the attribute into a clean/dirty assertion
244     fn assertion_maybe(&mut self, item_id: hir::HirId, attr: &Attribute)
245         -> Option<Assertion>
246     {
247         let is_clean = if attr.check_name(ATTR_DIRTY) {
248             false
249         } else if attr.check_name(ATTR_CLEAN) {
250             true
251         } else {
252             // skip: not rustc_clean/dirty
253             return None
254         };
255         if !check_config(self.tcx, attr) {
256             // skip: not the correct `cfg=`
257             return None;
258         }
259         let assertion = if let Some(labels) = self.labels(attr) {
260             if is_clean {
261                 Assertion::from_clean_labels(labels)
262             } else {
263                 Assertion::from_dirty_labels(labels)
264             }
265         } else {
266             self.assertion_auto(item_id, attr, is_clean)
267         };
268         Some(assertion)
269     }
270
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)
273         -> Assertion
274     {
275         let (name, mut auto) = self.auto_labels(item_id, attr);
276         let except = self.except(attr);
277         for e in except.iter() {
278             if !auto.remove(e) {
279                 let msg = format!(
280                     "`except` specified DepNodes that can not be affected for \"{}\": \"{}\"",
281                     name,
282                     e
283                 );
284                 self.tcx.sess.span_fatal(attr.span, &msg);
285             }
286         }
287         if is_clean {
288             Assertion {
289                 clean: auto,
290                 dirty: except,
291             }
292         } else {
293             Assertion {
294                 clean: except,
295                 dirty: auto,
296             }
297         }
298     }
299
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()));
305             }
306         }
307         None
308     }
309
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());
316             }
317         }
318         // if no `label` or `except` is given, only the node's group are asserted
319         Labels::default()
320     }
321
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) => {
328                 match item.node {
329                     // note: these are in the same order as hir::Item_;
330                     // FIXME(michaelwoerister): do commented out ones
331
332                     // // An `extern crate` item, with optional original crate name,
333                     // HirItem::ExternCrate(..),  // intentionally no assertions
334
335                     // // `use foo::bar::*;` or `use foo::bar::baz as quux;`
336                     // HirItem::Use(..),  // intentionally no assertions
337
338                     // A `static` item
339                     HirItem::Static(..) => ("ItemStatic", LABELS_CONST),
340
341                     // A `const` item
342                     HirItem::Const(..) => ("ItemConst", LABELS_CONST),
343
344                     // A function declaration
345                     HirItem::Fn(..) => ("ItemFn", LABELS_FN),
346
347                     // // A module
348                     HirItem::Mod(..) =>("ItemMod", LABELS_HIR_ONLY),
349
350                     // // An external module
351                     HirItem::ForeignMod(..) => ("ItemForeignMod", LABELS_HIR_ONLY),
352
353                     // Module-level inline assembly (from global_asm!)
354                     HirItem::GlobalAsm(..) => ("ItemGlobalAsm", LABELS_HIR_ONLY),
355
356                     // A type alias, e.g., `type Foo = Bar<u8>`
357                     HirItem::Ty(..) => ("ItemTy", LABELS_HIR_ONLY),
358
359                     // An enum definition, e.g., `enum Foo<A, B> {C<A>, D<B>}`
360                     HirItem::Enum(..) => ("ItemEnum", LABELS_ADT),
361
362                     // A struct definition, e.g., `struct Foo<A> {x: A}`
363                     HirItem::Struct(..) => ("ItemStruct", LABELS_ADT),
364
365                     // A union definition, e.g., `union Foo<A, B> {x: A, y: B}`
366                     HirItem::Union(..) => ("ItemUnion", LABELS_ADT),
367
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
371                     // calculated)
372                     //
373                     // michaelwoerister and vitiral came up with a possible solution,
374                     // to just do this before every query
375                     // ```
376                     // ::rustc::ty::query::plumbing::force_from_dep_node(tcx, dep_node)
377                     // ```
378                     //
379                     // However, this did not seem to work effectively and more bugs were hit.
380                     // Nebie @vitiral gave up :)
381                     //
382                     //HirItem::Trait(..) => ("ItemTrait", LABELS_TRAIT),
383
384                     // An implementation, eg `impl<A> Trait for Foo { .. }`
385                     HirItem::Impl(..) => ("ItemKind::Impl", LABELS_IMPL),
386
387                     _ => self.tcx.sess.span_fatal(
388                         attr.span,
389                         &format!(
390                             "clean/dirty auto-assertions not yet defined \
391                              for Node::Item.node={:?}",
392                             item.node
393                         )
394                     ),
395                 }
396             },
397             HirNode::TraitItem(item) => {
398                 match item.node {
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),
402                 }
403             },
404             HirNode::ImplItem(item) => {
405                 match item.node {
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),
410                 }
411             },
412             _ => self.tcx.sess.span_fatal(
413                 attr.span,
414                 &format!(
415                     "clean/dirty auto-assertions not yet defined for {:?}",
416                     node
417                 )
418             ),
419         };
420         let labels = Labels::from_iter(
421             labels.iter().flat_map(|s| s.iter().map(|l| l.to_string()))
422         );
423         (name, labels)
424     }
425
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(
433                         item.span(),
434                         &format!("dep-node label `{}` is repeated", label));
435                 }
436                 out.insert(label.to_string());
437             } else {
438                 self.tcx.sess.span_fatal(
439                     item.span(),
440                     &format!("dep-node label `{}` not recognized", label));
441             }
442         }
443         out
444     }
445
446     fn dep_nodes<'l>(
447         &self,
448         labels: &'l Labels,
449         def_id: DefId
450     ) -> impl Iterator<Item = DepNode> + 'l {
451         let def_path_hash = self.tcx.def_path_hash(def_id);
452         labels
453             .iter()
454             .map(move |label| {
455                 match DepNode::from_label_string(label, def_path_hash) {
456                     Ok(dep_node) => dep_node,
457                     Err(()) => unreachable!(),
458                 }
459             })
460     }
461
462     fn dep_node_str(&self, dep_node: &DepNode) -> String {
463         if let Some(def_id) = dep_node.extract_def_id(self.tcx) {
464             format!("{:?}({})",
465                     dep_node.kind,
466                     self.tcx.def_path_str(def_id))
467         } else {
468             format!("{:?}({:?})", dep_node.kind, dep_node.hash)
469         }
470     }
471
472     fn assert_dirty(&self, item_span: Span, dep_node: DepNode) {
473         debug!("assert_dirty({:?})", dep_node);
474
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);
478
479         if Some(current_fingerprint) == prev_fingerprint {
480             let dep_node_str = self.dep_node_str(&dep_node);
481             self.tcx.sess.span_err(
482                 item_span,
483                 &format!("`{}` should be dirty but is not", dep_node_str));
484         }
485     }
486
487     fn assert_clean(&self, item_span: Span, dep_node: DepNode) {
488         debug!("assert_clean({:?})", dep_node);
489
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);
493
494         if Some(current_fingerprint) != prev_fingerprint {
495             let dep_node_str = self.dep_node_str(&dep_node);
496             self.tcx.sess.span_err(
497                 item_span,
498                 &format!("`{}` should be clean but is not", dep_node_str));
499         }
500     }
501
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) {
506                 Some(a) => a,
507                 None => continue,
508             };
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);
512             }
513             for dep_node in self.dep_nodes(&assertion.dirty, def_id) {
514                 self.assert_dirty(item_span, dep_node);
515             }
516         }
517     }
518 }
519
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);
523     }
524
525     fn visit_trait_item(&mut self, item: &hir::TraitItem) {
526         self.check_item(item.hir_id, item.span);
527     }
528
529     fn visit_impl_item(&mut self, item: &hir::ImplItem) {
530         self.check_item(item.hir_id, item.span);
531     }
532 }
533
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`.
537 ///
538 /// Also make sure that the `label` and `except` fields do not
539 /// both exist.
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)));
550         }
551         if item.check_name(LABEL) {
552             label = true;
553         }
554         if item.check_name(EXCEPT) {
555             except = true;
556         }
557     }
558
559     if label && except {
560         tcx.sess.span_fatal(
561             attr.span,
562             "must specify only one of: `label`, `except`"
563         );
564     }
565
566     match cfg {
567         None => tcx.sess.span_fatal(
568             attr.span,
569             "no cfg attribute"
570         ),
571         Some(c) => c,
572     }
573 }
574
575 fn expect_associated_value(tcx: TyCtxt<'_, '_, '_>, item: &NestedMetaItem) -> ast::Name {
576     if let Some(value) = item.value_str() {
577         value
578     } else {
579         let msg = if let Some(name) = item.ident_str() {
580             format!("associated value expected for `{}`", name)
581         } else {
582             "expected an associated value".to_string()
583         };
584
585         tcx.sess.span_fatal(item.span(), &msg);
586     }
587 }
588
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
591 // nodes.
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>,
596 }
597
598 impl<'a, 'tcx> FindAllAttrs<'a, 'tcx> {
599
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) {
603                 return true;
604             }
605         }
606
607         false
608     }
609
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"));
615             }
616         }
617     }
618 }
619
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())
623     }
624
625     fn visit_attribute(&mut self, attr: &'tcx Attribute) {
626         if self.is_active_attr(attr) {
627             self.found_attrs.push(attr);
628         }
629     }
630 }