]> git.lizzy.rs Git - rust.git/blob - src/librustc_incremental/persist/dirty_clean.rs
Rollup merge of #42496 - Razaekel:feature/integer_max-min, r=BurntSushi
[rust.git] / src / librustc_incremental / persist / dirty_clean.rs
1 // Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 //! Debugging code to test the state of the dependency graph just
12 //! after it is loaded from disk and just after it has been saved.
13 //! For each node marked with `#[rustc_clean]` or `#[rustc_dirty]`,
14 //! we will check that a suitable node for that item either appears
15 //! or does not appear in the dep-graph, as appropriate:
16 //!
17 //! - `#[rustc_dirty(label="TypeckTables", cfg="rev2")]` if we are
18 //!   in `#[cfg(rev2)]`, then there MUST NOT be a node
19 //!   `DepNode::TypeckTables(X)` where `X` is the def-id of the
20 //!   current node.
21 //! - `#[rustc_clean(label="TypeckTables", cfg="rev2")]` same as above,
22 //!   except that the node MUST exist.
23 //!
24 //! Errors are reported if we are in the suitable configuration but
25 //! the required condition is not met.
26 //!
27 //! The `#[rustc_metadata_dirty]` and `#[rustc_metadata_clean]` attributes
28 //! can be used to check the incremental compilation hash (ICH) values of
29 //! metadata exported in rlibs.
30 //!
31 //! - If a node is marked with `#[rustc_metadata_clean(cfg="rev2")]` we
32 //!   check that the metadata hash for that node is the same for "rev2"
33 //!   it was for "rev1".
34 //! - If a node is marked with `#[rustc_metadata_dirty(cfg="rev2")]` we
35 //!   check that the metadata hash for that node is *different* for "rev2"
36 //!   than it was for "rev1".
37 //!
38 //! Note that the metadata-testing attributes must never specify the
39 //! first revision. This would lead to a crash since there is no
40 //! previous revision to compare things to.
41 //!
42
43 use super::data::DepNodeIndex;
44 use super::load::DirtyNodes;
45 use rustc::dep_graph::{DepGraphQuery, DepNode, DepKind};
46 use rustc::hir;
47 use rustc::hir::def_id::DefId;
48 use rustc::hir::itemlikevisit::ItemLikeVisitor;
49 use rustc::hir::intravisit;
50 use rustc::ich::{Fingerprint, ATTR_DIRTY, ATTR_CLEAN, ATTR_DIRTY_METADATA,
51                  ATTR_CLEAN_METADATA};
52 use syntax::ast::{self, Attribute, NestedMetaItem};
53 use rustc_data_structures::fx::{FxHashSet, FxHashMap};
54 use rustc_data_structures::indexed_vec::IndexVec;
55 use syntax_pos::Span;
56 use rustc::ty::TyCtxt;
57
58 const LABEL: &'static str = "label";
59 const CFG: &'static str = "cfg";
60
61 pub fn check_dirty_clean_annotations<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
62                                                nodes: &IndexVec<DepNodeIndex, DepNode>,
63                                                dirty_inputs: &DirtyNodes) {
64     // can't add `#[rustc_dirty]` etc without opting in to this feature
65     if !tcx.sess.features.borrow().rustc_attrs {
66         return;
67     }
68
69     let _ignore = tcx.dep_graph.in_ignore();
70     let dirty_inputs: FxHashSet<DepNode> =
71         dirty_inputs.keys()
72                     .filter_map(|dep_node_index| {
73                         let dep_node = nodes[*dep_node_index];
74                         if dep_node.extract_def_id(tcx).is_some() {
75                             Some(dep_node)
76                         } else {
77                             None
78                         }
79                     })
80                     .collect();
81
82     let query = tcx.dep_graph.query();
83     debug!("query-nodes: {:?}", query.nodes());
84     let krate = tcx.hir.krate();
85     let mut dirty_clean_visitor = DirtyCleanVisitor {
86         tcx: tcx,
87         query: &query,
88         dirty_inputs: dirty_inputs,
89         checked_attrs: FxHashSet(),
90     };
91     krate.visit_all_item_likes(&mut dirty_clean_visitor);
92
93     let mut all_attrs = FindAllAttrs {
94         tcx: tcx,
95         attr_names: vec![ATTR_DIRTY, ATTR_CLEAN],
96         found_attrs: vec![],
97     };
98     intravisit::walk_crate(&mut all_attrs, krate);
99
100     // Note that we cannot use the existing "unused attribute"-infrastructure
101     // here, since that is running before trans. This is also the reason why
102     // all trans-specific attributes are `Whitelisted` in syntax::feature_gate.
103     all_attrs.report_unchecked_attrs(&dirty_clean_visitor.checked_attrs);
104 }
105
106 pub struct DirtyCleanVisitor<'a, 'tcx:'a> {
107     tcx: TyCtxt<'a, 'tcx, 'tcx>,
108     query: &'a DepGraphQuery,
109     dirty_inputs: FxHashSet<DepNode>,
110     checked_attrs: FxHashSet<ast::AttrId>,
111 }
112
113 impl<'a, 'tcx> DirtyCleanVisitor<'a, 'tcx> {
114     fn dep_node(&self, attr: &Attribute, def_id: DefId) -> DepNode {
115         let def_path_hash = self.tcx.def_path_hash(def_id);
116         for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
117             if item.check_name(LABEL) {
118                 let value = expect_associated_value(self.tcx, &item);
119                 match DepNode::from_label_string(&value.as_str(), def_path_hash) {
120                     Ok(dep_node) => return dep_node,
121                     Err(()) => {
122                         self.tcx.sess.span_fatal(
123                             item.span,
124                             &format!("dep-node label `{}` not recognized", value));
125                     }
126                 }
127             }
128         }
129
130         self.tcx.sess.span_fatal(attr.span, "no `label` found");
131     }
132
133     fn dep_node_str(&self, dep_node: &DepNode) -> String {
134         if let Some(def_id) = dep_node.extract_def_id(self.tcx) {
135             format!("{:?}({})",
136                     dep_node.kind,
137                     self.tcx.item_path_str(def_id))
138         } else {
139             format!("{:?}({:?})", dep_node.kind, dep_node.hash)
140         }
141     }
142
143     fn assert_dirty(&self, item_span: Span, dep_node: DepNode) {
144         debug!("assert_dirty({:?})", dep_node);
145
146         match dep_node.kind {
147             DepKind::Krate |
148             DepKind::Hir |
149             DepKind::HirBody => {
150                 // HIR nodes are inputs, so if we are asserting that the HIR node is
151                 // dirty, we check the dirty input set.
152                 if !self.dirty_inputs.contains(&dep_node) {
153                     let dep_node_str = self.dep_node_str(&dep_node);
154                     self.tcx.sess.span_err(
155                         item_span,
156                         &format!("`{}` not found in dirty set, but should be dirty",
157                                  dep_node_str));
158                 }
159             }
160             _ => {
161                 // Other kinds of nodes would be targets, so check if
162                 // the dep-graph contains the node.
163                 if self.query.contains_node(&dep_node) {
164                     let dep_node_str = self.dep_node_str(&dep_node);
165                     self.tcx.sess.span_err(
166                         item_span,
167                         &format!("`{}` found in dep graph, but should be dirty", dep_node_str));
168                 }
169             }
170         }
171     }
172
173     fn assert_clean(&self, item_span: Span, dep_node: DepNode) {
174         debug!("assert_clean({:?})", dep_node);
175
176         match dep_node.kind {
177             DepKind::Krate |
178             DepKind::Hir |
179             DepKind::HirBody => {
180                 // For HIR nodes, check the inputs.
181                 if self.dirty_inputs.contains(&dep_node) {
182                     let dep_node_str = self.dep_node_str(&dep_node);
183                     self.tcx.sess.span_err(
184                         item_span,
185                         &format!("`{}` found in dirty-node set, but should be clean",
186                                  dep_node_str));
187                 }
188             }
189             _ => {
190                 // Otherwise, check if the dep-node exists.
191                 if !self.query.contains_node(&dep_node) {
192                     let dep_node_str = self.dep_node_str(&dep_node);
193                     self.tcx.sess.span_err(
194                         item_span,
195                         &format!("`{}` not found in dep graph, but should be clean",
196                                  dep_node_str));
197                 }
198             }
199         }
200     }
201
202     fn check_item(&mut self, item_id: ast::NodeId, item_span: Span) {
203         let def_id = self.tcx.hir.local_def_id(item_id);
204         for attr in self.tcx.get_attrs(def_id).iter() {
205             if attr.check_name(ATTR_DIRTY) {
206                 if check_config(self.tcx, attr) {
207                     self.checked_attrs.insert(attr.id);
208                     self.assert_dirty(item_span, self.dep_node(attr, def_id));
209                 }
210             } else if attr.check_name(ATTR_CLEAN) {
211                 if check_config(self.tcx, attr) {
212                     self.checked_attrs.insert(attr.id);
213                     self.assert_clean(item_span, self.dep_node(attr, def_id));
214                 }
215             }
216         }
217     }
218 }
219
220 impl<'a, 'tcx> ItemLikeVisitor<'tcx> for DirtyCleanVisitor<'a, 'tcx> {
221     fn visit_item(&mut self, item: &'tcx hir::Item) {
222         self.check_item(item.id, item.span);
223     }
224
225     fn visit_trait_item(&mut self, item: &hir::TraitItem) {
226         self.check_item(item.id, item.span);
227     }
228
229     fn visit_impl_item(&mut self, item: &hir::ImplItem) {
230         self.check_item(item.id, item.span);
231     }
232 }
233
234 pub fn check_dirty_clean_metadata<'a, 'tcx>(
235     tcx: TyCtxt<'a, 'tcx, 'tcx>,
236     prev_metadata_hashes: &FxHashMap<DefId, Fingerprint>,
237     current_metadata_hashes: &FxHashMap<DefId, Fingerprint>)
238 {
239     if !tcx.sess.opts.debugging_opts.query_dep_graph {
240         return;
241     }
242
243     tcx.dep_graph.with_ignore(||{
244         let krate = tcx.hir.krate();
245         let mut dirty_clean_visitor = DirtyCleanMetadataVisitor {
246             tcx: tcx,
247             prev_metadata_hashes: prev_metadata_hashes,
248             current_metadata_hashes: current_metadata_hashes,
249             checked_attrs: FxHashSet(),
250         };
251         intravisit::walk_crate(&mut dirty_clean_visitor, krate);
252
253         let mut all_attrs = FindAllAttrs {
254             tcx: tcx,
255             attr_names: vec![ATTR_DIRTY_METADATA, ATTR_CLEAN_METADATA],
256             found_attrs: vec![],
257         };
258         intravisit::walk_crate(&mut all_attrs, krate);
259
260         // Note that we cannot use the existing "unused attribute"-infrastructure
261         // here, since that is running before trans. This is also the reason why
262         // all trans-specific attributes are `Whitelisted` in syntax::feature_gate.
263         all_attrs.report_unchecked_attrs(&dirty_clean_visitor.checked_attrs);
264     });
265 }
266
267 pub struct DirtyCleanMetadataVisitor<'a, 'tcx: 'a, 'm> {
268     tcx: TyCtxt<'a, 'tcx, 'tcx>,
269     prev_metadata_hashes: &'m FxHashMap<DefId, Fingerprint>,
270     current_metadata_hashes: &'m FxHashMap<DefId, Fingerprint>,
271     checked_attrs: FxHashSet<ast::AttrId>,
272 }
273
274 impl<'a, 'tcx, 'm> intravisit::Visitor<'tcx> for DirtyCleanMetadataVisitor<'a, 'tcx, 'm> {
275
276     fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
277         intravisit::NestedVisitorMap::All(&self.tcx.hir)
278     }
279
280     fn visit_item(&mut self, item: &'tcx hir::Item) {
281         self.check_item(item.id, item.span);
282         intravisit::walk_item(self, item);
283     }
284
285     fn visit_variant(&mut self,
286                      variant: &'tcx hir::Variant,
287                      generics: &'tcx hir::Generics,
288                      parent_id: ast::NodeId) {
289         if let Some(e) = variant.node.disr_expr {
290             self.check_item(e.node_id, variant.span);
291         }
292
293         intravisit::walk_variant(self, variant, generics, parent_id);
294     }
295
296     fn visit_variant_data(&mut self,
297                           variant_data: &'tcx hir::VariantData,
298                           _: ast::Name,
299                           _: &'tcx hir::Generics,
300                           _parent_id: ast::NodeId,
301                           span: Span) {
302         if self.tcx.hir.find(variant_data.id()).is_some() {
303             // VariantData that represent structs or tuples don't have a
304             // separate entry in the HIR map and checking them would error,
305             // so only check if this is an enum or union variant.
306             self.check_item(variant_data.id(), span);
307         }
308
309         intravisit::walk_struct_def(self, variant_data);
310     }
311
312     fn visit_trait_item(&mut self, item: &'tcx hir::TraitItem) {
313         self.check_item(item.id, item.span);
314         intravisit::walk_trait_item(self, item);
315     }
316
317     fn visit_impl_item(&mut self, item: &'tcx hir::ImplItem) {
318         self.check_item(item.id, item.span);
319         intravisit::walk_impl_item(self, item);
320     }
321
322     fn visit_foreign_item(&mut self, i: &'tcx hir::ForeignItem) {
323         self.check_item(i.id, i.span);
324         intravisit::walk_foreign_item(self, i);
325     }
326
327     fn visit_struct_field(&mut self, s: &'tcx hir::StructField) {
328         self.check_item(s.id, s.span);
329         intravisit::walk_struct_field(self, s);
330     }
331 }
332
333 impl<'a, 'tcx, 'm> DirtyCleanMetadataVisitor<'a, 'tcx, 'm> {
334
335     fn check_item(&mut self, item_id: ast::NodeId, item_span: Span) {
336         let def_id = self.tcx.hir.local_def_id(item_id);
337
338         for attr in self.tcx.get_attrs(def_id).iter() {
339             if attr.check_name(ATTR_DIRTY_METADATA) {
340                 if check_config(self.tcx, attr) {
341                     if self.checked_attrs.insert(attr.id) {
342                         self.assert_state(false, def_id, item_span);
343                     }
344                 }
345             } else if attr.check_name(ATTR_CLEAN_METADATA) {
346                 if check_config(self.tcx, attr) {
347                     if self.checked_attrs.insert(attr.id) {
348                         self.assert_state(true, def_id, item_span);
349                     }
350                 }
351             }
352         }
353     }
354
355     fn assert_state(&self, should_be_clean: bool, def_id: DefId, span: Span) {
356         let item_path = self.tcx.item_path_str(def_id);
357         debug!("assert_state({})", item_path);
358
359         if let Some(&prev_hash) = self.prev_metadata_hashes.get(&def_id) {
360             let hashes_are_equal = prev_hash == self.current_metadata_hashes[&def_id];
361
362             if should_be_clean && !hashes_are_equal {
363                 self.tcx.sess.span_err(
364                         span,
365                         &format!("Metadata hash of `{}` is dirty, but should be clean",
366                                  item_path));
367             }
368
369             let should_be_dirty = !should_be_clean;
370             if should_be_dirty && hashes_are_equal {
371                 self.tcx.sess.span_err(
372                         span,
373                         &format!("Metadata hash of `{}` is clean, but should be dirty",
374                                  item_path));
375             }
376         } else {
377             self.tcx.sess.span_err(
378                         span,
379                         &format!("Could not find previous metadata hash of `{}`",
380                                  item_path));
381         }
382     }
383 }
384
385 /// Given a `#[rustc_dirty]` or `#[rustc_clean]` attribute, scan
386 /// for a `cfg="foo"` attribute and check whether we have a cfg
387 /// flag called `foo`.
388 fn check_config(tcx: TyCtxt, attr: &Attribute) -> bool {
389     debug!("check_config(attr={:?})", attr);
390     let config = &tcx.sess.parse_sess.config;
391     debug!("check_config: config={:?}", config);
392     for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
393         if item.check_name(CFG) {
394             let value = expect_associated_value(tcx, &item);
395             debug!("check_config: searching for cfg {:?}", value);
396             return config.contains(&(value, None));
397         }
398     }
399
400     tcx.sess.span_fatal(
401         attr.span,
402         "no cfg attribute");
403 }
404
405 fn expect_associated_value(tcx: TyCtxt, item: &NestedMetaItem) -> ast::Name {
406     if let Some(value) = item.value_str() {
407         value
408     } else {
409         let msg = if let Some(name) = item.name() {
410             format!("associated value expected for `{}`", name)
411         } else {
412             "expected an associated value".to_string()
413         };
414
415         tcx.sess.span_fatal(item.span, &msg);
416     }
417 }
418
419
420 // A visitor that collects all #[rustc_dirty]/#[rustc_clean] attributes from
421 // the HIR. It is used to verfiy that we really ran checks for all annotated
422 // nodes.
423 pub struct FindAllAttrs<'a, 'tcx:'a> {
424     tcx: TyCtxt<'a, 'tcx, 'tcx>,
425     attr_names: Vec<&'static str>,
426     found_attrs: Vec<&'tcx Attribute>,
427 }
428
429 impl<'a, 'tcx> FindAllAttrs<'a, 'tcx> {
430
431     fn is_active_attr(&mut self, attr: &Attribute) -> bool {
432         for attr_name in &self.attr_names {
433             if attr.check_name(attr_name) && check_config(self.tcx, attr) {
434                 return true;
435             }
436         }
437
438         false
439     }
440
441     fn report_unchecked_attrs(&self, checked_attrs: &FxHashSet<ast::AttrId>) {
442         for attr in &self.found_attrs {
443             if !checked_attrs.contains(&attr.id) {
444                 self.tcx.sess.span_err(attr.span, &format!("found unchecked \
445                     #[rustc_dirty]/#[rustc_clean] attribute"));
446             }
447         }
448     }
449 }
450
451 impl<'a, 'tcx> intravisit::Visitor<'tcx> for FindAllAttrs<'a, 'tcx> {
452     fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
453         intravisit::NestedVisitorMap::All(&self.tcx.hir)
454     }
455
456     fn visit_attribute(&mut self, attr: &'tcx Attribute) {
457         if self.is_active_attr(attr) {
458             self.found_attrs.push(attr);
459         }
460     }
461 }