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