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