Skip to main content

rustc_incremental/persist/
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_attr_ir::{Attribute, AttributeKind, RustcCleanAttribute, find_attr};
23use rustc_data_structures::fx::FxHashSet;
24use rustc_data_structures::unord::UnordSet;
25use rustc_hir::def_id::LocalDefId;
26use rustc_hir::{ImplItemKind, ItemKind as HirItem, Node as HirNode, TraitItemKind, intravisit};
27use rustc_middle::dep_graph::{DepKind, DepNode, dep_kind_from_label};
28use rustc_middle::hir::nested_filter;
29use rustc_middle::ty::TyCtxt;
30use rustc_span::{Span, Symbol};
31use tracing::debug;
32
33use crate::diagnostics;
34
35// Base and Extra labels to build up the labels
36
37/// For typedef, constants, and statics
38const BASE_CONST: &[DepKind] = &[DepKind::type_of];
39
40/// DepNodes for functions + methods
41const BASE_FN: &[DepKind] = &[
42    // Callers will depend on the signature of these items, so we better test
43    DepKind::fn_sig,
44    DepKind::generics_of,
45    DepKind::clauses_of,
46    DepKind::type_of,
47    // And a big part of compilation (that we eventually want to cache) is type inference
48    // information:
49    DepKind::typeck_root,
50];
51
52/// DepNodes for Hir, which is pretty much everything
53const BASE_HIR: &[DepKind] = &[
54    // hir_owner should be computed for all nodes
55    DepKind::hir_owner,
56];
57
58/// `impl` implementation of struct/trait
59const BASE_IMPL: &[DepKind] =
60    &[DepKind::associated_item_def_ids, DepKind::generics_of, DepKind::impl_trait_header];
61
62/// DepNodes for exported mir bodies, which is relevant in "executable"
63/// code, i.e., functions+methods
64const BASE_MIR: &[DepKind] = &[DepKind::optimized_mir, DepKind::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.
70const BASE_STRUCT: &[DepKind] = &[DepKind::generics_of, DepKind::clauses_of, DepKind::type_of];
71
72/// Trait definition `DepNode`s.
73/// Extra `DepNode`s for functions and methods.
74const EXTRA_ASSOCIATED: &[DepKind] = &[DepKind::associated_item];
75
76const EXTRA_TRAIT: &[DepKind] = &[];
77
78// Fully Built Labels
79
80const LABELS_CONST: &[&[DepKind]] = &[BASE_HIR, BASE_CONST];
81
82/// Constant/Typedef in an impl
83const LABELS_CONST_IN_IMPL: &[&[DepKind]] = &[BASE_HIR, BASE_CONST, EXTRA_ASSOCIATED];
84
85/// Trait-Const/Typedef DepNodes
86const LABELS_CONST_IN_TRAIT: &[&[DepKind]] = &[BASE_HIR, BASE_CONST, EXTRA_ASSOCIATED, EXTRA_TRAIT];
87
88/// Function `DepNode`s.
89const LABELS_FN: &[&[DepKind]] = &[BASE_HIR, BASE_MIR, BASE_FN];
90
91/// Method `DepNode`s.
92const LABELS_FN_IN_IMPL: &[&[DepKind]] = &[BASE_HIR, BASE_MIR, BASE_FN, EXTRA_ASSOCIATED];
93
94/// Trait method `DepNode`s.
95const LABELS_FN_IN_TRAIT: &[&[DepKind]] =
96    &[BASE_HIR, BASE_MIR, BASE_FN, EXTRA_ASSOCIATED, EXTRA_TRAIT];
97
98/// For generic cases like inline-assembly, modules, etc.
99const LABELS_HIR_ONLY: &[&[DepKind]] = &[BASE_HIR];
100
101/// Impl `DepNode`s.
102const LABELS_TRAIT: &[&[DepKind]] =
103    &[BASE_HIR, &[DepKind::associated_item_def_ids, DepKind::clauses_of, DepKind::generics_of]];
104
105/// Impl `DepNode`s.
106const LABELS_IMPL: &[&[DepKind]] = &[BASE_HIR, BASE_IMPL];
107
108/// Abstract data type (struct, enum, union) `DepNode`s.
109const LABELS_ADT: &[&[DepKind]] = &[BASE_HIR, BASE_STRUCT];
110
111// FIXME: Struct/Enum/Unions Fields (there is currently no way to attach these)
112//
113// Fields are kind of separate from their containers, as they can change independently from
114// them. We should at least check
115//
116//     type_of for these.
117
118type Labels = UnordSet<String>;
119
120/// Represents the requested configuration by rustc_clean
121struct Assertion {
122    clean: Labels,
123    dirty: Labels,
124    loaded_from_disk: Labels,
125}
126
127pub(crate) fn check_clean_annotations(tcx: TyCtxt<'_>) {
128    if !tcx.sess.opts.unstable_opts.query_dep_graph {
129        return;
130    }
131
132    // can't add `#[rustc_clean]` etc without opting into this feature
133    if !tcx.features().rustc_attrs() {
134        return;
135    }
136
137    tcx.dep_graph.with_ignore(|| {
138        let mut clean_visitor = CleanVisitor { tcx, checked_attrs: Default::default() };
139
140        let crate_items = tcx.hir_crate_items(());
141
142        for id in crate_items.free_items() {
143            clean_visitor.check_item(id.owner_id.def_id);
144        }
145
146        for id in crate_items.trait_items() {
147            clean_visitor.check_item(id.owner_id.def_id);
148        }
149
150        for id in crate_items.impl_items() {
151            clean_visitor.check_item(id.owner_id.def_id);
152        }
153
154        for id in crate_items.foreign_items() {
155            clean_visitor.check_item(id.owner_id.def_id);
156        }
157
158        let mut all_attrs = FindAllAttrs { tcx, found_attrs: ::alloc::vec::Vec::new()vec![] };
159        tcx.hir_walk_attributes(&mut all_attrs);
160
161        // Note that we cannot use the existing "unused attribute"-infrastructure
162        // here, since that is running before codegen. This is also the reason why
163        // all codegen-specific attributes are `AssumedUsed` in rustc_ast::feature_gate.
164        all_attrs.report_unchecked_attrs(clean_visitor.checked_attrs);
165    })
166}
167
168struct CleanVisitor<'tcx> {
169    tcx: TyCtxt<'tcx>,
170    checked_attrs: FxHashSet<Span>,
171}
172
173impl<'tcx> CleanVisitor<'tcx> {
174    /// Convert the attribute to an [`Assertion`] if the relevant cfg is active
175    fn assertion_maybe(
176        &mut self,
177        item_id: LocalDefId,
178        attr: &RustcCleanAttribute,
179    ) -> Option<Assertion> {
180        self.tcx.sess.config.contains(&(attr.cfg, None)).then(|| self.assertion_auto(item_id, attr))
181    }
182
183    /// Gets the "auto" assertion on pre-validated attr, along with the `except` labels.
184    fn assertion_auto(&mut self, item_id: LocalDefId, attr: &RustcCleanAttribute) -> Assertion {
185        let (name, mut auto) = self.auto_labels(item_id, attr.span);
186        let except = self.except(attr);
187        let loaded_from_disk = self.loaded_from_disk(attr);
188        for e in except.items().into_sorted_stable_ord() {
189            if !auto.remove(e) {
190                self.tcx.dcx().emit_fatal(diagnostics::AssertionAuto { span: attr.span, name, e });
191            }
192        }
193        Assertion { clean: auto, dirty: except, loaded_from_disk }
194    }
195
196    /// `loaded_from_disk=` attribute value
197    fn loaded_from_disk(&self, attr: &RustcCleanAttribute) -> Labels {
198        attr.loaded_from_disk
199            .as_ref()
200            .map(|queries| self.resolve_labels(&queries.entries, queries.span))
201            .unwrap_or_default()
202    }
203
204    /// `except=` attribute value
205    fn except(&self, attr: &RustcCleanAttribute) -> Labels {
206        attr.except
207            .as_ref()
208            .map(|queries| self.resolve_labels(&queries.entries, queries.span))
209            .unwrap_or_default()
210    }
211
212    /// Return all DepNode labels that should be asserted for this item.
213    /// index=0 is the "name" used for error messages
214    fn auto_labels(&mut self, item_id: LocalDefId, span: Span) -> (&'static str, Labels) {
215        let node = self.tcx.hir_node_by_def_id(item_id);
216        let (name, labels) = match node {
217            HirNode::Item(item) => {
218                match item.kind {
219                    // note: these are in the same order as hir::Item_;
220                    // FIXME(michaelwoerister): do commented out ones
221
222                    // // An `extern crate` item, with optional original crate name,
223                    // HirItem::ExternCrate(..),  // intentionally no assertions
224
225                    // // `use foo::bar::*;` or `use foo::bar::baz as quux;`
226                    // HirItem::Use(..),  // intentionally no assertions
227
228                    // A `static` item
229                    HirItem::Static(..) => ("ItemStatic", LABELS_CONST),
230
231                    // A `const` item
232                    HirItem::Const(..) => ("ItemConst", LABELS_CONST),
233
234                    // A function declaration
235                    HirItem::Fn { .. } => ("ItemFn", LABELS_FN),
236
237                    // // A module
238                    HirItem::Mod(..) => ("ItemMod", LABELS_HIR_ONLY),
239
240                    // // An external module
241                    HirItem::ForeignMod { .. } => ("ItemForeignMod", LABELS_HIR_ONLY),
242
243                    // Module-level inline assembly (from global_asm!)
244                    HirItem::GlobalAsm { .. } => ("ItemGlobalAsm", LABELS_HIR_ONLY),
245
246                    // A type alias, e.g., `type Foo = Bar<u8>`
247                    HirItem::TyAlias(..) => ("ItemTy", LABELS_HIR_ONLY),
248
249                    // An enum definition, e.g., `enum Foo<A, B> {C<A>, D<B>}`
250                    HirItem::Enum(..) => ("ItemEnum", LABELS_ADT),
251
252                    // A struct definition, e.g., `struct Foo<A> {x: A}`
253                    HirItem::Struct(..) => ("ItemStruct", LABELS_ADT),
254
255                    // A union definition, e.g., `union Foo<A, B> {x: A, y: B}`
256                    HirItem::Union(..) => ("ItemUnion", LABELS_ADT),
257
258                    // Represents a Trait Declaration
259                    HirItem::Trait { .. } => ("ItemTrait", LABELS_TRAIT),
260
261                    // An implementation, eg `impl<A> Trait for Foo { .. }`
262                    HirItem::Impl { .. } => ("ItemKind::Impl", LABELS_IMPL),
263
264                    _ => self.tcx.dcx().emit_fatal(diagnostics::UndefinedCleanDirtyItem {
265                        span,
266                        kind: ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:?}", item.kind))
    })format!("{:?}", item.kind),
267                    }),
268                }
269            }
270            HirNode::TraitItem(item) => match item.kind {
271                TraitItemKind::Fn(..) => ("Node::TraitItem", LABELS_FN_IN_TRAIT),
272                TraitItemKind::Const(..) => ("NodeTraitConst", LABELS_CONST_IN_TRAIT),
273                TraitItemKind::Type(..) => ("NodeTraitType", LABELS_CONST_IN_TRAIT),
274            },
275            HirNode::ImplItem(item) => match item.kind {
276                ImplItemKind::Fn(..) => ("Node::ImplItem", LABELS_FN_IN_IMPL),
277                ImplItemKind::Const(..) => ("NodeImplConst", LABELS_CONST_IN_IMPL),
278                ImplItemKind::Type(..) => ("NodeImplType", LABELS_CONST_IN_IMPL),
279            },
280            _ => self
281                .tcx
282                .dcx()
283                .emit_fatal(diagnostics::UndefinedCleanDirty { span, kind: ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:?}", node))
    })format!("{node:?}") }),
