]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_incremental/src/persist/dirty_clean.rs
Auto merge of #87252 - RalfJung:miri, r=RalfJung
[rust.git] / compiler / rustc_incremental / src / persist / dirty_clean.rs
1 //! Debugging code to test fingerprints computed for query results.  For each node marked with
2 //! `#[rustc_clean]` we will compare the fingerprint from the current and from the previous
3 //! compilation session as appropriate:
4 //!
5 //! - `#[rustc_clean(cfg="rev2", except="typeck")]` if we are
6 //!   in `#[cfg(rev2)]`, then the fingerprints associated with
7 //!   `DepNode::typeck(X)` must be DIFFERENT (`X` is the `DefId` of the
8 //!   current node).
9 //! - `#[rustc_clean(cfg="rev2")]` same as above, except that the
10 //!   fingerprints must be the SAME (along with all other fingerprints).
11 //!
12 //! Errors are reported if we are in the suitable configuration but
13 //! the required condition is not met.
14
15 use rustc_ast::{self as ast, Attribute, NestedMetaItem};
16 use rustc_data_structures::fx::FxHashSet;
17 use rustc_hir as hir;
18 use rustc_hir::def_id::{DefId, LocalDefId};
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_middle::dep_graph::{label_strs, DepNode, DepNodeExt};
24 use rustc_middle::hir::map::Map;
25 use rustc_middle::ty::TyCtxt;
26 use rustc_span::symbol::{sym, Symbol};
27 use rustc_span::Span;
28 use std::iter::FromIterator;
29 use std::vec::Vec;
30
31 const EXCEPT: Symbol = sym::except;
32 const CFG: Symbol = sym::cfg;
33
34 // Base and Extra labels to build up the labels
35
36 /// For typedef, constants, and statics
37 const BASE_CONST: &[&str] = &[label_strs::type_of];
38
39 /// DepNodes for functions + methods
40 const BASE_FN: &[&str] = &[
41     // Callers will depend on the signature of these items, so we better test
42     label_strs::fn_sig,
43     label_strs::generics_of,
44     label_strs::predicates_of,
45     label_strs::type_of,
46     // And a big part of compilation (that we eventually want to cache) is type inference
47     // information:
48     label_strs::typeck,
49 ];
50
51 /// DepNodes for Hir, which is pretty much everything
52 const BASE_HIR: &[&str] = &[
53     // hir_owner and hir_owner_nodes should be computed for all nodes
54     label_strs::hir_owner,
55     label_strs::hir_owner_nodes,
56 ];
57
58 /// `impl` implementation of struct/trait
59 const BASE_IMPL: &[&str] =
60     &[label_strs::associated_item_def_ids, label_strs::generics_of, label_strs::impl_trait_ref];
61
62 /// DepNodes for mir_built/Optimized, which is relevant in "executable"
63 /// code, i.e., functions+methods
64 const BASE_MIR: &[&str] = &[label_strs::optimized_mir, label_strs::promoted_mir];
65
66 /// Struct, Enum and Union DepNodes
67 ///
68 /// Note that changing the type of a field does not change the type of the struct or enum, but
69 /// adding/removing fields or changing a fields name or visibility does.
70 const BASE_STRUCT: &[&str] =
71     &[label_strs::generics_of, label_strs::predicates_of, label_strs::type_of];
72
73 /// Trait definition `DepNode`s.
74 /// Extra `DepNode`s for functions and methods.
75 const EXTRA_ASSOCIATED: &[&str] = &[label_strs::associated_item];
76
77 const EXTRA_TRAIT: &[&str] = &[label_strs::trait_of_item];
78
79 // Fully Built Labels
80
81 const LABELS_CONST: &[&[&str]] = &[BASE_HIR, BASE_CONST];
82
83 /// Constant/Typedef in an impl
84 const LABELS_CONST_IN_IMPL: &[&[&str]] = &[BASE_HIR, BASE_CONST, EXTRA_ASSOCIATED];
85
86 /// Trait-Const/Typedef DepNodes
87 const LABELS_CONST_IN_TRAIT: &[&[&str]] = &[BASE_HIR, BASE_CONST, EXTRA_ASSOCIATED, EXTRA_TRAIT];
88
89 /// Function `DepNode`s.
90 const LABELS_FN: &[&[&str]] = &[BASE_HIR, BASE_MIR, BASE_FN];
91
92 /// Method `DepNode`s.
93 const LABELS_FN_IN_IMPL: &[&[&str]] = &[BASE_HIR, BASE_MIR, BASE_FN, EXTRA_ASSOCIATED];
94
95 /// Trait method `DepNode`s.
96 const LABELS_FN_IN_TRAIT: &[&[&str]] =
97     &[BASE_HIR, BASE_MIR, BASE_FN, EXTRA_ASSOCIATED, EXTRA_TRAIT];
98
99 /// For generic cases like inline-assembly, modules, etc.
100 const LABELS_HIR_ONLY: &[&[&str]] = &[BASE_HIR];
101
102 /// Impl `DepNode`s.
103 const LABELS_TRAIT: &[&[&str]] = &[
104     BASE_HIR,
105     &[label_strs::associated_item_def_ids, label_strs::predicates_of, label_strs::generics_of],
106 ];
107
108 /// Impl `DepNode`s.
109 const LABELS_IMPL: &[&[&str]] = &[BASE_HIR, BASE_IMPL];
110
111 /// Abstract data type (struct, enum, union) `DepNode`s.
112 const LABELS_ADT: &[&[&str]] = &[BASE_HIR, BASE_STRUCT];
113
114 // FIXME: Struct/Enum/Unions Fields (there is currently no way to attach these)
115 //
116 // Fields are kind of separate from their containers, as they can change independently from
117 // them. We should at least check
118 //
119 //     type_of for these.
120
121 type Labels = FxHashSet<String>;
122
123 /// Represents the requested configuration by rustc_clean/dirty
124 struct Assertion {
125     clean: Labels,
126     dirty: Labels,
127 }
128
129 pub fn check_dirty_clean_annotations(tcx: TyCtxt<'_>) {
130     if !tcx.sess.opts.debugging_opts.query_dep_graph {
131         return;
132     }
133
134     // can't add `#[rustc_clean]` etc without opting in to this feature
135     if !tcx.features().rustc_attrs {
136         return;
137     }
138
139     tcx.dep_graph.with_ignore(|| {
140         let krate = tcx.hir().krate();
141         let mut dirty_clean_visitor = DirtyCleanVisitor { tcx, checked_attrs: Default::default() };
142         krate.visit_all_item_likes(&mut dirty_clean_visitor);
143
144         let mut all_attrs = FindAllAttrs { tcx, found_attrs: vec![] };
145         intravisit::walk_crate(&mut all_attrs, krate);
146
147         // Note that we cannot use the existing "unused attribute"-infrastructure
148         // here, since that is running before codegen. This is also the reason why
149         // all codegen-specific attributes are `AssumedUsed` in rustc_ast::feature_gate.
150         all_attrs.report_unchecked_attrs(dirty_clean_visitor.checked_attrs);
151     })
152 }
153
154 pub struct DirtyCleanVisitor<'tcx> {
155     tcx: TyCtxt<'tcx>,
156     checked_attrs: FxHashSet<ast::AttrId>,
157 }
158
159 impl DirtyCleanVisitor<'tcx> {
160     /// Possibly "deserialize" the attribute into a clean/dirty assertion
161     fn assertion_maybe(&mut self, item_id: LocalDefId, attr: &Attribute) -> Option<Assertion> {
162         if !self.tcx.sess.check_name(attr, sym::rustc_clean) {
163             // skip: not rustc_clean/dirty
164             return None;
165         }
166         if !check_config(self.tcx, attr) {
167             // skip: not the correct `cfg=`
168             return None;
169         }
170         let assertion = self.assertion_auto(item_id, attr);
171         Some(assertion)
172     }
173
174     /// Gets the "auto" assertion on pre-validated attr, along with the `except` labels.
175     fn assertion_auto(&mut self, item_id: LocalDefId, attr: &Attribute) -> Assertion {
176         let (name, mut auto) = self.auto_labels(item_id, attr);
177         let except = self.except(attr);
178         for e in except.iter() {
179             if !auto.remove(e) {
180                 let msg = format!(
181                     "`except` specified DepNodes that can not be affected for \"{}\": \"{}\"",
182                     name, e
183                 );
184                 self.tcx.sess.span_fatal(attr.span, &msg);
185             }
186         }
187         Assertion { clean: auto, dirty: except }
188     }
189
190     /// `except=` attribute value
191     fn except(&self, attr: &Attribute) -> Labels {
192         for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
193             if item.has_name(EXCEPT) {
194                 let value = expect_associated_value(self.tcx, &item);
195                 return self.resolve_labels(&item, value);
196             }
197         }
198         // if no `label` or `except` is given, only the node's group are asserted
199         Labels::default()
200     }
201
202     /// Return all DepNode labels that should be asserted for this item.
203     /// index=0 is the "name" used for error messages
204     fn auto_labels(&mut self, item_id: LocalDefId, attr: &Attribute) -> (&'static str, Labels) {
205         let hir_id = self.tcx.hir().local_def_id_to_hir_id(item_id);
206         let node = self.tcx.hir().get(hir_id);
207         let (name, labels) = match node {
208             HirNode::Item(item) => {
209                 match item.kind {
210                     // note: these are in the same order as hir::Item_;
211                     // FIXME(michaelwoerister): do commented out ones
212
213                     // // An `extern crate` item, with optional original crate name,
214                     // HirItem::ExternCrate(..),  // intentionally no assertions
215
216                     // // `use foo::bar::*;` or `use foo::bar::baz as quux;`
217                     // HirItem::Use(..),  // intentionally no assertions
218
219                     // A `static` item
220                     HirItem::Static(..) => ("ItemStatic", LABELS_CONST),
221
222                     // A `const` item
223                     HirItem::Const(..) => ("ItemConst", LABELS_CONST),
224
225                     // A function declaration
226                     HirItem::Fn(..) => ("ItemFn", LABELS_FN),
227
228                     // // A module
229                     HirItem::Mod(..) => ("ItemMod", LABELS_HIR_ONLY),
230
231                     // // An external module
232                     HirItem::ForeignMod { .. } => ("ItemForeignMod", LABELS_HIR_ONLY),
233
234                     // Module-level inline assembly (from global_asm!)
235                     HirItem::GlobalAsm(..) => ("ItemGlobalAsm", LABELS_HIR_ONLY),
236
237                     // A type alias, e.g., `type Foo = Bar<u8>`
238                     HirItem::TyAlias(..) => ("ItemTy", LABELS_HIR_ONLY),
239
240                     // An enum definition, e.g., `enum Foo<A, B> {C<A>, D<B>}`
241                     HirItem::Enum(..) => ("ItemEnum", LABELS_ADT),
242
243                     // A struct definition, e.g., `struct Foo<A> {x: A}`
244                     HirItem::Struct(..) => ("ItemStruct", LABELS_ADT),
245
246                     // A union definition, e.g., `union Foo<A, B> {x: A, y: B}`
247                     HirItem::Union(..) => ("ItemUnion", LABELS_ADT),
248
249                     // Represents a Trait Declaration
250                     HirItem::Trait(..) => ("ItemTrait", LABELS_TRAIT),
251
252                     // An implementation, eg `impl<A> Trait for Foo { .. }`
253                     HirItem::Impl { .. } => ("ItemKind::Impl", LABELS_IMPL),
254
255                     _ => self.tcx.sess.span_fatal(
256                         attr.span,
257                         &format!(
258                             "clean/dirty auto-assertions not yet defined \
259                              for Node::Item.node={:?}",
260                             item.kind
261                         ),
262                     ),
263                 }
264             }
265             HirNode::TraitItem(item) => match item.kind {
266                 TraitItemKind::Fn(..) => ("Node::TraitItem", LABELS_FN_IN_TRAIT),
267                 TraitItemKind::Const(..) => ("NodeTraitConst", LABELS_CONST_IN_TRAIT),
268                 TraitItemKind::Type(..) => ("NodeTraitType", LABELS_CONST_IN_TRAIT),
269             },
270             HirNode::ImplItem(item) => match item.kind {
271                 ImplItemKind::Fn(..) => ("Node::ImplItem", LABELS_FN_IN_IMPL),
272                 ImplItemKind::Const(..) => ("NodeImplConst", LABELS_CONST_IN_IMPL),
273                 ImplItemKind::TyAlias(..) => ("NodeImplType", LABELS_CONST_IN_IMPL),
274             },
275             _ => self.tcx.sess.span_fatal(
276                 attr.span,
277                 &format!("clean/dirty auto-assertions not yet defined for {:?}", node),
278             ),
279         };
280         let labels =
281             Labels::from_iter(labels.iter().flat_map(|s| s.iter().map(|l| (*l).to_string())));
282         (name, labels)
283     }
284
285     fn resolve_labels(&self, item: &NestedMetaItem, value: Symbol) -> Labels {
286         let mut out = Labels::default();
287         for label in value.as_str().split(',') {
288             let label = label.trim();
289             if DepNode::has_label_string(label) {
290                 if out.contains(label) {
291                     self.tcx.sess.span_fatal(
292                         item.span(),
293                         &format!("dep-node label `{}` is repeated", label),
294                     );
295                 }
296                 out.insert(label.to_string());
297             } else {
298                 self.tcx
299                     .sess
300                     .span_fatal(item.span(), &format!("dep-node label `{}` not recognized", label));
301             }
302         }
303         out
304     }
305
306     fn dep_nodes<'l>(
307         &self,
308         labels: &'l Labels,
309         def_id: DefId,
310     ) -> impl Iterator<Item = DepNode> + 'l {
311         let def_path_hash = self.tcx.def_path_hash(def_id);
312         labels.iter().map(move |label| match DepNode::from_label_string(label, def_path_hash) {
313             Ok(dep_node) => dep_node,
314             Err(()) => unreachable!("label: {}", label),
315         })
316     }
317
318     fn dep_node_str(&self, dep_node: &DepNode) -> String {
319         if let Some(def_id) = dep_node.extract_def_id(self.tcx) {
320             format!("{:?}({})", dep_node.kind, self.tcx.def_path_str(def_id))
321         } else {
322             format!("{:?}({:?})", dep_node.kind, dep_node.hash)
323         }
324     }
325
326     fn assert_dirty(&self, item_span: Span, dep_node: DepNode) {
327         debug!("assert_dirty({:?})", dep_node);
328
329         if self.tcx.dep_graph.is_green(&dep_node) {
330             let dep_node_str = self.dep_node_str(&dep_node);
331             self.tcx
332                 .sess
333                 .span_err(item_span, &format!("`{}` should be dirty but is not", dep_node_str));
334         }
335     }
336
337     fn assert_clean(&self, item_span: Span, dep_node: DepNode) {
338         debug!("assert_clean({:?})", dep_node);
339
340         if self.tcx.dep_graph.is_red(&dep_node) {
341             let dep_node_str = self.dep_node_str(&dep_node);
342             self.tcx
343                 .sess
344                 .span_err(item_span, &format!("`{}` should be clean but is not", dep_node_str));
345         }
346     }
347
348     fn check_item(&mut self, item_id: LocalDefId, item_span: Span) {
349         for attr in self.tcx.get_attrs(item_id.to_def_id()).iter() {
350             let assertion = match self.assertion_maybe(item_id, attr) {
351                 Some(a) => a,
352                 None => continue,
353             };
354             self.checked_attrs.insert(attr.id);
355             for dep_node in self.dep_nodes(&assertion.clean, item_id.to_def_id()) {
356                 self.assert_clean(item_span, dep_node);
357             }
358             for dep_node in self.dep_nodes(&assertion.dirty, item_id.to_def_id()) {
359                 self.assert_dirty(item_span, dep_node);
360             }
361         }
362     }
363 }
364
365 impl ItemLikeVisitor<'tcx> for DirtyCleanVisitor<'tcx> {
366     fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) {
367         self.check_item(item.def_id, item.span);
368     }
369
370     fn visit_trait_item(&mut self, item: &hir::TraitItem<'_>) {
371         self.check_item(item.def_id, item.span);
372     }
373
374     fn visit_impl_item(&mut self, item: &hir::ImplItem<'_>) {
375         self.check_item(item.def_id, item.span);
376     }
377
378     fn visit_foreign_item(&mut self, item: &hir::ForeignItem<'_>) {
379         self.check_item(item.def_id, item.span);
380     }
381 }
382
383 /// Given a `#[rustc_clean]` attribute, scan for a `cfg="foo"` attribute and check whether we have
384 /// a cfg flag called `foo`.
385 fn check_config(tcx: TyCtxt<'_>, attr: &Attribute) -> bool {
386     debug!("check_config(attr={:?})", attr);
387     let config = &tcx.sess.parse_sess.config;
388     debug!("check_config: config={:?}", config);
389     let mut cfg = None;
390     for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
391         if item.has_name(CFG) {
392             let value = expect_associated_value(tcx, &item);
393             debug!("check_config: searching for cfg {:?}", value);
394             cfg = Some(config.contains(&(value, None)));
395         } else if !item.has_name(EXCEPT) {
396             tcx.sess.span_err(attr.span, &format!("unknown item `{}`", item.name_or_empty()));
397         }
398     }
399
400     match cfg {
401         None => tcx.sess.span_fatal(attr.span, "no cfg attribute"),
402         Some(c) => c,
403     }
404 }
405
406 fn expect_associated_value(tcx: TyCtxt<'_>, item: &NestedMetaItem) -> Symbol {
407     if let Some(value) = item.value_str() {
408         value
409     } else {
410         let msg = if let Some(ident) = item.ident() {
411             format!("associated value expected for `{}`", ident)
412         } else {
413             "expected an associated value".to_string()
414         };
415
416         tcx.sess.span_fatal(item.span(), &msg);
417     }
418 }
419
420 // A visitor that collects all #[rustc_clean] attributes from
421 // the HIR. It is used to verify that we really ran checks for all annotated
422 // nodes.
423 pub struct FindAllAttrs<'tcx> {
424     tcx: TyCtxt<'tcx>,
425     found_attrs: Vec<&'tcx Attribute>,
426 }
427
428 impl FindAllAttrs<'tcx> {
429     fn is_active_attr(&mut self, attr: &Attribute) -> bool {
430         if self.tcx.sess.check_name(attr, sym::rustc_clean) && check_config(self.tcx, attr) {
431             return true;
432         }
433
434         false
435     }
436
437     fn report_unchecked_attrs(&self, mut checked_attrs: FxHashSet<ast::AttrId>) {
438         for attr in &self.found_attrs {
439             if !checked_attrs.contains(&attr.id) {
440                 self.tcx.sess.span_err(attr.span, "found unchecked `#[rustc_clean]` attribute");
441                 checked_attrs.insert(attr.id);
442             }
443         }
444     }
445 }
446
447 impl intravisit::Visitor<'tcx> for FindAllAttrs<'tcx> {
448     type Map = Map<'tcx>;
449
450     fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap<Self::Map> {
451         intravisit::NestedVisitorMap::All(self.tcx.hir())
452     }
453
454     fn visit_attribute(&mut self, _: hir::HirId, attr: &'tcx Attribute) {
455         if self.is_active_attr(attr) {
456             self.found_attrs.push(attr);
457         }
458     }
459 }