]> git.lizzy.rs Git - rust.git/blob - src/librustc_incremental/persist/dirty_clean.rs
Merge branch 'refactor-select' of https://github.com/aravind-pg/rust into update...
[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.features().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                     // An implementation, eg `impl<A> Trait for Foo { .. }`
397                     HirItem::ItemImpl(..) => ("ItemImpl", LABELS_IMPL),
398
399                     _ => self.tcx.sess.span_fatal(
400                         attr.span,
401                         &format!(
402                             "clean/dirty auto-assertions not yet defined for NodeItem.node={:?}",
403                             item.node
404                         )
405                     ),
406                 }
407             },
408             HirNode::NodeTraitItem(item) => {
409                 match item.node {
410                     TraitItemKind::Method(..) => ("NodeTraitItem", LABELS_FN_IN_TRAIT),
411                     TraitItemKind::Const(..) => ("NodeTraitConst", LABELS_CONST_IN_TRAIT),
412                     TraitItemKind::Type(..) => ("NodeTraitType", LABELS_CONST_IN_TRAIT),
413                 }
414             },
415             HirNode::NodeImplItem(item) => {
416                 match item.node {
417                     ImplItemKind::Method(..) => ("NodeImplItem", LABELS_FN_IN_IMPL),
418                     ImplItemKind::Const(..) => ("NodeImplConst", LABELS_CONST_IN_IMPL),
419                     ImplItemKind::Type(..) => ("NodeImplType", LABELS_CONST_IN_IMPL),
420                 }
421             },
422             _ => self.tcx.sess.span_fatal(
423                 attr.span,
424                 &format!(
425                     "clean/dirty auto-assertions not yet defined for {:?}",
426                     node
427                 )
428             ),
429         };
430         let labels = Labels::from_iter(
431             labels.iter().flat_map(|s| s.iter().map(|l| l.to_string()))
432         );
433         (name, labels)
434     }
435
436     fn resolve_labels(&self, item: &NestedMetaItem, value: &str) -> Labels {
437         let mut out: Labels = HashSet::new();
438         for label in value.split(',') {
439             let label = label.trim();
440             if DepNode::has_label_string(label) {
441                 if out.contains(label) {
442                     self.tcx.sess.span_fatal(
443                         item.span,
444                         &format!("dep-node label `{}` is repeated", label));
445                 }
446                 out.insert(label.to_string());
447             } else {
448                 self.tcx.sess.span_fatal(
449                     item.span,
450                     &format!("dep-node label `{}` not recognized", label));
451             }
452         }
453         out
454     }
455
456     fn dep_nodes(&self, labels: &Labels, def_id: DefId) -> Vec<DepNode> {
457         let mut out = Vec::with_capacity(labels.len());
458         let def_path_hash = self.tcx.def_path_hash(def_id);
459         for label in labels.iter() {
460             match DepNode::from_label_string(label, def_path_hash) {
461                 Ok(dep_node) => out.push(dep_node),
462                 Err(()) => unreachable!(),
463             }
464         }
465         out
466     }
467
468     fn dep_node_str(&self, dep_node: &DepNode) -> String {
469         if let Some(def_id) = dep_node.extract_def_id(self.tcx) {
470             format!("{:?}({})",
471                     dep_node.kind,
472                     self.tcx.item_path_str(def_id))
473         } else {
474             format!("{:?}({:?})", dep_node.kind, dep_node.hash)
475         }
476     }
477
478     fn assert_dirty(&self, item_span: Span, dep_node: DepNode) {
479         debug!("assert_dirty({:?})", dep_node);
480
481         let dep_node_index = self.tcx.dep_graph.dep_node_index_of(&dep_node);
482         let current_fingerprint = self.tcx.dep_graph.fingerprint_of(dep_node_index);
483         let prev_fingerprint = self.tcx.dep_graph.prev_fingerprint_of(&dep_node);
484
485         if Some(current_fingerprint) == prev_fingerprint {
486             let dep_node_str = self.dep_node_str(&dep_node);
487             self.tcx.sess.span_err(
488                 item_span,
489                 &format!("`{}` should be dirty but is not", dep_node_str));
490         }
491     }
492
493     fn assert_clean(&self, item_span: Span, dep_node: DepNode) {
494         debug!("assert_clean({:?})", dep_node);
495
496         let dep_node_index = self.tcx.dep_graph.dep_node_index_of(&dep_node);
497         let current_fingerprint = self.tcx.dep_graph.fingerprint_of(dep_node_index);
498         let prev_fingerprint = self.tcx.dep_graph.prev_fingerprint_of(&dep_node);
499
500         if Some(current_fingerprint) != prev_fingerprint {
501             let dep_node_str = self.dep_node_str(&dep_node);
502             self.tcx.sess.span_err(
503                 item_span,
504                 &format!("`{}` should be clean but is not", dep_node_str));
505         }
506     }
507
508     fn check_item(&mut self, item_id: ast::NodeId, item_span: Span) {
509         let def_id = self.tcx.hir.local_def_id(item_id);
510         for attr in self.tcx.get_attrs(def_id).iter() {
511             let assertion = match self.assertion_maybe(item_id, attr) {
512                 Some(a) => a,
513                 None => continue,
514             };
515             self.checked_attrs.insert(attr.id);
516             for dep_node in self.dep_nodes(&assertion.clean, def_id) {
517                 self.assert_clean(item_span, dep_node);
518             }
519             for dep_node in self.dep_nodes(&assertion.dirty, def_id) {
520                 self.assert_dirty(item_span, dep_node);
521             }
522         }
523     }
524 }
525
526 impl<'a, 'tcx> ItemLikeVisitor<'tcx> for DirtyCleanVisitor<'a, 'tcx> {
527     fn visit_item(&mut self, item: &'tcx hir::Item) {
528         self.check_item(item.id, item.span);
529     }
530
531     fn visit_trait_item(&mut self, item: &hir::TraitItem) {
532         self.check_item(item.id, item.span);
533     }
534
535     fn visit_impl_item(&mut self, item: &hir::ImplItem) {
536         self.check_item(item.id, item.span);
537     }
538 }
539
540 /// Given a `#[rustc_dirty]` or `#[rustc_clean]` attribute, scan
541 /// for a `cfg="foo"` attribute and check whether we have a cfg
542 /// flag called `foo`.
543 ///
544 /// Also make sure that the `label` and `except` fields do not
545 /// both exist.
546 fn check_config(tcx: TyCtxt, attr: &Attribute) -> bool {
547     debug!("check_config(attr={:?})", attr);
548     let config = &tcx.sess.parse_sess.config;
549     debug!("check_config: config={:?}", config);
550     let (mut cfg, mut except, mut label) = (None, false, false);
551     for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
552         if item.check_name(CFG) {
553             let value = expect_associated_value(tcx, &item);
554             debug!("check_config: searching for cfg {:?}", value);
555             cfg = Some(config.contains(&(value, None)));
556         }
557         if item.check_name(LABEL) {
558             label = true;
559         }
560         if item.check_name(EXCEPT) {
561             except = true;
562         }
563     }
564
565     if label && except {
566         tcx.sess.span_fatal(
567             attr.span,
568             "must specify only one of: `label`, `except`"
569         );
570     }
571
572     match cfg {
573         None => tcx.sess.span_fatal(
574             attr.span,
575             "no cfg attribute"
576         ),
577         Some(c) => c,
578     }
579 }
580
581 fn expect_associated_value(tcx: TyCtxt, item: &NestedMetaItem) -> ast::Name {
582     if let Some(value) = item.value_str() {
583         value
584     } else {
585         let msg = if let Some(name) = item.name() {
586             format!("associated value expected for `{}`", name)
587         } else {
588             "expected an associated value".to_string()
589         };
590
591         tcx.sess.span_fatal(item.span, &msg);
592     }
593 }
594
595 // A visitor that collects all #[rustc_dirty]/#[rustc_clean] attributes from
596 // the HIR. It is used to verfiy that we really ran checks for all annotated
597 // nodes.
598 pub struct FindAllAttrs<'a, 'tcx:'a> {
599     tcx: TyCtxt<'a, 'tcx, 'tcx>,
600     attr_names: Vec<&'static str>,
601     found_attrs: Vec<&'tcx Attribute>,
602 }
603
604 impl<'a, 'tcx> FindAllAttrs<'a, 'tcx> {
605
606     fn is_active_attr(&mut self, attr: &Attribute) -> bool {
607         for attr_name in &self.attr_names {
608             if attr.check_name(attr_name) && check_config(self.tcx, attr) {
609                 return true;
610             }
611         }
612
613         false
614     }
615
616     fn report_unchecked_attrs(&self, checked_attrs: &FxHashSet<ast::AttrId>) {
617         for attr in &self.found_attrs {
618             if !checked_attrs.contains(&attr.id) {
619                 self.tcx.sess.span_err(attr.span, &format!("found unchecked \
620                     #[rustc_dirty]/#[rustc_clean] attribute"));
621             }
622         }
623     }
624 }
625
626 impl<'a, 'tcx> intravisit::Visitor<'tcx> for FindAllAttrs<'a, 'tcx> {
627     fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
628         intravisit::NestedVisitorMap::All(&self.tcx.hir)
629     }
630
631     fn visit_attribute(&mut self, attr: &'tcx Attribute) {
632         if self.is_active_attr(attr) {
633             self.found_attrs.push(attr);
634         }
635     }
636 }