From 6a2666d5b01d92d5e33487ea8c4aba9e00359a80 Mon Sep 17 00:00:00 2001 From: Michael Woerister Date: Tue, 30 Aug 2016 16:49:54 -0400 Subject: [PATCH] ICH: Add ability to test the ICH of exported metadata items. --- src/librustc_incremental/assert_dep_graph.rs | 10 +- src/librustc_incremental/calculate_svh/mod.rs | 40 ++- .../calculate_svh/svh_visitor.rs | 12 +- src/librustc_incremental/lib.rs | 7 + src/librustc_incremental/persist/data.rs | 13 + src/librustc_incremental/persist/directory.rs | 1 - .../persist/dirty_clean.rs | 192 ++++++++++---- src/librustc_incremental/persist/load.rs | 56 ++++- src/librustc_incremental/persist/save.rs | 79 ++++-- src/libsyntax/feature_gate.rs | 10 + src/test/incremental/hashes/struct_defs.rs | 238 ++++++++++++++++++ src/tools/compiletest/src/header.rs | 50 ++-- src/tools/compiletest/src/runtest.rs | 29 ++- 13 files changed, 622 insertions(+), 115 deletions(-) create mode 100644 src/test/incremental/hashes/struct_defs.rs diff --git a/src/librustc_incremental/assert_dep_graph.rs b/src/librustc_incremental/assert_dep_graph.rs index b28454cddb2..28aab1fdd41 100644 --- a/src/librustc_incremental/assert_dep_graph.rs +++ b/src/librustc_incremental/assert_dep_graph.rs @@ -59,9 +59,7 @@ use std::io::Write; use syntax::ast; use syntax::parse::token::InternedString; use syntax_pos::Span; - -const IF_THIS_CHANGED: &'static str = "rustc_if_this_changed"; -const THEN_THIS_WOULD_NEED: &'static str = "rustc_then_this_would_need"; +use {ATTR_IF_THIS_CHANGED, ATTR_THEN_THIS_WOULD_NEED}; pub fn assert_dep_graph<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) { let _ignore = tcx.dep_graph.in_ignore(); @@ -91,7 +89,7 @@ pub fn assert_dep_graph<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) { assert!(tcx.sess.opts.debugging_opts.query_dep_graph, "cannot use the `#[{}]` or `#[{}]` annotations \ without supplying `-Z query-dep-graph`", - IF_THIS_CHANGED, THEN_THIS_WOULD_NEED); + ATTR_IF_THIS_CHANGED, ATTR_THEN_THIS_WOULD_NEED); } // Check paths. @@ -125,7 +123,7 @@ impl<'a, 'tcx> IfThisChanged<'a, 'tcx> { fn process_attrs(&mut self, node_id: ast::NodeId, attrs: &[ast::Attribute]) { let def_id = self.tcx.map.local_def_id(node_id); for attr in attrs { - if attr.check_name(IF_THIS_CHANGED) { + if attr.check_name(ATTR_IF_THIS_CHANGED) { let dep_node_interned = self.argument(attr); let dep_node = match dep_node_interned { None => DepNode::Hir(def_id), @@ -141,7 +139,7 @@ impl<'a, 'tcx> IfThisChanged<'a, 'tcx> { } }; self.if_this_changed.push((attr.span, def_id, dep_node)); - } else if attr.check_name(THEN_THIS_WOULD_NEED) { + } else if attr.check_name(ATTR_THEN_THIS_WOULD_NEED) { let dep_node_interned = self.argument(attr); let dep_node = match dep_node_interned { Some(ref n) => { diff --git a/src/librustc_incremental/calculate_svh/mod.rs b/src/librustc_incremental/calculate_svh/mod.rs index c54fe211451..92ed2637c3d 100644 --- a/src/librustc_incremental/calculate_svh/mod.rs +++ b/src/librustc_incremental/calculate_svh/mod.rs @@ -28,6 +28,7 @@ //! at the beginning. use syntax::ast; +use std::cell::RefCell; use std::hash::{Hash, SipHasher, Hasher}; use rustc::dep_graph::DepNode; use rustc::hir; @@ -46,7 +47,42 @@ mod def_path_hash; mod svh_visitor; mod caching_codemap_view; -pub type IncrementalHashesMap = FnvHashMap, u64>; +pub struct IncrementalHashesMap { + hashes: FnvHashMap, u64>, + + // These are the metadata hashes for the current crate as they were stored + // during the last compilation session. They are only loaded if + // -Z query-dep-graph was specified and are needed for auto-tests using + // the #[rustc_metadata_dirty] and #[rustc_metadata_clean] attributes to + // check whether some metadata hash has changed in between two revisions. + pub prev_metadata_hashes: RefCell>, +} + +impl IncrementalHashesMap { + pub fn new() -> IncrementalHashesMap { + IncrementalHashesMap { + hashes: FnvHashMap(), + prev_metadata_hashes: RefCell::new(FnvHashMap()), + } + } + + pub fn insert(&mut self, k: DepNode, v: u64) -> Option { + self.hashes.insert(k, v) + } + + pub fn iter<'a>(&'a self) -> ::std::collections::hash_map::Iter<'a, DepNode, u64> { + self.hashes.iter() + } +} + +impl<'a> ::std::ops::Index<&'a DepNode> for IncrementalHashesMap { + type Output = u64; + + fn index(&self, index: &'a DepNode) -> &u64 { + &self.hashes[index] + } +} + pub fn compute_incremental_hashes_map<'a, 'tcx: 'a>(tcx: TyCtxt<'a, 'tcx, 'tcx>) -> IncrementalHashesMap { @@ -55,7 +91,7 @@ pub fn compute_incremental_hashes_map<'a, 'tcx: 'a>(tcx: TyCtxt<'a, 'tcx, 'tcx>) let hash_spans = tcx.sess.opts.debuginfo != NoDebugInfo; let mut visitor = HashItemsVisitor { tcx: tcx, - hashes: FnvHashMap(), + hashes: IncrementalHashesMap::new(), def_path_hashes: DefPathHashes::new(tcx), codemap: CachingCodemapView::new(tcx), hash_spans: hash_spans, diff --git a/src/librustc_incremental/calculate_svh/svh_visitor.rs b/src/librustc_incremental/calculate_svh/svh_visitor.rs index 9950f470a82..55fe5fc1e34 100644 --- a/src/librustc_incremental/calculate_svh/svh_visitor.rs +++ b/src/librustc_incremental/calculate_svh/svh_visitor.rs @@ -30,9 +30,15 @@ use std::hash::{Hash, SipHasher}; use super::def_path_hash::DefPathHashes; use super::caching_codemap_view::CachingCodemapView; -const IGNORED_ATTRIBUTES: &'static [&'static str] = &["cfg", - "rustc_clean", - "rustc_dirty"]; +const IGNORED_ATTRIBUTES: &'static [&'static str] = &[ + "cfg", + ::ATTR_IF_THIS_CHANGED, + ::ATTR_THEN_THIS_WOULD_NEED, + ::ATTR_DIRTY, + ::ATTR_CLEAN, + ::ATTR_DIRTY_METADATA, + ::ATTR_CLEAN_METADATA +]; pub struct StrictVersionHashVisitor<'a, 'hash: 'a, 'tcx: 'hash> { pub tcx: TyCtxt<'hash, 'tcx, 'tcx>, diff --git a/src/librustc_incremental/lib.rs b/src/librustc_incremental/lib.rs index 42b5657e212..2c1340e566d 100644 --- a/src/librustc_incremental/lib.rs +++ b/src/librustc_incremental/lib.rs @@ -35,6 +35,13 @@ extern crate serialize as rustc_serialize; #[macro_use] extern crate syntax; extern crate syntax_pos; +const ATTR_DIRTY: &'static str = "rustc_dirty"; +const ATTR_CLEAN: &'static str = "rustc_clean"; +const ATTR_DIRTY_METADATA: &'static str = "rustc_metadata_dirty"; +const ATTR_CLEAN_METADATA: &'static str = "rustc_metadata_clean"; +const ATTR_IF_THIS_CHANGED: &'static str = "rustc_if_this_changed"; +const ATTR_THEN_THIS_WOULD_NEED: &'static str = "rustc_then_this_would_need"; + mod assert_dep_graph; mod calculate_svh; mod persist; diff --git a/src/librustc_incremental/persist/data.rs b/src/librustc_incremental/persist/data.rs index 12f3ed8ae2b..57e7a0bc21a 100644 --- a/src/librustc_incremental/persist/data.rs +++ b/src/librustc_incremental/persist/data.rs @@ -13,6 +13,7 @@ use rustc::dep_graph::{DepNode, WorkProduct, WorkProductId}; use rustc::hir::def_id::DefIndex; use std::sync::Arc; +use rustc_data_structures::fnv::FnvHashMap; use super::directory::DefPathIndex; @@ -93,6 +94,18 @@ pub struct SerializedMetadataHashes { /// a `DefPathIndex` that gets retracted to the current `DefId` /// (matching the one found in this structure). pub hashes: Vec, + + /// For each DefIndex (as it occurs in SerializedMetadataHash), this + /// map stores the DefPathIndex (as it occurs in DefIdDirectory), so + /// that we can find the new DefId for a SerializedMetadataHash in a + /// subsequent compilation session. + /// + /// This map is only needed for running auto-tests using the + /// #[rustc_metadata_dirty] and #[rustc_metadata_clean] attributes, and + /// is only populated if -Z query-dep-graph is specified. It will be + /// empty otherwise. Importing crates are perfectly happy with just having + /// the DefIndex. + pub index_map: FnvHashMap } /// The hash for some metadata that (when saving) will be exported diff --git a/src/librustc_incremental/persist/directory.rs b/src/librustc_incremental/persist/directory.rs index cca364f442d..619e237ee34 100644 --- a/src/librustc_incremental/persist/directory.rs +++ b/src/librustc_incremental/persist/directory.rs @@ -178,7 +178,6 @@ impl<'a,'tcx> DefIdDirectoryBuilder<'a,'tcx> { &self.directory.paths[id.index as usize] } - pub fn map(&mut self, node: &DepNode) -> DepNode { node.map_def(|&def_id| Some(self.add(def_id))).unwrap() } diff --git a/src/librustc_incremental/persist/dirty_clean.rs b/src/librustc_incremental/persist/dirty_clean.rs index fda7ef207a3..95452021d87 100644 --- a/src/librustc_incremental/persist/dirty_clean.rs +++ b/src/librustc_incremental/persist/dirty_clean.rs @@ -9,10 +9,10 @@ // except according to those terms. //! Debugging code to test the state of the dependency graph just -//! after it is loaded from disk. For each node marked with -//! `#[rustc_clean]` or `#[rustc_dirty]`, we will check that a -//! suitable node for that item either appears or does not appear in -//! the dep-graph, as appropriate: +//! after it is loaded from disk and just after it has been saved. +//! For each node marked with `#[rustc_clean]` or `#[rustc_dirty]`, +//! we will check that a suitable node for that item either appears +//! or does not appear in the dep-graph, as appropriate: //! //! - `#[rustc_dirty(label="TypeckItemBody", cfg="rev2")]` if we are //! in `#[cfg(rev2)]`, then there MUST NOT be a node @@ -23,6 +23,22 @@ //! //! Errors are reported if we are in the suitable configuration but //! the required condition is not met. +//! +//! The `#[rustc_metadata_dirty]` and `#[rustc_metadata_clean]` attributes +//! can be used to check the incremental compilation hash (ICH) values of +//! metadata exported in rlibs. +//! +//! - If a node is marked with `#[rustc_metadata_clean(cfg="rev2")]` we +//! check that the metadata hash for that node is the same for "rev2" +//! it was for "rev1". +//! - If a node is marked with `#[rustc_metadata_dirty(cfg="rev2")]` we +//! check that the metadata hash for that node is *different* for "rev2" +//! than it was for "rev1". +//! +//! Note that the metadata-testing attributes must never specify the +//! first revision. This would lead to a crash since there is no +//! previous revision to compare things to. +//! use super::directory::RetracedDefIdDirectory; use super::load::DirtyNodes; @@ -30,13 +46,14 @@ use rustc::dep_graph::{DepGraphQuery, DepNode}; use rustc::hir; use rustc::hir::def_id::DefId; use rustc::hir::intravisit::Visitor; -use rustc_data_structures::fnv::FnvHashSet; use syntax::ast::{self, Attribute, NestedMetaItem}; +use rustc_data_structures::fnv::{FnvHashSet, FnvHashMap}; use syntax::parse::token::InternedString; +use syntax_pos::Span; use rustc::ty::TyCtxt; -const DIRTY: &'static str = "rustc_dirty"; -const CLEAN: &'static str = "rustc_clean"; +use {ATTR_DIRTY, ATTR_CLEAN, ATTR_DIRTY_METADATA, ATTR_CLEAN_METADATA}; + const LABEL: &'static str = "label"; const CFG: &'static str = "cfg"; @@ -70,50 +87,11 @@ pub struct DirtyCleanVisitor<'a, 'tcx:'a> { } impl<'a, 'tcx> DirtyCleanVisitor<'a, 'tcx> { - fn expect_associated_value(&self, item: &NestedMetaItem) -> InternedString { - if let Some(value) = item.value_str() { - value - } else { - let msg = if let Some(name) = item.name() { - format!("associated value expected for `{}`", name) - } else { - "expected an associated value".to_string() - }; - - self.tcx.sess.span_fatal(item.span, &msg); - } - } - - /// Given a `#[rustc_dirty]` or `#[rustc_clean]` attribute, scan - /// for a `cfg="foo"` attribute and check whether we have a cfg - /// flag called `foo`. - fn check_config(&self, attr: &ast::Attribute) -> bool { - debug!("check_config(attr={:?})", attr); - let config = &self.tcx.map.krate().config; - debug!("check_config: config={:?}", config); - for item in attr.meta_item_list().unwrap_or(&[]) { - if item.check_name(CFG) { - let value = self.expect_associated_value(item); - debug!("check_config: searching for cfg {:?}", value); - for cfg in &config[..] { - if cfg.check_name(&value[..]) { - debug!("check_config: matched {:?}", cfg); - return true; - } - } - return false; - } - } - - self.tcx.sess.span_fatal( - attr.span, - &format!("no cfg attribute")); - } fn dep_node(&self, attr: &Attribute, def_id: DefId) -> DepNode { for item in attr.meta_item_list().unwrap_or(&[]) { if item.check_name(LABEL) { - let value = self.expect_associated_value(item); + let value = expect_associated_value(self.tcx, item); match DepNode::from_label_string(&value[..], def_id) { Ok(def_id) => return def_id, Err(()) => { @@ -194,12 +172,12 @@ impl<'a, 'tcx> Visitor<'tcx> for DirtyCleanVisitor<'a, 'tcx> { fn visit_item(&mut self, item: &'tcx hir::Item) { let def_id = self.tcx.map.local_def_id(item.id); for attr in self.tcx.get_attrs(def_id).iter() { - if attr.check_name(DIRTY) { - if self.check_config(attr) { + if attr.check_name(ATTR_DIRTY) { + if check_config(self.tcx, attr) { self.assert_dirty(item, self.dep_node(attr, def_id)); } - } else if attr.check_name(CLEAN) { - if self.check_config(attr) { + } else if attr.check_name(ATTR_CLEAN) { + if check_config(self.tcx, attr) { self.assert_clean(item, self.dep_node(attr, def_id)); } } @@ -207,3 +185,115 @@ impl<'a, 'tcx> Visitor<'tcx> for DirtyCleanVisitor<'a, 'tcx> { } } +pub fn check_dirty_clean_metadata<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, + prev_metadata_hashes: &FnvHashMap, + current_metadata_hashes: &FnvHashMap) { + if !tcx.sess.opts.debugging_opts.query_dep_graph { + return; + } + + tcx.dep_graph.with_ignore(||{ + let krate = tcx.map.krate(); + krate.visit_all_items(&mut DirtyCleanMetadataVisitor { + tcx: tcx, + prev_metadata_hashes: prev_metadata_hashes, + current_metadata_hashes: current_metadata_hashes, + }); + }); +} + +pub struct DirtyCleanMetadataVisitor<'a, 'tcx:'a, 'm> { + tcx: TyCtxt<'a, 'tcx, 'tcx>, + prev_metadata_hashes: &'m FnvHashMap, + current_metadata_hashes: &'m FnvHashMap, +} + +impl<'a, 'tcx, 'm> Visitor<'tcx> for DirtyCleanMetadataVisitor<'a, 'tcx, 'm> { + fn visit_item(&mut self, item: &'tcx hir::Item) { + let def_id = self.tcx.map.local_def_id(item.id); + + for attr in self.tcx.get_attrs(def_id).iter() { + if attr.check_name(ATTR_DIRTY_METADATA) { + if check_config(self.tcx, attr) { + self.assert_state(false, def_id, item.span); + } + } else if attr.check_name(ATTR_CLEAN_METADATA) { + if check_config(self.tcx, attr) { + self.assert_state(true, def_id, item.span); + } + } + } + } +} + +impl<'a, 'tcx, 'm> DirtyCleanMetadataVisitor<'a, 'tcx, 'm> { + + fn assert_state(&self, should_be_clean: bool, def_id: DefId, span: Span) { + let item_path = self.tcx.item_path_str(def_id); + debug!("assert_state({})", item_path); + + if let Some(&prev_hash) = self.prev_metadata_hashes.get(&def_id) { + let hashes_are_equal = prev_hash == self.current_metadata_hashes[&def_id]; + + if should_be_clean && !hashes_are_equal { + self.tcx.sess.span_err( + span, + &format!("Metadata hash of `{}` is dirty, but should be clean", + item_path)); + } + + let should_be_dirty = !should_be_clean; + if should_be_dirty && hashes_are_equal { + self.tcx.sess.span_err( + span, + &format!("Metadata hash of `{}` is clean, but should be dirty", + item_path)); + } + } else { + self.tcx.sess.span_err( + span, + &format!("Could not find previous metadata hash of `{}`", + item_path)); + } + } +} + +/// Given a `#[rustc_dirty]` or `#[rustc_clean]` attribute, scan +/// for a `cfg="foo"` attribute and check whether we have a cfg +/// flag called `foo`. +fn check_config(tcx: TyCtxt, attr: &ast::Attribute) -> bool { + debug!("check_config(attr={:?})", attr); + let config = &tcx.map.krate().config; + debug!("check_config: config={:?}", config); + for item in attr.meta_item_list().unwrap_or(&[]) { + if item.check_name(CFG) { + let value = expect_associated_value(tcx, item); + debug!("check_config: searching for cfg {:?}", value); + for cfg in &config[..] { + if cfg.check_name(&value[..]) { + debug!("check_config: matched {:?}", cfg); + return true; + } + } + return false; + } + } + + tcx.sess.span_fatal( + attr.span, + &format!("no cfg attribute")); +} + +fn expect_associated_value(tcx: TyCtxt, item: &NestedMetaItem) -> InternedString { + if let Some(value) = item.value_str() { + value + } else { + let msg = if let Some(name) = item.name() { + format!("associated value expected for `{}`", name) + } else { + "expected an associated value".to_string() + }; + + tcx.sess.span_fatal(item.span, &msg); + } +} diff --git a/src/librustc_incremental/persist/load.rs b/src/librustc_incremental/persist/load.rs index b051e6c5ab7..ba15529c81a 100644 --- a/src/librustc_incremental/persist/load.rs +++ b/src/librustc_incremental/persist/load.rs @@ -12,9 +12,10 @@ use rustc::dep_graph::DepNode; use rustc::hir::def_id::DefId; +use rustc::hir::svh::Svh; use rustc::session::Session; use rustc::ty::TyCtxt; -use rustc_data_structures::fnv::FnvHashSet; +use rustc_data_structures::fnv::{FnvHashSet, FnvHashMap}; use rustc_serialize::Decodable as RustcDecodable; use rustc_serialize::opaque::Decoder; use std::io::Read; @@ -224,6 +225,9 @@ pub fn decode_dep_graph<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, dirty_clean::check_dirty_clean_annotations(tcx, &dirty_raw_source_nodes, &retraced); + load_prev_metadata_hashes(tcx, + &retraced, + &mut *incremental_hashes_map.prev_metadata_hashes.borrow_mut()); Ok(()) } @@ -241,6 +245,9 @@ fn dirty_nodes<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, if let Some(dep_node) = retraced.map(&hash.dep_node) { let current_hash = hcx.hash(&dep_node).unwrap(); if current_hash == hash.hash { + debug!("initial_dirty_nodes: {:?} is clean (hash={:?})", + dep_node.map_def(|&def_id| Some(tcx.def_path(def_id))).unwrap(), + current_hash); continue; } debug!("initial_dirty_nodes: {:?} is dirty as hash is {:?}, was {:?}", @@ -304,3 +311,50 @@ fn delete_dirty_work_product(tcx: TyCtxt, } } } + +fn load_prev_metadata_hashes(tcx: TyCtxt, + retraced: &RetracedDefIdDirectory, + output: &mut FnvHashMap) { + if !tcx.sess.opts.debugging_opts.query_dep_graph { + return + } + + debug!("load_prev_metadata_hashes() - Loading previous metadata hashes"); + + let file_path = metadata_hash_export_path(tcx.sess); + + if !file_path.exists() { + debug!("load_prev_metadata_hashes() - Couldn't find file containing \ + hashes at `{}`", file_path.display()); + return + } + + debug!("load_prev_metadata_hashes() - File: {}", file_path.display()); + + let mut data = vec![]; + if !File::open(&file_path) + .and_then(|mut file| file.read_to_end(&mut data)).is_ok() { + debug!("load_prev_metadata_hashes() - Couldn't read file containing \ + hashes at `{}`", file_path.display()); + return + } + + debug!("load_prev_metadata_hashes() - Decoding hashes"); + let mut decoder = Decoder::new(&mut data, 0); + let _ = Svh::decode(&mut decoder).unwrap(); + let serialized_hashes = SerializedMetadataHashes::decode(&mut decoder).unwrap(); + + debug!("load_prev_metadata_hashes() - Mapping DefIds"); + + assert_eq!(serialized_hashes.index_map.len(), serialized_hashes.hashes.len()); + for serialized_hash in serialized_hashes.hashes { + let def_path_index = serialized_hashes.index_map[&serialized_hash.def_index]; + if let Some(def_id) = retraced.def_id(def_path_index) { + let old = output.insert(def_id, serialized_hash.hash); + assert!(old.is_none(), "already have hash for {:?}", def_id); + } + } + + debug!("load_prev_metadata_hashes() - successfully loaded {} hashes", + serialized_hashes.index_map.len()); +} diff --git a/src/librustc_incremental/persist/save.rs b/src/librustc_incremental/persist/save.rs index 5b45874840f..896e8a9845e 100644 --- a/src/librustc_incremental/persist/save.rs +++ b/src/librustc_incremental/persist/save.rs @@ -27,6 +27,7 @@ use super::directory::*; use super::hash::*; use super::preds::*; use super::fs::*; +use super::dirty_clean; pub fn save_dep_graph<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, incremental_hashes_map: &IncrementalHashesMap, @@ -37,16 +38,32 @@ pub fn save_dep_graph<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, if sess.opts.incremental.is_none() { return; } - let mut hcx = HashContext::new(tcx, incremental_hashes_map); + let mut builder = DefIdDirectoryBuilder::new(tcx); let query = tcx.dep_graph.query(); + let mut hcx = HashContext::new(tcx, incremental_hashes_map); let preds = Predecessors::new(&query, &mut hcx); + let mut current_metadata_hashes = FnvHashMap(); + + // IMPORTANT: We are saving the metadata hashes *before* the dep-graph, + // since metadata-encoding might add new entries to the + // DefIdDirectory (which is saved in the dep-graph file). + save_in(sess, + metadata_hash_export_path(sess), + |e| encode_metadata_hashes(tcx, + svh, + &preds, + &mut builder, + &mut current_metadata_hashes, + e)); save_in(sess, dep_graph_path(sess), |e| encode_dep_graph(&preds, &mut builder, e)); - save_in(sess, - metadata_hash_export_path(sess), - |e| encode_metadata_hashes(tcx, svh, &preds, &mut builder, e)); + + let prev_metadata_hashes = incremental_hashes_map.prev_metadata_hashes.borrow(); + dirty_clean::check_dirty_clean_metadata(tcx, + &*prev_metadata_hashes, + ¤t_metadata_hashes); } pub fn save_work_products(sess: &Session) { @@ -63,13 +80,17 @@ pub fn save_work_products(sess: &Session) { fn save_in(sess: &Session, path_buf: PathBuf, encode: F) where F: FnOnce(&mut Encoder) -> io::Result<()> { + debug!("save: storing data in {}", path_buf.display()); + // delete the old dep-graph, if any // Note: It's important that we actually delete the old file and not just // truncate and overwrite it, since it might be a shared hard-link, the // underlying data of which we don't want to modify if path_buf.exists() { match fs::remove_file(&path_buf) { - Ok(()) => {} + Ok(()) => { + debug!("save: remove old file"); + } Err(err) => { sess.err(&format!("unable to delete old dep-graph at `{}`: {}", path_buf.display(), @@ -94,7 +115,9 @@ fn save_in(sess: &Session, path_buf: PathBuf, encode: F) // write the data out let data = wr.into_inner(); match File::create(&path_buf).and_then(|mut file| file.write_all(&data)) { - Ok(_) => {} + Ok(_) => { + debug!("save: data written to disk successfully"); + } Err(err) => { sess.err(&format!("failed to write dep-graph to `{}`: {}", path_buf.display(), @@ -159,18 +182,9 @@ pub fn encode_metadata_hashes(tcx: TyCtxt, svh: Svh, preds: &Predecessors, builder: &mut DefIdDirectoryBuilder, + current_metadata_hashes: &mut FnvHashMap, encoder: &mut Encoder) -> io::Result<()> { - let mut def_id_hashes = FnvHashMap(); - let mut def_id_hash = |def_id: DefId| -> u64 { - *def_id_hashes.entry(def_id) - .or_insert_with(|| { - let index = builder.add(def_id); - let path = builder.lookup_def_path(index); - path.deterministic_hash(tcx) - }) - }; - // For each `MetaData(X)` node where `X` is local, accumulate a // hash. These are the metadata items we export. Downstream // crates will want to see a hash that tells them whether we might @@ -178,7 +192,13 @@ pub fn encode_metadata_hashes(tcx: TyCtxt, // compiled. // // (I initially wrote this with an iterator, but it seemed harder to read.) - let mut serialized_hashes = SerializedMetadataHashes { hashes: vec![] }; + let mut serialized_hashes = SerializedMetadataHashes { + hashes: vec![], + index_map: FnvHashMap() + }; + + let mut def_id_hashes = FnvHashMap(); + for (&target, sources) in &preds.inputs { let def_id = match *target { DepNode::MetaData(def_id) => { @@ -188,6 +208,15 @@ pub fn encode_metadata_hashes(tcx: TyCtxt, _ => continue, }; + let mut def_id_hash = |def_id: DefId| -> u64 { + *def_id_hashes.entry(def_id) + .or_insert_with(|| { + let index = builder.add(def_id); + let path = builder.lookup_def_path(index); + path.deterministic_hash(tcx) + }) + }; + // To create the hash for each item `X`, we don't hash the raw // bytes of the metadata (though in principle we // could). Instead, we walk the predecessors of `MetaData(X)` @@ -221,6 +250,22 @@ pub fn encode_metadata_hashes(tcx: TyCtxt, }); } + if tcx.sess.opts.debugging_opts.query_dep_graph { + for serialized_hash in &serialized_hashes.hashes { + let def_id = DefId::local(serialized_hash.def_index); + + // Store entry in the index_map + let def_path_index = builder.add(def_id); + serialized_hashes.index_map.insert(def_id.index, def_path_index); + + // Record hash in current_metadata_hashes + current_metadata_hashes.insert(def_id, serialized_hash.hash); + } + + debug!("save: stored index_map (len={}) for serialized hashes", + serialized_hashes.index_map.len()); + } + // Encode everything. svh.encode(encoder)?; serialized_hashes.encode(encoder)?; diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 27b97a0ad66..75cfa587ab1 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -508,6 +508,16 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGat is just used for rustc unit tests \ and will never be stable", cfg_fn!(rustc_attrs))), + ("rustc_metadata_dirty", Whitelisted, Gated("rustc_attrs", + "the `#[rustc_metadata_dirty]` attribute \ + is just used for rustc unit tests \ + and will never be stable", + cfg_fn!(rustc_attrs))), + ("rustc_metadata_clean", Whitelisted, Gated("rustc_attrs", + "the `#[rustc_metadata_clean]` attribute \ + is just used for rustc unit tests \ + and will never be stable", + cfg_fn!(rustc_attrs))), ("rustc_partition_reused", Whitelisted, Gated("rustc_attrs", "this attribute \ is just used for rustc unit tests \ diff --git a/src/test/incremental/hashes/struct_defs.rs b/src/test/incremental/hashes/struct_defs.rs new file mode 100644 index 00000000000..74c7797be2a --- /dev/null +++ b/src/test/incremental/hashes/struct_defs.rs @@ -0,0 +1,238 @@ +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + + +// This test case tests the incremental compilation hash (ICH) implementation +// for struct definitions. + +// The general pattern followed here is: Change one thing between rev1 and rev2 +// and make sure that the hash has changed, then change nothing between rev2 and +// rev3 and make sure that the hash has not changed. + +// We also test the ICH for struct definitions exported in metadata. Same as +// above, we want to make sure that the change between rev1 and rev2 also +// results in a change of the ICH for the struct's metadata, and that it stays +// the same between rev2 and rev3. + +// must-compile-successfully +// revisions: cfail1 cfail2 cfail3 +// compile-flags: -Z query-dep-graph + + +#![allow(warnings)] +#![feature(rustc_attrs)] +#![crate_type="rlib"] + +// Layout ---------------------------------------------------------------------- +#[cfg(cfail1)] +pub struct LayoutPacked; + +#[cfg(not(cfail1))] +#[rustc_dirty(label="Hir", cfg="cfail2")] +#[rustc_clean(label="Hir", cfg="cfail3")] +#[rustc_metadata_dirty(cfg="cfail2")] +#[rustc_metadata_clean(cfg="cfail3")] +#[repr(packed)] +pub struct LayoutPacked; + +#[cfg(cfail1)] +struct LayoutC; + +#[cfg(not(cfail1))] +#[rustc_dirty(label="Hir", cfg="cfail2")] +#[rustc_clean(label="Hir", cfg="cfail3")] +#[rustc_metadata_dirty(cfg="cfail2")] +#[rustc_metadata_clean(cfg="cfail3")] +#[repr(C)] +struct LayoutC; + + +// Tuple Struct Change Field Type ---------------------------------------------- + +#[cfg(cfail1)] +struct TupleStructFieldType(i32); + +#[cfg(not(cfail1))] +#[rustc_dirty(label="Hir", cfg="cfail2")] +#[rustc_clean(label="Hir", cfg="cfail3")] +#[rustc_metadata_dirty(cfg="cfail2")] +#[rustc_metadata_clean(cfg="cfail3")] +struct TupleStructFieldType(u32); + + +// Tuple Struct Add Field ------------------------------------------------------ + +#[cfg(cfail1)] +struct TupleStructAddField(i32); + +#[cfg(not(cfail1))] +#[rustc_dirty(label="Hir", cfg="cfail2")] +#[rustc_clean(label="Hir", cfg="cfail3")] +#[rustc_metadata_dirty(cfg="cfail2")] +#[rustc_metadata_clean(cfg="cfail3")] +struct TupleStructAddField(i32, u32); + + +// Tuple Struct Field Visibility ----------------------------------------------- + +#[cfg(cfail1)] +struct TupleStructFieldVisibility(char); + +#[cfg(not(cfail1))] +#[rustc_dirty(label="Hir", cfg="cfail2")] +#[rustc_clean(label="Hir", cfg="cfail3")] +#[rustc_metadata_dirty(cfg="cfail2")] +#[rustc_metadata_clean(cfg="cfail3")] +struct TupleStructFieldVisibility(pub char); + + +// Record Struct Field Type ---------------------------------------------------- + +#[cfg(cfail1)] +struct RecordStructFieldType { x: f32 } + +#[cfg(not(cfail1))] +#[rustc_dirty(label="Hir", cfg="cfail2")] +#[rustc_clean(label="Hir", cfg="cfail3")] +#[rustc_metadata_dirty(cfg="cfail2")] +#[rustc_metadata_clean(cfg="cfail3")] +struct RecordStructFieldType { x: u64 } + + +// Record Struct Field Name ---------------------------------------------------- + +#[cfg(cfail1)] +struct RecordStructFieldName { x: f32 } + +#[cfg(not(cfail1))] +#[rustc_dirty(label="Hir", cfg="cfail2")] +#[rustc_clean(label="Hir", cfg="cfail3")] +#[rustc_metadata_dirty(cfg="cfail2")] +#[rustc_metadata_clean(cfg="cfail3")] +struct RecordStructFieldName { y: f32 } + + +// Record Struct Add Field ----------------------------------------------------- + +#[cfg(cfail1)] +struct RecordStructAddField { x: f32 } + +#[cfg(not(cfail1))] +#[rustc_dirty(label="Hir", cfg="cfail2")] +#[rustc_clean(label="Hir", cfg="cfail3")] +#[rustc_metadata_dirty(cfg="cfail2")] +#[rustc_metadata_clean(cfg="cfail3")] +struct RecordStructAddField { x: f32, y: () } + + +// Record Struct Field Visibility ---------------------------------------------- + +#[cfg(cfail1)] +struct RecordStructFieldVisibility { x: f32 } + +#[cfg(not(cfail1))] +#[rustc_dirty(label="Hir", cfg="cfail2")] +#[rustc_clean(label="Hir", cfg="cfail3")] +#[rustc_metadata_dirty(cfg="cfail2")] +#[rustc_metadata_clean(cfg="cfail3")] +struct RecordStructFieldVisibility { pub x: f32 } + + +// Add Lifetime Parameter ------------------------------------------------------ + +#[cfg(cfail1)] +struct AddLifetimeParameter<'a>(&'a f32, &'a f64); + +#[cfg(not(cfail1))] +#[rustc_dirty(label="Hir", cfg="cfail2")] +#[rustc_clean(label="Hir", cfg="cfail3")] +#[rustc_metadata_dirty(cfg="cfail2")] +#[rustc_metadata_clean(cfg="cfail3")] +struct AddLifetimeParameter<'a, 'b>(&'a f32, &'b f64); + + +// Add Lifetime Parameter Bound ------------------------------------------------ + +#[cfg(cfail1)] +struct AddLifetimeParameterBound<'a, 'b>(&'a f32, &'b f64); + +#[cfg(not(cfail1))] +#[rustc_dirty(label="Hir", cfg="cfail2")] +#[rustc_clean(label="Hir", cfg="cfail3")] +#[rustc_metadata_dirty(cfg="cfail2")] +#[rustc_metadata_clean(cfg="cfail3")] +struct AddLifetimeParameterBound<'a, 'b: 'a>(&'a f32, &'b f64); + +#[cfg(cfail1)] +struct AddLifetimeParameterBoundWhereClause<'a, 'b>(&'a f32, &'b f64); + +#[cfg(not(cfail1))] +#[rustc_dirty(label="Hir", cfg="cfail2")] +#[rustc_clean(label="Hir", cfg="cfail3")] +#[rustc_metadata_dirty(cfg="cfail2")] +#[rustc_metadata_clean(cfg="cfail3")] +struct AddLifetimeParameterBoundWhereClause<'a, 'b>(&'a f32, &'b f64) + where 'b: 'a; + + +// Add Type Parameter ---------------------------------------------------------- + +#[cfg(cfail1)] +struct AddTypeParameter(T1, T1); + +#[cfg(not(cfail1))] +#[rustc_dirty(label="Hir", cfg="cfail2")] +#[rustc_clean(label="Hir", cfg="cfail3")] +#[rustc_metadata_dirty(cfg="cfail2")] +#[rustc_metadata_clean(cfg="cfail3")] +struct AddTypeParameter(T1, T2); + + +// Add Type Parameter Bound ---------------------------------------------------- + +#[cfg(cfail1)] +struct AddTypeParameterBound(T); + +#[cfg(not(cfail1))] +#[rustc_dirty(label="Hir", cfg="cfail2")] +#[rustc_clean(label="Hir", cfg="cfail3")] +#[rustc_metadata_dirty(cfg="cfail2")] +#[rustc_metadata_clean(cfg="cfail3")] +struct AddTypeParameterBound(T); + + +#[cfg(cfail1)] +struct AddTypeParameterBoundWhereClause(T); + +#[cfg(not(cfail1))] +#[rustc_dirty(label="Hir", cfg="cfail2")] +#[rustc_clean(label="Hir", cfg="cfail3")] +#[rustc_metadata_dirty(cfg="cfail2")] +#[rustc_metadata_clean(cfg="cfail3")] +struct AddTypeParameterBoundWhereClause(T) where T: Sync; + + +// Empty struct ---------------------------------------------------------------- + +#[rustc_clean(label="Hir", cfg="cfail2")] +#[rustc_metadata_clean(cfg="cfail2")] +pub struct EmptyStruct; + + +// Visibility ------------------------------------------------------------------ + +#[cfg(cfail1)] +struct Visibility; + +#[cfg(not(cfail1))] +#[rustc_dirty(label="Hir", cfg="cfail2")] +#[rustc_clean(label="Hir", cfg="cfail3")] +#[rustc_metadata_clean(cfg="cfail3")] +pub struct Visibility; diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index 899a366a4bb..503a8516769 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -182,42 +182,32 @@ pub struct TestProps { // testing harness and used when generating compilation // arguments. (In particular, it propagates to the aux-builds.) pub incremental_dir: Option, + // Specifies that a cfail test must actually compile without errors. + pub must_compile_successfully: bool, } impl TestProps { pub fn new() -> Self { - let error_patterns = Vec::new(); - let aux_builds = Vec::new(); - let exec_env = Vec::new(); - let run_flags = None; - let pp_exact = None; - let check_lines = Vec::new(); - let build_aux_docs = false; - let force_host = false; - let check_stdout = false; - let no_prefer_dynamic = false; - let pretty_expanded = false; - let pretty_compare_only = false; - let forbid_output = Vec::new(); TestProps { - error_patterns: error_patterns, + error_patterns: vec![], compile_flags: vec![], - run_flags: run_flags, - pp_exact: pp_exact, - aux_builds: aux_builds, + run_flags: None, + pp_exact: None, + aux_builds: vec![], revisions: vec![], rustc_env: vec![], - exec_env: exec_env, - check_lines: check_lines, - build_aux_docs: build_aux_docs, - force_host: force_host, - check_stdout: check_stdout, - no_prefer_dynamic: no_prefer_dynamic, - pretty_expanded: pretty_expanded, + exec_env: vec![], + check_lines: vec![], + build_aux_docs: false, + force_host: false, + check_stdout: false, + no_prefer_dynamic: false, + pretty_expanded: false, pretty_mode: format!("normal"), - pretty_compare_only: pretty_compare_only, - forbid_output: forbid_output, + pretty_compare_only: false, + forbid_output: vec![], incremental_dir: None, + must_compile_successfully: false, } } @@ -313,6 +303,10 @@ impl TestProps { if let Some(of) = parse_forbid_output(ln) { self.forbid_output.push(of); } + + if !self.must_compile_successfully { + self.must_compile_successfully = parse_must_compile_successfully(ln); + } }); for key in vec!["RUST_TEST_NOCAPTURE", "RUST_TEST_THREADS"] { @@ -420,6 +414,10 @@ fn parse_pretty_compare_only(line: &str) -> bool { parse_name_directive(line, "pretty-compare-only") } +fn parse_must_compile_successfully(line: &str) -> bool { + parse_name_directive(line, "must-compile-successfully") +} + fn parse_env(line: &str, name: &str) -> Option<(String, String)> { parse_name_value_directive(line, name).map(|nv| { // nv is either FOO or FOO=BAR diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index bfb85dd479d..9e490738402 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -129,13 +129,21 @@ impl<'test> TestCx<'test> { fn run_cfail_test(&self) { let proc_res = self.compile_test(); - if proc_res.status.success() { - self.fatal_proc_rec( - &format!("{} test compiled successfully!", self.config.mode)[..], - &proc_res); - } + if self.props.must_compile_successfully { + if !proc_res.status.success() { + self.fatal_proc_rec( + "test compilation failed although it shouldn't!", + &proc_res); + } + } else { + if proc_res.status.success() { + self.fatal_proc_rec( + &format!("{} test compiled successfully!", self.config.mode)[..], + &proc_res); + } - self.check_correct_failure_status(&proc_res); + self.check_correct_failure_status(&proc_res); + } let output_to_check = self.get_output(&proc_res); let expected_errors = errors::load_errors(&self.testpaths.file, self.revision); @@ -147,6 +155,7 @@ impl<'test> TestCx<'test> { } else { self.check_error_patterns(&output_to_check, &proc_res); } + self.check_no_compiler_crash(&proc_res); self.check_forbid_output(&output_to_check, &proc_res); } @@ -943,8 +952,12 @@ actual:\n\ output_to_check: &str, proc_res: &ProcRes) { if self.props.error_patterns.is_empty() { - self.fatal(&format!("no error pattern specified in {:?}", - self.testpaths.file.display())); + if self.props.must_compile_successfully { + return + } else { + self.fatal(&format!("no error pattern specified in {:?}", + self.testpaths.file.display())); + } } let mut next_err_idx = 0; let mut next_err_pat = self.props.error_patterns[next_err_idx].trim();