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