284        };
285        let labels =
286            Labels::from_iter(labels.iter().flat_map(|s| s.iter().map(|l| ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:?}", l))
    })format!("{l:?}"))));
287        (name, labels)
288    }
289
290    fn resolve_labels(&self, values: &[Symbol], span: Span) -> Labels {
291        let mut out = Labels::default();
292        for label in values {
293            let label_str = label.as_str();
294            if DepNode::has_label_string(label_str) {
295                if out.contains(label_str) {
296                    self.tcx
297                        .dcx()
298                        .emit_fatal(diagnostics::RepeatedDepNodeLabel { span, label: label_str });
299                }
300                out.insert(label_str.to_string());
301            } else {
302                self.tcx
303                    .dcx()
304                    .emit_fatal(diagnostics::UnrecognizedDepNodeLabel { span, label: label_str });
305            }
306        }
307        out
308    }
309
310    fn dep_node_str(&self, dep_node: &DepNode) -> String {
311        if let Some(def_id) = dep_node.extract_def_id(self.tcx) {
312            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:?}({1})", dep_node.kind,
                self.tcx.def_path_str(def_id)))
    })format!("{:?}({})", dep_node.kind, self.tcx.def_path_str(def_id))
313        } else {
314            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:?}({1:?})", dep_node.kind,
                dep_node.key_fingerprint))
    })format!("{:?}({:?})", dep_node.kind, dep_node.key_fingerprint)
