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