rustc_incremental/persist/
dirty_clean.rs
1use 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
42const BASE_CONST: &[&str] = &[label_strs::type_of];
46
47const BASE_FN: &[&str] = &[
49 label_strs::fn_sig,
51 label_strs::generics_of,
52 label_strs::predicates_of,
53 label_strs::type_of,
54 label_strs::typeck,
57];
58
59const BASE_HIR: &[&str] = &[
61 label_strs::opt_hir_owner_nodes,
63];
64
65const BASE_IMPL: &[&str] =
67 &[label_strs::associated_item_def_ids, label_strs::generics_of, label_strs::impl_trait_header];
68
69const BASE_MIR: &[&str] = &[label_strs::optimized_mir, label_strs::promoted_mir];
72
73const BASE_STRUCT: &[&str] =
78 &[label_strs::generics_of, label_strs::predicates_of, label_strs::type_of];
79
80const EXTRA_ASSOCIATED: &[&str] = &[label_strs::associated_item];
83
84const EXTRA_TRAIT: &[&str] = &[];
85
86const LABELS_CONST: &[&[&str]] = &[BASE_HIR, BASE_CONST];
89
90const LABELS_CONST_IN_IMPL: &[&[&str]] = &[BASE_HIR, BASE_CONST, EXTRA_ASSOCIATED];
92
93const LABELS_CONST_IN_TRAIT: &[&[&str]] = &[BASE_HIR, BASE_CONST, EXTRA_ASSOCIATED, EXTRA_TRAIT];
95
96const LABELS_FN: &[&[&str]] = &[BASE_HIR, BASE_MIR, BASE_FN];
98
99const LABELS_FN_IN_IMPL: &[&[&str]] = &[BASE_HIR, BASE_MIR, BASE_FN, EXTRA_ASSOCIATED];
101
102const LABELS_FN_IN_TRAIT: &[&[&str]] =
104 &[BASE_HIR, BASE_MIR, BASE_FN, EXTRA_ASSOCIATED, EXTRA_TRAIT];
105
106const LABELS_HIR_ONLY: &[&[&str]] = &[BASE_HIR];
108
109const LABELS_TRAIT: &[&[&str]] = &[
111 BASE_HIR,
112 &[label_strs::associated_item_def_ids, label_strs::predicates_of, label_strs::generics_of],
113];
114
115const LABELS_IMPL: &[&[&str]] = &[BASE_HIR, BASE_IMPL];
117
118const LABELS_ADT: &[&[&str]] = &[BASE_HIR, BASE_STRUCT];
120
121type Labels = UnordSet<String>;
129
130struct 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 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 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 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 return None;
190 }
191 let assertion = self.assertion_auto(item_id, attr);
192 Some(assertion)
193 }
194
195 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 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 Labels::default()
218 }
219
220 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 Labels::default()
230 }
231
232 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 HirItem::Static(..) => ("ItemStatic", LABELS_CONST),
250
251 HirItem::Const(..) => ("ItemConst", LABELS_CONST),
253
254 HirItem::Fn { .. } => ("ItemFn", LABELS_FN),
256
257 HirItem::Mod(..) => ("ItemMod", LABELS_HIR_ONLY),
259
260 HirItem::ForeignMod { .. } => ("ItemForeignMod", LABELS_HIR_ONLY),
262
263 HirItem::GlobalAsm(..) => ("ItemGlobalAsm", LABELS_HIR_ONLY),
265
266 HirItem::TyAlias(..) => ("ItemTy", LABELS_HIR_ONLY),
268
269 HirItem::Enum(..) => ("ItemEnum", LABELS_ADT),
271
272 HirItem::Struct(..) => ("ItemStruct", LABELS_ADT),
274
275 HirItem::Union(..) => ("ItemUnion", LABELS_ADT),
277
278 HirItem::Trait(..) => ("ItemTrait", LABELS_TRAIT),
280
281 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
395fn 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
428struct 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}