315        }
316    }
317
318    fn assert_dirty(&self, item_span: Span, dep_node: DepNode) {
319        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/75a75c3e0a67d3fa3d03982775f5bb0356e7b510/compiler/rustc_incremental/src/persist/clean.rs:319",
                        "rustc_incremental::persist::clean",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/75a75c3e0a67d3fa3d03982775f5bb0356e7b510/compiler/rustc_incremental/src/persist/clean.rs"),
                        ::tracing_core::__macro_support::Option::Some(319u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_incremental::persist::clean"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("assert_dirty({0:?})",
                                                    dep_node) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("assert_dirty({:?})", dep_node);
320
321        if self.tcx.dep_graph.is_green(&dep_node) {
322            let dep_node_str = self.dep_node_str(&dep_node);
323            self.tcx
324                .dcx()
325                .emit_err(diagnostics::NotDirty { span: item_span, dep_node_str: &dep_node_str });
326        }
327    }
328
329    fn assert_clean(&self, item_span: Span, dep_node: DepNode) {
330        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/75a75c3e0a67d3fa3d03982775f5bb0356e7b510/compiler/rustc_incremental/src/persist/clean.rs:330",
                        "rustc_incremental::persist::clean",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/75a75c3e0a67d3fa3d03982775f5bb0356e7b510/compiler/rustc_incremental/src/persist/clean.rs"),
                        ::tracing_core::__macro_support::Option::Some(330u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_incremental::persist::clean"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("assert_clean({0:?})",
                                                    dep_node) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!("assert_clean({:?})", dep_node);
331
332        if self.tcx.dep_graph.is_red(&dep_node) {
333            let dep_node_str = self.dep_node_str(&dep_node);
334            self.tcx
335                .dcx()
336                .emit_err(diagnostics::NotClean { span: item_span, dep_node_str: &dep_node_str });
337        }
338    }
339
340    fn check_item(&mut self, item_id: LocalDefId) {
341        let item_span = self.tcx.def_span(item_id.to_def_id());
342        let def_path_hash = self.tcx.def_path_hash(item_id.to_def_id());
343
344        let Some(clean_attrs) = {
    {
        'done:
            {
            for i in ::rustc_attr_ir::HasAttrs::get_attrs(item_id, &self.tcx)
                {
                #[allow(unused_imports)]
                use ::rustc_attr_ir::AttributeKind::*;
                let i: &::rustc_attr_ir::Attribute = i;
                match i {
                    ::rustc_attr_ir::Attribute::Parsed(RustcClean(attr)) => {
                        break 'done Some(attr);
                    }
                    ::rustc_attr_ir::Attribute::Unparsed(..) =>
                        {}
                        #[deny(unreachable_patterns)]
                        _ => {}
                }
            }
            None
        }
    }
}find_attr!(self.tcx, item_id, RustcClean(attr) => attr) else {
345            return;
346        };
347
348        for attr in clean_attrs {
349            let Some(assertion) = self.assertion_maybe(item_id, attr) else {
350                continue;
351            };
352            self.checked_attrs.insert(attr.span);
353            for label in assertion.clean.items().into_sorted_stable_ord() {
354                let dep_node = DepNode::from_label_string(self.tcx, label, def_path_hash).unwrap();
355                self.assert_clean(item_span, dep_node);
356            }
357            for label in assertion.dirty.items().into_sorted_stable_ord() {
358                let dep_node = DepNode::from_label_string(self.tcx, label, def_path_hash).unwrap();
359                self.assert_dirty(item_span, dep_node);
360            }
361            for label in assertion.loaded_from_disk.items().into_sorted_stable_ord() {
362                match DepNode::from_label_string(self.tcx, label, def_path_hash) {
363                    Ok(dep_node) => {
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.dcx().emit_err(diagnostics::NotLoaded {
367                                span: item_span,
368                                dep_node_str: &dep_node_str,
369                            });
370                        }
371                    }
372                    // Opaque/unit hash, we only know the dep kind
373                    Err(()) => {
374                        let dep_kind = dep_kind_from_label(label);
375                        if !self.tcx.dep_graph.debug_dep_kind_was_loaded_from_disk(dep_kind) {
376                            self.tcx.dcx().emit_err(diagnostics::NotLoaded {
377                                span: item_span,
378                                dep_node_str: &label,
379                            });
380                        }
381                    }
382                }
383            }
384        }
385    }
386}
387
388/// A visitor that collects all `#[rustc_clean]` attributes from
389/// the HIR. It is used to verify that we really ran checks for all annotated
390/// nodes.
391struct FindAllAttrs<'tcx> {
392    tcx: TyCtxt<'tcx>,
393    found_attrs: Vec<&'tcx RustcCleanAttribute>,
394}
395
396impl<'tcx> FindAllAttrs<'tcx> {
397    fn is_active_attr(&self, attr: &RustcCleanAttribute) -> bool {
398        self.tcx.sess.config.contains(&(attr.cfg, None))
399    }
400
401    fn report_unchecked_attrs(&self, mut checked_attrs: FxHashSet<Span>) {
402        for attr in &self.found_attrs {
403            if !checked_attrs.contains(&attr.span) {
404                self.tcx.dcx().emit_err(diagnostics::UncheckedClean { span: attr.span });
405                checked_attrs.insert(attr.span);
406            }
407        }
408    }
409}
410
411impl<'tcx> intravisit::Visitor<'tcx> for FindAllAttrs<'tcx> {
412    type NestedFilter = nested_filter::All;
413
414    fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
415        self.tcx
416    }
417
418    fn visit_attribute(&mut self, attr: &'tcx Attribute) {
419        if let Attribute::Parsed(AttributeKind::RustcClean(attrs)) = attr {
420            for attr in attrs {
421                if self.is_active_attr(attr) {
422                    self.found_attrs.push(attr);
423                }
424            }
425        }
426    }
427}