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