]> git.lizzy.rs Git - rust.git/blob - src/librustc_incremental/persist/dirty_clean.rs
Rollup merge of #41087 - estebank:tuple-float-index, r=arielb1
[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::directory::RetracedDefIdDirectory;
44 use super::load::DirtyNodes;
45 use rustc::dep_graph::{DepGraphQuery, DepNode};
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 syntax_pos::Span;
55 use rustc::ty::TyCtxt;
56
57 const LABEL: &'static str = "label";
58 const CFG: &'static str = "cfg";
59
60 pub fn check_dirty_clean_annotations<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
61                                                dirty_inputs: &DirtyNodes,
62                                                retraced: &RetracedDefIdDirectory) {
63     // can't add `#[rustc_dirty]` etc without opting in to this feature
64     if !tcx.sess.features.borrow().rustc_attrs {
65         return;
66     }
67
68     let _ignore = tcx.dep_graph.in_ignore();
69     let dirty_inputs: FxHashSet<DepNode<DefId>> =
70         dirty_inputs.keys()
71                     .filter_map(|d| retraced.map(d))
72                     .collect();
73     let query = tcx.dep_graph.query();
74     debug!("query-nodes: {:?}", query.nodes());
75     let krate = tcx.hir.krate();
76     let mut dirty_clean_visitor = DirtyCleanVisitor {
77         tcx: tcx,
78         query: &query,
79         dirty_inputs: dirty_inputs,
80         checked_attrs: FxHashSet(),
81     };
82     krate.visit_all_item_likes(&mut dirty_clean_visitor);
83
84     let mut all_attrs = FindAllAttrs {
85         tcx: tcx,
86         attr_names: vec![ATTR_DIRTY, ATTR_CLEAN],
87         found_attrs: vec![],
88     };
89     intravisit::walk_crate(&mut all_attrs, krate);
90
91     // Note that we cannot use the existing "unused attribute"-infrastructure
92     // here, since that is running before trans. This is also the reason why
93     // all trans-specific attributes are `Whitelisted` in syntax::feature_gate.
94     all_attrs.report_unchecked_attrs(&dirty_clean_visitor.checked_attrs);
95 }
96
97 pub struct DirtyCleanVisitor<'a, 'tcx:'a> {
98     tcx: TyCtxt<'a, 'tcx, 'tcx>,
99     query: &'a DepGraphQuery<DefId>,
100     dirty_inputs: FxHashSet<DepNode<DefId>>,
101     checked_attrs: FxHashSet<ast::AttrId>,
102 }
103
104 impl<'a, 'tcx> DirtyCleanVisitor<'a, 'tcx> {
105     fn dep_node(&self, attr: &Attribute, def_id: DefId) -> DepNode<DefId> {
106         for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
107             if item.check_name(LABEL) {
108                 let value = expect_associated_value(self.tcx, &item);
109                 match DepNode::from_label_string(&value.as_str(), def_id) {
110                     Ok(def_id) => return def_id,
111                     Err(()) => {
112                         self.tcx.sess.span_fatal(
113                             item.span,
114                             &format!("dep-node label `{}` not recognized", value));
115                     }
116                 }
117             }
118         }
119
120         self.tcx.sess.span_fatal(attr.span, "no `label` found");
121     }
122
123     fn dep_node_str(&self, dep_node: &DepNode<DefId>) -> DepNode<String> {
124         dep_node.map_def(|&def_id| Some(self.tcx.item_path_str(def_id))).unwrap()
125     }
126
127     fn assert_dirty(&self, item_span: Span, dep_node: DepNode<DefId>) {
128         debug!("assert_dirty({:?})", dep_node);
129
130         match dep_node {
131             DepNode::Krate |
132             DepNode::Hir(_) |
133             DepNode::HirBody(_) => {
134                 // HIR nodes are inputs, so if we are asserting that the HIR node is
135                 // dirty, we check the dirty input set.
136                 if !self.dirty_inputs.contains(&dep_node) {
137                     let dep_node_str = self.dep_node_str(&dep_node);
138                     self.tcx.sess.span_err(
139                         item_span,
140                         &format!("`{:?}` not found in dirty set, but should be dirty",
141                                  dep_node_str));
142                 }
143             }
144             _ => {
145                 // Other kinds of nodes would be targets, so check if
146                 // the dep-graph contains the node.
147                 if self.query.contains_node(&dep_node) {
148                     let dep_node_str = self.dep_node_str(&dep_node);
149                     self.tcx.sess.span_err(
150                         item_span,
151                         &format!("`{:?}` found in dep graph, but should be dirty", dep_node_str));
152                 }
153             }
154         }
155     }
156
157     fn assert_clean(&self, item_span: Span, dep_node: DepNode<DefId>) {
158         debug!("assert_clean({:?})", dep_node);
159
160         match dep_node {
161             DepNode::Krate |
162             DepNode::Hir(_) |
163             DepNode::HirBody(_) => {
164                 // For HIR nodes, check the inputs.
165                 if self.dirty_inputs.contains(&dep_node) {
166                     let dep_node_str = self.dep_node_str(&dep_node);
167                     self.tcx.sess.span_err(
168                         item_span,
169                         &format!("`{:?}` found in dirty-node set, but should be clean",
170                                  dep_node_str));
171                 }
172             }
173             _ => {
174                 // Otherwise, check if the dep-node exists.
175                 if !self.query.contains_node(&dep_node) {
176                     let dep_node_str = self.dep_node_str(&dep_node);
177                     self.tcx.sess.span_err(
178                         item_span,
179                         &format!("`{:?}` not found in dep graph, but should be clean",
180                                  dep_node_str));
181                 }
182             }
183         }
184     }
185
186     fn check_item(&mut self, item_id: ast::NodeId, item_span: Span) {
187         let def_id = self.tcx.hir.local_def_id(item_id);
188         for attr in self.tcx.get_attrs(def_id).iter() {
189             if attr.check_name(ATTR_DIRTY) {
190                 if check_config(self.tcx, attr) {
191                     self.checked_attrs.insert(attr.id);
192                     self.assert_dirty(item_span, self.dep_node(attr, def_id));
193                 }
194             } else if attr.check_name(ATTR_CLEAN) {
195                 if check_config(self.tcx, attr) {
196                     self.checked_attrs.insert(attr.id);
197                     self.assert_clean(item_span, self.dep_node(attr, def_id));
198                 }
199             }
200         }
201     }
202 }
203
204 impl<'a, 'tcx> ItemLikeVisitor<'tcx> for DirtyCleanVisitor<'a, 'tcx> {
205     fn visit_item(&mut self, item: &'tcx hir::Item) {
206         self.check_item(item.id, item.span);
207     }
208
209     fn visit_trait_item(&mut self, item: &hir::TraitItem) {
210         self.check_item(item.id, item.span);
211     }
212
213     fn visit_impl_item(&mut self, item: &hir::ImplItem) {
214         self.check_item(item.id, item.span);
215     }
216 }
217
218 pub fn check_dirty_clean_metadata<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
219                                   prev_metadata_hashes: &FxHashMap<DefId, Fingerprint>,
220                                   current_metadata_hashes: &FxHashMap<DefId, Fingerprint>) {
221     if !tcx.sess.opts.debugging_opts.query_dep_graph {
222         return;
223     }
224
225     tcx.dep_graph.with_ignore(||{
226         let krate = tcx.hir.krate();
227         let mut dirty_clean_visitor = DirtyCleanMetadataVisitor {
228             tcx: tcx,
229             prev_metadata_hashes: prev_metadata_hashes,
230             current_metadata_hashes: current_metadata_hashes,
231             checked_attrs: FxHashSet(),
232         };
233         krate.visit_all_item_likes(&mut dirty_clean_visitor);
234
235         let mut all_attrs = FindAllAttrs {
236             tcx: tcx,
237             attr_names: vec![ATTR_DIRTY_METADATA, ATTR_CLEAN_METADATA],
238             found_attrs: vec![],
239         };
240         intravisit::walk_crate(&mut all_attrs, krate);
241
242         // Note that we cannot use the existing "unused attribute"-infrastructure
243         // here, since that is running before trans. This is also the reason why
244         // all trans-specific attributes are `Whitelisted` in syntax::feature_gate.
245         all_attrs.report_unchecked_attrs(&dirty_clean_visitor.checked_attrs);
246     });
247 }
248
249 pub struct DirtyCleanMetadataVisitor<'a, 'tcx:'a, 'm> {
250     tcx: TyCtxt<'a, 'tcx, 'tcx>,
251     prev_metadata_hashes: &'m FxHashMap<DefId, Fingerprint>,
252     current_metadata_hashes: &'m FxHashMap<DefId, Fingerprint>,
253     checked_attrs: FxHashSet<ast::AttrId>,
254 }
255
256 impl<'a, 'tcx, 'm> ItemLikeVisitor<'tcx> for DirtyCleanMetadataVisitor<'a, 'tcx, 'm> {
257     fn visit_item(&mut self, item: &'tcx hir::Item) {
258         self.check_item(item.id, item.span);
259
260         if let hir::ItemEnum(ref def, _) = item.node {
261             for v in &def.variants {
262                 self.check_item(v.node.data.id(), v.span);
263             }
264         }
265     }
266
267     fn visit_trait_item(&mut self, item: &hir::TraitItem) {
268         self.check_item(item.id, item.span);
269     }
270
271     fn visit_impl_item(&mut self, item: &hir::ImplItem) {
272         self.check_item(item.id, item.span);
273     }
274 }
275
276 impl<'a, 'tcx, 'm> DirtyCleanMetadataVisitor<'a, 'tcx, 'm> {
277
278     fn check_item(&mut self, item_id: ast::NodeId, item_span: Span) {
279         let def_id = self.tcx.hir.local_def_id(item_id);
280
281         for attr in self.tcx.get_attrs(def_id).iter() {
282             if attr.check_name(ATTR_DIRTY_METADATA) {
283                 if check_config(self.tcx, attr) {
284                     self.checked_attrs.insert(attr.id);
285                     self.assert_state(false, def_id, item_span);
286                 }
287             } else if attr.check_name(ATTR_CLEAN_METADATA) {
288                 if check_config(self.tcx, attr) {
289                     self.checked_attrs.insert(attr.id);
290                     self.assert_state(true, def_id, item_span);
291                 }
292             }
293         }
294     }
295
296     fn assert_state(&self, should_be_clean: bool, def_id: DefId, span: Span) {
297         let item_path = self.tcx.item_path_str(def_id);
298         debug!("assert_state({})", item_path);
299
300         if let Some(&prev_hash) = self.prev_metadata_hashes.get(&def_id) {
301             let hashes_are_equal = prev_hash == self.current_metadata_hashes[&def_id];
302
303             if should_be_clean && !hashes_are_equal {
304                 self.tcx.sess.span_err(
305                         span,
306                         &format!("Metadata hash of `{}` is dirty, but should be clean",
307                                  item_path));
308             }
309
310             let should_be_dirty = !should_be_clean;
311             if should_be_dirty && hashes_are_equal {
312                 self.tcx.sess.span_err(
313                         span,
314                         &format!("Metadata hash of `{}` is clean, but should be dirty",
315                                  item_path));
316             }
317         } else {
318             self.tcx.sess.span_err(
319                         span,
320                         &format!("Could not find previous metadata hash of `{}`",
321                                  item_path));
322         }
323     }
324 }
325
326 /// Given a `#[rustc_dirty]` or `#[rustc_clean]` attribute, scan
327 /// for a `cfg="foo"` attribute and check whether we have a cfg
328 /// flag called `foo`.
329 fn check_config(tcx: TyCtxt, attr: &Attribute) -> bool {
330     debug!("check_config(attr={:?})", attr);
331     let config = &tcx.sess.parse_sess.config;
332     debug!("check_config: config={:?}", config);
333     for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
334         if item.check_name(CFG) {
335             let value = expect_associated_value(tcx, &item);
336             debug!("check_config: searching for cfg {:?}", value);
337             return config.contains(&(value, None));
338         }
339     }
340
341     tcx.sess.span_fatal(
342         attr.span,
343         &format!("no cfg attribute"));
344 }
345
346 fn expect_associated_value(tcx: TyCtxt, item: &NestedMetaItem) -> ast::Name {
347     if let Some(value) = item.value_str() {
348         value
349     } else {
350         let msg = if let Some(name) = item.name() {
351             format!("associated value expected for `{}`", name)
352         } else {
353             "expected an associated value".to_string()
354         };
355
356         tcx.sess.span_fatal(item.span, &msg);
357     }
358 }
359
360
361 // A visitor that collects all #[rustc_dirty]/#[rustc_clean] attributes from
362 // the HIR. It is used to verfiy that we really ran checks for all annotated
363 // nodes.
364 pub struct FindAllAttrs<'a, 'tcx:'a> {
365     tcx: TyCtxt<'a, 'tcx, 'tcx>,
366     attr_names: Vec<&'static str>,
367     found_attrs: Vec<&'tcx Attribute>,
368 }
369
370 impl<'a, 'tcx> FindAllAttrs<'a, 'tcx> {
371
372     fn is_active_attr(&mut self, attr: &Attribute) -> bool {
373         for attr_name in &self.attr_names {
374             if attr.check_name(attr_name) && check_config(self.tcx, attr) {
375                 return true;
376             }
377         }
378
379         false
380     }
381
382     fn report_unchecked_attrs(&self, checked_attrs: &FxHashSet<ast::AttrId>) {
383         for attr in &self.found_attrs {
384             if !checked_attrs.contains(&attr.id) {
385                 self.tcx.sess.span_err(attr.span, &format!("found unchecked \
386                     #[rustc_dirty]/#[rustc_clean] attribute"));
387             }
388         }
389     }
390 }
391
392 impl<'a, 'tcx> intravisit::Visitor<'tcx> for FindAllAttrs<'a, 'tcx> {
393     fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
394         intravisit::NestedVisitorMap::All(&self.tcx.hir)
395     }
396
397     fn visit_attribute(&mut self, attr: &'tcx Attribute) {
398         if self.is_active_attr(attr) {
399             self.found_attrs.push(attr);
400         }
401     }
402 }