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