]> git.lizzy.rs Git - rust.git/blob - src/librustc_incremental/persist/dirty_clean.rs
Rollup merge of #41141 - michaelwoerister:direct-metadata-ich-final, r=nikomatsakis
[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>(
219     tcx: TyCtxt<'a, 'tcx, 'tcx>,
220     prev_metadata_hashes: &FxHashMap<DefId, Fingerprint>,
221     current_metadata_hashes: &FxHashMap<DefId, Fingerprint>)
222 {
223     if !tcx.sess.opts.debugging_opts.query_dep_graph {
224         return;
225     }
226
227     tcx.dep_graph.with_ignore(||{
228         let krate = tcx.hir.krate();
229         let mut dirty_clean_visitor = DirtyCleanMetadataVisitor {
230             tcx: tcx,
231             prev_metadata_hashes: prev_metadata_hashes,
232             current_metadata_hashes: current_metadata_hashes,
233             checked_attrs: FxHashSet(),
234         };
235         intravisit::walk_crate(&mut dirty_clean_visitor, krate);
236
237         let mut all_attrs = FindAllAttrs {
238             tcx: tcx,
239             attr_names: vec![ATTR_DIRTY_METADATA, ATTR_CLEAN_METADATA],
240             found_attrs: vec![],
241         };
242         intravisit::walk_crate(&mut all_attrs, krate);
243
244         // Note that we cannot use the existing "unused attribute"-infrastructure
245         // here, since that is running before trans. This is also the reason why
246         // all trans-specific attributes are `Whitelisted` in syntax::feature_gate.
247         all_attrs.report_unchecked_attrs(&dirty_clean_visitor.checked_attrs);
248     });
249 }
250
251 pub struct DirtyCleanMetadataVisitor<'a, 'tcx: 'a, 'm> {
252     tcx: TyCtxt<'a, 'tcx, 'tcx>,
253     prev_metadata_hashes: &'m FxHashMap<DefId, Fingerprint>,
254     current_metadata_hashes: &'m FxHashMap<DefId, Fingerprint>,
255     checked_attrs: FxHashSet<ast::AttrId>,
256 }
257
258 impl<'a, 'tcx, 'm> intravisit::Visitor<'tcx> for DirtyCleanMetadataVisitor<'a, 'tcx, 'm> {
259
260     fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
261         intravisit::NestedVisitorMap::All(&self.tcx.hir)
262     }
263
264     fn visit_item(&mut self, item: &'tcx hir::Item) {
265         self.check_item(item.id, item.span);
266         intravisit::walk_item(self, item);
267     }
268
269     fn visit_variant_data(&mut self,
270                           variant_data: &'tcx hir::VariantData,
271                           _: ast::Name,
272                           _: &'tcx hir::Generics,
273                           _parent_id: ast::NodeId,
274                           span: Span) {
275         if self.tcx.hir.find(variant_data.id()).is_some() {
276             // VariantData that represent structs or tuples don't have a
277             // separate entry in the HIR map and checking them would error,
278             // so only check if this is an enum or union variant.
279             self.check_item(variant_data.id(), span);
280         }
281
282         intravisit::walk_struct_def(self, variant_data);
283     }
284
285     fn visit_trait_item(&mut self, item: &'tcx hir::TraitItem) {
286         self.check_item(item.id, item.span);
287         intravisit::walk_trait_item(self, item);
288     }
289
290     fn visit_impl_item(&mut self, item: &'tcx hir::ImplItem) {
291         self.check_item(item.id, item.span);
292         intravisit::walk_impl_item(self, item);
293     }
294
295     fn visit_foreign_item(&mut self, i: &'tcx hir::ForeignItem) {
296         self.check_item(i.id, i.span);
297         intravisit::walk_foreign_item(self, i);
298     }
299
300     fn visit_struct_field(&mut self, s: &'tcx hir::StructField) {
301         self.check_item(s.id, s.span);
302         intravisit::walk_struct_field(self, s);
303     }
304 }
305
306 impl<'a, 'tcx, 'm> DirtyCleanMetadataVisitor<'a, 'tcx, 'm> {
307
308     fn check_item(&mut self, item_id: ast::NodeId, item_span: Span) {
309         let def_id = self.tcx.hir.local_def_id(item_id);
310
311         for attr in self.tcx.get_attrs(def_id).iter() {
312             if attr.check_name(ATTR_DIRTY_METADATA) {
313                 if check_config(self.tcx, attr) {
314                     if self.checked_attrs.insert(attr.id) {
315                         self.assert_state(false, def_id, item_span);
316                     }
317                 }
318             } else if attr.check_name(ATTR_CLEAN_METADATA) {
319                 if check_config(self.tcx, attr) {
320                     if self.checked_attrs.insert(attr.id) {
321                         self.assert_state(true, def_id, item_span);
322                     }
323                 }
324             }
325         }
326     }
327
328     fn assert_state(&self, should_be_clean: bool, def_id: DefId, span: Span) {
329         let item_path = self.tcx.item_path_str(def_id);
330         debug!("assert_state({})", item_path);
331
332         if let Some(&prev_hash) = self.prev_metadata_hashes.get(&def_id) {
333             let hashes_are_equal = prev_hash == self.current_metadata_hashes[&def_id];
334
335             if should_be_clean && !hashes_are_equal {
336                 self.tcx.sess.span_err(
337                         span,
338                         &format!("Metadata hash of `{}` is dirty, but should be clean",
339                                  item_path));
340             }
341
342             let should_be_dirty = !should_be_clean;
343             if should_be_dirty && hashes_are_equal {
344                 self.tcx.sess.span_err(
345                         span,
346                         &format!("Metadata hash of `{}` is clean, but should be dirty",
347                                  item_path));
348             }
349         } else {
350             self.tcx.sess.span_err(
351                         span,
352                         &format!("Could not find previous metadata hash of `{}`",
353                                  item_path));
354         }
355     }
356 }
357
358 /// Given a `#[rustc_dirty]` or `#[rustc_clean]` attribute, scan
359 /// for a `cfg="foo"` attribute and check whether we have a cfg
360 /// flag called `foo`.
361 fn check_config(tcx: TyCtxt, attr: &Attribute) -> bool {
362     debug!("check_config(attr={:?})", attr);
363     let config = &tcx.sess.parse_sess.config;
364     debug!("check_config: config={:?}", config);
365     for item in attr.meta_item_list().unwrap_or_else(Vec::new) {
366         if item.check_name(CFG) {
367             let value = expect_associated_value(tcx, &item);
368             debug!("check_config: searching for cfg {:?}", value);
369             return config.contains(&(value, None));
370         }
371     }
372
373     tcx.sess.span_fatal(
374         attr.span,
375         &format!("no cfg attribute"));
376 }
377
378 fn expect_associated_value(tcx: TyCtxt, item: &NestedMetaItem) -> ast::Name {
379     if let Some(value) = item.value_str() {
380         value
381     } else {
382         let msg = if let Some(name) = item.name() {
383             format!("associated value expected for `{}`", name)
384         } else {
385             "expected an associated value".to_string()
386         };
387
388         tcx.sess.span_fatal(item.span, &msg);
389     }
390 }
391
392
393 // A visitor that collects all #[rustc_dirty]/#[rustc_clean] attributes from
394 // the HIR. It is used to verfiy that we really ran checks for all annotated
395 // nodes.
396 pub struct FindAllAttrs<'a, 'tcx:'a> {
397     tcx: TyCtxt<'a, 'tcx, 'tcx>,
398     attr_names: Vec<&'static str>,
399     found_attrs: Vec<&'tcx Attribute>,
400 }
401
402 impl<'a, 'tcx> FindAllAttrs<'a, 'tcx> {
403
404     fn is_active_attr(&mut self, attr: &Attribute) -> bool {
405         for attr_name in &self.attr_names {
406             if attr.check_name(attr_name) && check_config(self.tcx, attr) {
407                 return true;
408             }
409         }
410
411         false
412     }
413
414     fn report_unchecked_attrs(&self, checked_attrs: &FxHashSet<ast::AttrId>) {
415         for attr in &self.found_attrs {
416             if !checked_attrs.contains(&attr.id) {
417                 self.tcx.sess.span_err(attr.span, &format!("found unchecked \
418                     #[rustc_dirty]/#[rustc_clean] attribute"));
419             }
420         }
421     }
422 }
423
424 impl<'a, 'tcx> intravisit::Visitor<'tcx> for FindAllAttrs<'a, 'tcx> {
425     fn nested_visit_map<'this>(&'this mut self) -> intravisit::NestedVisitorMap<'this, 'tcx> {
426         intravisit::NestedVisitorMap::All(&self.tcx.hir)
427     }
428
429     fn visit_attribute(&mut self, attr: &'tcx Attribute) {
430         if self.is_active_attr(attr) {
431             self.found_attrs.push(attr);
432         }
433     }
434 }