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