]> git.proxmox.com Git - rustc.git/blob - src/librustc_incremental/persist/dirty_clean.rs
New upstream version 1.41.1+dfsg1
[rustc.git] / src / librustc_incremental / persist / dirty_clean.rs
1 //! Debugging code to test fingerprints computed for query results.
2 //! For each node marked with `#[rustc_clean]` or `#[rustc_dirty]`,
3 //! we will compare the fingerprint from the current and from the previous
4 //! compilation session as appropriate:
5 //!
6 //! - `#[rustc_clean(cfg="rev2", except="typeck_tables_of")]` if we are
7 //! in `#[cfg(rev2)]`, then the fingerprints associated with
8 //! `DepNode::typeck_tables_of(X)` must be DIFFERENT (`X` is the `DefId` of the
9 //! current node).
10 //! - `#[rustc_clean(cfg="rev2")]` same as above, except that the
11 //! fingerprints must be the SAME (along with all other fingerprints).
12 //!
13 //! Errors are reported if we are in the suitable configuration but
14 //! the required condition is not met.
15
16 use std::iter::FromIterator;
17 use std::vec::Vec;
18 use rustc::dep_graph::{DepNode, label_strs};
19 use rustc::hir;
20 use rustc::hir::{ItemKind as HirItem, ImplItemKind, TraitItemKind};
21 use rustc::hir::Node as HirNode;
22 use rustc::hir::def_id::DefId;
23 use rustc::hir::itemlikevisit::ItemLikeVisitor;
24 use rustc::hir::intravisit;
25 use rustc::ich::{ATTR_DIRTY, ATTR_CLEAN};
26 use rustc::ty::TyCtxt;
27 use rustc_data_structures::fingerprint::Fingerprint;
28 use rustc_data_structures::fx::FxHashSet;
29 use syntax::ast::{self, Attribute, NestedMetaItem};
30 use syntax::symbol::{Symbol, sym};
31 use syntax_pos::Span;
32
33 const EXCEPT: Symbol = sym::except;
34 const LABEL: Symbol = sym::label;
35 const CFG: Symbol = sym::cfg;
36
37 // Base and Extra labels to build up the labels
38
39 /// For typedef, constants, and statics
40 const BASE_CONST: &[&str] = &[
41 label_strs::type_of,
42 ];
43
44 /// DepNodes for functions + methods
45 const BASE_FN: &[&str] = &[
46 // Callers will depend on the signature of these items, so we better test
47 label_strs::fn_sig,
48 label_strs::generics_of,
49 label_strs::predicates_of,
50 label_strs::type_of,
51
52 // And a big part of compilation (that we eventually want to cache) is type inference
53 // information:
54 label_strs::typeck_tables_of,
55 ];
56
57 /// DepNodes for Hir, which is pretty much everything
58 const BASE_HIR: &[&str] = &[
59 // Hir and HirBody should be computed for all nodes
60 label_strs::Hir,
61 label_strs::HirBody,
62 ];
63
64 /// `impl` implementation of struct/trait
65 const BASE_IMPL: &[&str] = &[
66 label_strs::associated_item_def_ids,
67 label_strs::generics_of,
68 label_strs::impl_trait_ref,
69 ];
70
71 /// DepNodes for mir_built/Optimized, which is relevant in "executable"
72 /// code, i.e., functions+methods
73 const BASE_MIR: &[&str] = &[
74 label_strs::optimized_mir,
75 label_strs::promoted_mir,
76 label_strs::mir_built,
77 ];
78
79 /// Struct, Enum and Union DepNodes
80 ///
81 /// Note that changing the type of a field does not change the type of the struct or enum, but
82 /// adding/removing fields or changing a fields name or visibility does.
83 const BASE_STRUCT: &[&str] = &[
84 label_strs::generics_of,
85 label_strs::predicates_of,
86 label_strs::type_of,
87 ];
88
89 /// Trait definition `DepNode`s.
90 const BASE_TRAIT_DEF: &[&str] = &[
91 label_strs::associated_item_def_ids,
92 label_strs::generics_of,
93 label_strs::is_object_safe,
94 label_strs::predicates_of,
95 label_strs::specialization_graph_of,
96 label_strs::trait_def,
97 label_strs::trait_impls_of,
98 ];
99
100 /// Extra `DepNode`s for functions and methods.
101 const EXTRA_ASSOCIATED: &[&str] = &[
102 label_strs::associated_item,
103 ];
104
105 const EXTRA_TRAIT: &[&str] = &[
106 label_strs::trait_of_item,
107 ];
108
109 // Fully Built Labels
110
111 const LABELS_CONST: &[&[&str]] = &[
112 BASE_HIR,
113 BASE_CONST,
114 ];
115
116 /// Constant/Typedef in an impl
117 const LABELS_CONST_IN_IMPL: &[&[&str]] = &[
118 BASE_HIR,
119 BASE_CONST,
120 EXTRA_ASSOCIATED,
121 ];
122
123 /// Trait-Const/Typedef DepNodes
124 const LABELS_CONST_IN_TRAIT: &[&[&str]] = &[
125 BASE_HIR,
126 BASE_CONST,
127 EXTRA_ASSOCIATED,
128 EXTRA_TRAIT,
129 ];
130
131 /// Function `DepNode`s.
132 const LABELS_FN: &[&[&str]] = &[
133 BASE_HIR,
134 BASE_MIR,
135 BASE_FN,
136 ];
137
138 /// Method `DepNode`s.
139 const LABELS_FN_IN_IMPL: &[&[&str]] = &[
140 BASE_HIR,
141 BASE_MIR,
142 BASE_FN,
143 EXTRA_ASSOCIATED,
144 ];
145
146 /// Trait method `DepNode`s.
147 const LABELS_FN_IN_TRAIT: &[&[&str]] = &[
148 BASE_HIR,
149 BASE_MIR,
150 BASE_FN,
151 EXTRA_ASSOCIATED,
152 EXTRA_TRAIT,
153 ];
154
155 /// For generic cases like inline-assembly, modules, etc.
156 const LABELS_HIR_ONLY: &[&[&str]] = &[
157 BASE_HIR,
158 ];
159
160 /// Impl `DepNode`s.
161 const LABELS_IMPL: &[&[&str]] = &[
162 BASE_HIR,
163 BASE_IMPL,
164 ];
165
166 /// Abstract data type (struct, enum, union) `DepNode`s.
167 const LABELS_ADT: &[&[&str]] = &[
168 BASE_HIR,
169 BASE_STRUCT,
170 ];
171
172 /// Trait definition `DepNode`s.
173 #[allow(dead_code)]
174 const LABELS_TRAIT: &[&[&str]] = &[
175 BASE_HIR,
176 BASE_TRAIT_DEF,
177 ];
178
179
180 // FIXME: Struct/Enum/Unions Fields (there is currently no way to attach these)
181 //
182 // Fields are kind of separate from their containers, as they can change independently from
183 // them. We should at least check
184 //
185 // type_of for these.
186
187 type Labels = FxHashSet<String>;
188
189 /// Represents the requested configuration by rustc_clean/dirty
190 struct Assertion {
191 clean: Labels,
192 dirty: Labels,
193 }
194
195 impl Assertion {
196 fn from_clean_labels(labels: Labels) -> Assertion {
197 Assertion {
198 clean: labels,
199 dirty: Labels::default(),
200 }
201 }
202
203 fn from_dirty_labels(labels: Labels) -> Assertion {
204 Assertion {
205 clean: Labels::default(),
206 dirty: labels,
207 }
208 }
209 }
210
211 pub fn check_dirty_clean_annotations(tcx: TyCtxt<'_>) {
212 // can't add `#[rustc_dirty]` etc without opting in to this feature
213 if !tcx.features().rustc_attrs {
214 return;
215 }
216
217 tcx.dep_graph.with_ignore(|| {
218 let krate = tcx.hir().krate();
219 let mut dirty_clean_visitor = DirtyCleanVisitor {
220 tcx,
221 checked_attrs: Default::default(),
222 };
223 krate.visit_all_item_likes(&mut dirty_clean_visitor);
224
225 let mut all_attrs = FindAllAttrs {
226 tcx,
227 attr_names: vec![ATTR_DIRTY, ATTR_CLEAN],
228 found_attrs: vec![],
229 };
230 intravisit::walk_crate(&mut all_attrs, krate);
231
232 // Note that we cannot use the existing "unused attribute"-infrastructure
233 // here, since that is running before codegen. This is also the reason why
234 // all codegen-specific attributes are `Whitelisted` in syntax::feature_gate.
235 all_attrs.report_unchecked_attrs(&dirty_clean_visitor.checked_attrs);
236 })
237 }
238
239 pub struct DirtyCleanVisitor<'tcx> {
240 tcx: TyCtxt<'tcx>,
241 checked_attrs: FxHashSet<ast::AttrId>,
242 }
243
244 impl DirtyCleanVisitor<'tcx> {
245 /// Possibly "deserialize" the attribute into a clean/dirty assertion
246 fn assertion_maybe(&mut self, item_id: hir::HirId, attr: &Attribute)
247 -> Option<Assertion>
248 {
249 let is_clean = if attr.check_name(ATTR_DIRTY) {
250 false
251 } else if attr.check_name(ATTR_CLEAN) {
252 true
253 } else {
254 // skip: not rustc_clean/dirty
255 return None
256 };
257 if !check_config(self.tcx, attr) {
258 // skip: not the correct `cfg=`
259 return None;
260 }
261 let assertion = if let Some(labels) = self.labels(attr) {
262 if is_clean {
263 Assertion::from_clean_labels(labels)
264 } else {
265 Assertion::from_dirty_labels(labels)
266 }
267 } else {
268 self.assertion_auto(item_id, attr, is_clean)
269 };
270 Some(assertion)
271 }
272
273 /// Gets the "auto" assertion on pre-validated attr, along with the `except` labels.
274 fn assertion_auto(&mut self, item_id: hir::HirId, attr: &Attribute, is_clean: bool)
275 -> Assertion
276 {
277 let (name, mut auto) = self.auto_labels(item_id, attr);
278 let except = self.except(attr);
279 for e in except.iter() {
280 if !auto.remove(e) {
281 let msg = format!(
282 "`except` specified DepNodes that can not be affected for \"{}\": \"{}\"",
283 name,
284 e
285 );
286 self.tcx.sess.span_fatal(attr.span, &msg);
287 }
288 }
289 if is_clean {
290 Assertion {
291 clean: auto,
292 dirty: except,
293 }
294 } else {
295 Assertion {
296 clean: except,
297 dirty: auto,
298 }
299 }
300 }
301
302 fn labels(&self, attr: &Attribute) -> Option<Labels> {
303 for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
304 if item.check_name(LABEL) {
305 let value = expect_associated_value(self.tcx, &item);
306 return Some(self.resolve_labels(&item, &value.as_str()));
307 }
308 }
309 None
310 }
311
312 /// `except=` attribute value
313 fn except(&self, attr: &Attribute) -> Labels {
314 for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
315 if item.check_name(EXCEPT) {
316 let value = expect_associated_value(self.tcx, &item);
317 return self.resolve_labels(&item, &value.as_str());
318 }
319 }
320 // if no `label` or `except` is given, only the node's group are asserted
321 Labels::default()
322 }
323
324 /// Return all DepNode labels that should be asserted for this item.
325 /// index=0 is the "name" used for error messages
326 fn auto_labels(&mut self, item_id: hir::HirId, attr: &Attribute) -> (&'static str, Labels) {
327 let node = self.tcx.hir().get(item_id);
328 let (name, labels) = match node {
329 HirNode::Item(item) => {
330 match item.kind {
331 // note: these are in the same order as hir::Item_;
332 // FIXME(michaelwoerister): do commented out ones
333
334 // // An `extern crate` item, with optional original crate name,
335 // HirItem::ExternCrate(..), // intentionally no assertions
336
337 // // `use foo::bar::*;` or `use foo::bar::baz as quux;`
338 // HirItem::Use(..), // intentionally no assertions
339
340 // A `static` item
341 HirItem::Static(..) => ("ItemStatic", LABELS_CONST),
342
343 // A `const` item
344 HirItem::Const(..) => ("ItemConst", LABELS_CONST),
345
346 // A function declaration
347 HirItem::Fn(..) => ("ItemFn", LABELS_FN),
348
349 // // A module
350 HirItem::Mod(..) =>("ItemMod", LABELS_HIR_ONLY),
351
352 // // An external module
353 HirItem::ForeignMod(..) => ("ItemForeignMod", LABELS_HIR_ONLY),
354
355 // Module-level inline assembly (from global_asm!)
356 HirItem::GlobalAsm(..) => ("ItemGlobalAsm", LABELS_HIR_ONLY),
357
358 // A type alias, e.g., `type Foo = Bar<u8>`
359 HirItem::TyAlias(..) => ("ItemTy", LABELS_HIR_ONLY),
360
361 // An enum definition, e.g., `enum Foo<A, B> {C<A>, D<B>}`
362 HirItem::Enum(..) => ("ItemEnum", LABELS_ADT),
363
364 // A struct definition, e.g., `struct Foo<A> {x: A}`
365 HirItem::Struct(..) => ("ItemStruct", LABELS_ADT),
366
367 // A union definition, e.g., `union Foo<A, B> {x: A, y: B}`
368 HirItem::Union(..) => ("ItemUnion", LABELS_ADT),
369
370 // Represents a Trait Declaration
371 // FIXME(michaelwoerister): trait declaration is buggy because sometimes some of
372 // the depnodes don't exist (because they legitametely didn't need to be
373 // calculated)
374 //
375 // michaelwoerister and vitiral came up with a possible solution,
376 // to just do this before every query
377 // ```
378 // ::rustc::ty::query::plumbing::force_from_dep_node(tcx, dep_node)
379 // ```
380 //
381 // However, this did not seem to work effectively and more bugs were hit.
382 // Nebie @vitiral gave up :)
383 //
384 //HirItem::Trait(..) => ("ItemTrait", LABELS_TRAIT),
385
386 // An implementation, eg `impl<A> Trait for Foo { .. }`
387 HirItem::Impl(..) => ("ItemKind::Impl", LABELS_IMPL),
388
389 _ => self.tcx.sess.span_fatal(
390 attr.span,
391 &format!(
392 "clean/dirty auto-assertions not yet defined \
393 for Node::Item.node={:?}",
394 item.kind
395 )
396 ),
397 }
398 },
399 HirNode::TraitItem(item) => {
400 match item.kind {
401 TraitItemKind::Method(..) => ("Node::TraitItem", LABELS_FN_IN_TRAIT),
402 TraitItemKind::Const(..) => ("NodeTraitConst", LABELS_CONST_IN_TRAIT),
403 TraitItemKind::Type(..) => ("NodeTraitType", LABELS_CONST_IN_TRAIT),
404 }
405 },
406 HirNode::ImplItem(item) => {
407 match item.kind {
408 ImplItemKind::Method(..) => ("Node::ImplItem", LABELS_FN_IN_IMPL),
409 ImplItemKind::Const(..) => ("NodeImplConst", LABELS_CONST_IN_IMPL),
410 ImplItemKind::TyAlias(..) => ("NodeImplType", LABELS_CONST_IN_IMPL),
411 ImplItemKind::OpaqueTy(..) => ("NodeImplType", LABELS_CONST_IN_IMPL),
412 }
413 },
414 _ => self.tcx.sess.span_fatal(
415 attr.span,
416 &format!(
417 "clean/dirty auto-assertions not yet defined for {:?}",
418 node
419 )
420 ),
421 };
422 let labels = Labels::from_iter(
423 labels.iter().flat_map(|s| s.iter().map(|l| l.to_string()))
424 );
425 (name, labels)
426 }
427
428 fn resolve_labels(&self, item: &NestedMetaItem, value: &str) -> Labels {
429 let mut out = Labels::default();
430 for label in value.split(',') {
431 let label = label.trim();
432 if DepNode::has_label_string(label) {
433 if out.contains(label) {
434 self.tcx.sess.span_fatal(
435 item.span(),
436 &format!("dep-node label `{}` is repeated", label));
437 }
438 out.insert(label.to_string());
439 } else {
440 self.tcx.sess.span_fatal(
441 item.span(),
442 &format!("dep-node label `{}` not recognized", label));
443 }
444 }
445 out
446 }
447
448 fn dep_nodes<'l>(
449 &self,
450 labels: &'l Labels,
451 def_id: DefId
452 ) -> impl Iterator<Item = DepNode> + 'l {
453 let def_path_hash = self.tcx.def_path_hash(def_id);
454 labels
455 .iter()
456 .map(move |label| {
457 match DepNode::from_label_string(label, def_path_hash) {
458 Ok(dep_node) => dep_node,
459 Err(()) => unreachable!(),
460 }
461 })
462 }
463
464 fn dep_node_str(&self, dep_node: &DepNode) -> String {
465 if let Some(def_id) = dep_node.extract_def_id(self.tcx) {
466 format!("{:?}({})",
467 dep_node.kind,
468 self.tcx.def_path_str(def_id))
469 } else {
470 format!("{:?}({:?})", dep_node.kind, dep_node.hash)
471 }
472 }
473
474 fn assert_dirty(&self, item_span: Span, dep_node: DepNode) {
475 debug!("assert_dirty({:?})", dep_node);
476
477 let current_fingerprint = self.get_fingerprint(&dep_node);
478 let prev_fingerprint = self.tcx.dep_graph.prev_fingerprint_of(&dep_node);
479
480 if current_fingerprint == prev_fingerprint {
481 let dep_node_str = self.dep_node_str(&dep_node);
482 self.tcx.sess.span_err(
483 item_span,
484 &format!("`{}` should be dirty but is not", dep_node_str));
485 }
486 }
487
488 fn get_fingerprint(&self, dep_node: &DepNode) -> Option<Fingerprint> {
489 if self.tcx.dep_graph.dep_node_exists(dep_node) {
490 let dep_node_index = self.tcx.dep_graph.dep_node_index_of(dep_node);
491 Some(self.tcx.dep_graph.fingerprint_of(dep_node_index))
492 } else {
493 None
494 }
495 }
496
497 fn assert_clean(&self, item_span: Span, dep_node: DepNode) {
498 debug!("assert_clean({:?})", dep_node);
499
500 let current_fingerprint = self.get_fingerprint(&dep_node);
501 let prev_fingerprint = self.tcx.dep_graph.prev_fingerprint_of(&dep_node);
502
503 // if the node wasn't previously evaluated and now is (or vice versa),
504 // then the node isn't actually clean or dirty.
505 if (current_fingerprint == None) ^ (prev_fingerprint == None) {
506 return;
507 }
508
509 if current_fingerprint != prev_fingerprint {
510 let dep_node_str = self.dep_node_str(&dep_node);
511 self.tcx.sess.span_err(
512 item_span,
513 &format!("`{}` should be clean but is not", dep_node_str));
514 }
515 }
516
517 fn check_item(&mut self, item_id: hir::HirId, item_span: Span) {
518 let def_id = self.tcx.hir().local_def_id(item_id);
519 for attr in self.tcx.get_attrs(def_id).iter() {
520 let assertion = match self.assertion_maybe(item_id, attr) {
521 Some(a) => a,
522 None => continue,
523 };
524 self.checked_attrs.insert(attr.id);
525 for dep_node in self.dep_nodes(&assertion.clean, def_id) {
526 self.assert_clean(item_span, dep_node);
527 }
528 for dep_node in self.dep_nodes(&assertion.dirty, def_id) {
529 self.assert_dirty(item_span, dep_node);
530 }
531 }
532 }
533 }
534
535 impl ItemLikeVisitor<'tcx> for DirtyCleanVisitor<'tcx> {
536 fn visit_item(&mut self, item: &'tcx hir::Item) {
537 self.check_item(item.hir_id, item.span);
538 }
539
540 fn visit_trait_item(&mut self, item: &hir::TraitItem) {
541 self.check_item(item.hir_id, item.span);
542 }
543
544 fn visit_impl_item(&mut self, item: &hir::ImplItem) {
545 self.check_item(item.hir_id, item.span);
546 }
547 }
548
549 /// Given a `#[rustc_dirty]` or `#[rustc_clean]` attribute, scan
550 /// for a `cfg="foo"` attribute and check whether we have a cfg
551 /// flag called `foo`.
552 ///
553 /// Also make sure that the `label` and `except` fields do not
554 /// both exist.
555 fn check_config(tcx: TyCtxt<'_>, attr: &Attribute) -> bool {
556 debug!("check_config(attr={:?})", attr);
557 let config = &tcx.sess.parse_sess.config;
558 debug!("check_config: config={:?}", config);
559 let (mut cfg, mut except, mut label) = (None, false, false);
560 for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
561 if item.check_name(CFG) {
562 let value = expect_associated_value(tcx, &item);
563 debug!("check_config: searching for cfg {:?}", value);
564 cfg = Some(config.contains(&(value, None)));
565 }
566 if item.check_name(LABEL) {
567 label = true;
568 }
569 if item.check_name(EXCEPT) {
570 except = true;
571 }
572 }
573
574 if label && except {
575 tcx.sess.span_fatal(
576 attr.span,
577 "must specify only one of: `label`, `except`"
578 );
579 }
580
581 match cfg {
582 None => tcx.sess.span_fatal(
583 attr.span,
584 "no cfg attribute"
585 ),
586 Some(c) => c,
587 }
588 }
589
590 fn expect_associated_value(tcx: TyCtxt<'_>, item: &NestedMetaItem) -> ast::Name {
591 if let Some(value) = item.value_str() {
592 value
593 } else {
594 let msg = if let Some(ident) = item.ident() {
595 format!("associated value expected for `{}`", ident)
596 } else {
597 "expected an associated value".to_string()
598 };
599
600 tcx.sess.span_fatal(item.span(), &msg);
601 }
602 }
603
604 // A visitor that collects all #[rustc_dirty]/#[rustc_clean] attributes from
605 // the HIR. It is used to verfiy that we really ran checks for all annotated
606 // nodes.
607 pub struct FindAllAttrs<'tcx> {
608 tcx: TyCtxt<'tcx>,
609 attr_names: Vec<Symbol>,
610 found_attrs: Vec<&'tcx Attribute>,
611 }
612
613 impl FindAllAttrs<'tcx> {
614 fn is_active_attr(&mut self, attr: &Attribute) -> bool {
615 for attr_name in &self.attr_names {
616 if attr.check_name(*attr_name) && check_config(self.tcx, attr) {
617 return true;
618 }
619 }
620
621 false
622 }
623
624 fn report_unchecked_attrs(&self, checked_attrs: &FxHashSet<ast::AttrId>) {
625 for attr in &self.found_attrs {
626 if !checked_attrs.contains(&attr.id) {
627 self.tcx.sess.span_err(attr.span, &format!("found unchecked \
628 `#[rustc_dirty]` / `#[rustc_clean]` attribute"));
629 }
630 }
631 }
632 }
633
634 impl intravisit::Visitor<'tcx> for FindAllAttrs<'tcx> {
635 fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
636 intravisit::NestedVisitorMap::All(&self.tcx.hir())
637 }
638
639 fn visit_attribute(&mut self, attr: &'tcx Attribute) {
640 if self.is_active_attr(attr) {
641 self.found_attrs.push(attr);
642 }
643 }
644 }