ICH: Add ability to test the ICH of exported metadata items.

This commit is contained in:
Michael Woerister 2016-08-30 16:49:54 -04:00
parent f2c53ea66b
commit 6a2666d5b0
13 changed files with 622 additions and 115 deletions

View File

@ -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) => {

View File

@ -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<DepNode<DefId>, u64>;
pub struct IncrementalHashesMap {
hashes: FnvHashMap<DepNode<DefId>, 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<FnvHashMap<DefId, u64>>,
}
impl IncrementalHashesMap {
pub fn new() -> IncrementalHashesMap {
IncrementalHashesMap {
hashes: FnvHashMap(),
prev_metadata_hashes: RefCell::new(FnvHashMap()),
}
}
pub fn insert(&mut self, k: DepNode<DefId>, v: u64) -> Option<u64> {
self.hashes.insert(k, v)
}
pub fn iter<'a>(&'a self) -> ::std::collections::hash_map::Iter<'a, DepNode<DefId>, u64> {
self.hashes.iter()
}
}
impl<'a> ::std::ops::Index<&'a DepNode<DefId>> for IncrementalHashesMap {
type Output = u64;
fn index(&self, index: &'a DepNode<DefId>) -> &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,

View File

@ -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>,

View File

@ -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;

View File

@ -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<SerializedMetadataHash>,
/// 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<DefIndex, DefPathIndex>
}
/// The hash for some metadata that (when saving) will be exported

View File

@ -178,7 +178,6 @@ impl<'a,'tcx> DefIdDirectoryBuilder<'a,'tcx> {
&self.directory.paths[id.index as usize]
}
pub fn map(&mut self, node: &DepNode<DefId>) -> DepNode<DefPathIndex> {
node.map_def(|&def_id| Some(self.add(def_id))).unwrap()
}

View File

@ -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<DefId> {
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<DefId, u64>,
current_metadata_hashes: &FnvHashMap<DefId, u64>) {
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<DefId, u64>,
current_metadata_hashes: &'m FnvHashMap<DefId, u64>,
}
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);
}
}

View File

@ -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<DefId, u64>) {
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());
}

View File

@ -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,
&current_metadata_hashes);
}
pub fn save_work_products(sess: &Session) {
@ -63,13 +80,17 @@ pub fn save_work_products(sess: &Session) {
fn save_in<F>(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<F>(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<DefId, u64>,
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)?;

View File

@ -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 \

View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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, 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>(T1, T2);
// Add Type Parameter Bound ----------------------------------------------------
#[cfg(cfail1)]
struct AddTypeParameterBound<T>(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: Send>(T);
#[cfg(cfail1)]
struct AddTypeParameterBoundWhereClause<T>(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>(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;

View File

@ -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<PathBuf>,
// 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

View File

@ -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();