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