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