rustc: Add the concept of a Strict Version Hash
This new SVH is used to uniquely identify all crates as a snapshot in time of their ABI/API/publicly reachable state. This current calculation is just a hash of the entire crate's AST. This is obviously incorrect, but it is currently the reality for today. This change threads through the new Svh structure which originates from crate dependencies. The concept of crate id hash is preserved to provide efficient matching on filenames for crate loading. The inspected hash once crate metadata is opened has been changed to use the new Svh. The goal of this hash is to identify when upstream crates have changed but downstream crates have not been recompiled. This will prevent the def-id drift problem where upstream crates were recompiled, thereby changing their metadata, but downstream crates were not recompiled. In the future this hash can be expanded to exclude contents of the AST like doc comments, but limitations in the compiler prevent this change from being made at this time. Closes #10207
This commit is contained in:
parent
8213e18447
commit
ec57db083f
19 changed files with 345 additions and 139 deletions
|
|
@ -8,9 +8,9 @@
|
|||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
|
||||
use back::archive::{Archive, METADATA_FILENAME};
|
||||
use back::rpath;
|
||||
use back::svh::Svh;
|
||||
use driver::driver::{CrateTranslation, OutputFilenames};
|
||||
use driver::session::Session;
|
||||
use driver::session;
|
||||
|
|
@ -499,30 +499,33 @@ pub mod write {
|
|||
* system linkers understand.
|
||||
*/
|
||||
|
||||
pub fn build_link_meta(attrs: &[ast::Attribute],
|
||||
output: &OutputFilenames,
|
||||
symbol_hasher: &mut Sha256)
|
||||
-> LinkMeta {
|
||||
// This calculates CMH as defined above
|
||||
fn crate_hash(symbol_hasher: &mut Sha256, crateid: &CrateId) -> ~str {
|
||||
symbol_hasher.reset();
|
||||
symbol_hasher.input_str(crateid.to_str());
|
||||
truncated_hash_result(symbol_hasher)
|
||||
}
|
||||
|
||||
let crateid = match attr::find_crateid(attrs) {
|
||||
pub fn find_crate_id(attrs: &[ast::Attribute],
|
||||
output: &OutputFilenames) -> CrateId {
|
||||
match attr::find_crateid(attrs) {
|
||||
None => from_str(output.out_filestem).unwrap(),
|
||||
Some(s) => s,
|
||||
};
|
||||
|
||||
let hash = crate_hash(symbol_hasher, &crateid);
|
||||
|
||||
LinkMeta {
|
||||
crateid: crateid,
|
||||
crate_hash: hash,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn crate_id_hash(crate_id: &CrateId) -> ~str {
|
||||
// This calculates CMH as defined above. Note that we don't use the path of
|
||||
// the crate id in the hash because lookups are only done by (name/vers),
|
||||
// not by path.
|
||||
let mut s = Sha256::new();
|
||||
s.input_str(crate_id.short_name_with_version());
|
||||
truncated_hash_result(&mut s).slice_to(8).to_owned()
|
||||
}
|
||||
|
||||
pub fn build_link_meta(krate: &ast::Crate,
|
||||
output: &OutputFilenames) -> LinkMeta {
|
||||
let r = LinkMeta {
|
||||
crateid: find_crate_id(krate.attrs, output),
|
||||
crate_hash: Svh::calculate(krate),
|
||||
};
|
||||
info!("{}", r);
|
||||
return r;
|
||||
}
|
||||
|
||||
fn truncated_hash_result(symbol_hasher: &mut Sha256) -> ~str {
|
||||
let output = symbol_hasher.result_bytes();
|
||||
// 64 bits should be enough to avoid collisions.
|
||||
|
|
@ -539,7 +542,7 @@ fn symbol_hash(tcx: ty::ctxt, symbol_hasher: &mut Sha256,
|
|||
symbol_hasher.reset();
|
||||
symbol_hasher.input_str(link_meta.crateid.name);
|
||||
symbol_hasher.input_str("-");
|
||||
symbol_hasher.input_str(link_meta.crate_hash);
|
||||
symbol_hasher.input_str(link_meta.crate_hash.as_str());
|
||||
symbol_hasher.input_str("-");
|
||||
symbol_hasher.input_str(encoder::encoded_ty(tcx, t));
|
||||
let mut hash = truncated_hash_result(symbol_hasher);
|
||||
|
|
@ -712,11 +715,8 @@ pub fn mangle_internal_name_by_path_and_seq(path: PathElems, flav: &str) -> ~str
|
|||
mangle(path.chain(Some(gensym_name(flav)).move_iter()), None, None)
|
||||
}
|
||||
|
||||
pub fn output_lib_filename(lm: &LinkMeta) -> ~str {
|
||||
format!("{}-{}-{}",
|
||||
lm.crateid.name,
|
||||
lm.crate_hash.slice_chars(0, 8),
|
||||
lm.crateid.version_or_default())
|
||||
pub fn output_lib_filename(id: &CrateId) -> ~str {
|
||||
format!("{}-{}-{}", id.name, crate_id_hash(id), id.version_or_default())
|
||||
}
|
||||
|
||||
pub fn get_cc_prog(sess: Session) -> ~str {
|
||||
|
|
@ -779,11 +779,11 @@ fn remove(sess: Session, path: &Path) {
|
|||
pub fn link_binary(sess: Session,
|
||||
trans: &CrateTranslation,
|
||||
outputs: &OutputFilenames,
|
||||
lm: &LinkMeta) -> ~[Path] {
|
||||
id: &CrateId) -> ~[Path] {
|
||||
let mut out_filenames = ~[];
|
||||
let crate_types = sess.crate_types.borrow();
|
||||
for &crate_type in crate_types.get().iter() {
|
||||
let out_file = link_binary_output(sess, trans, crate_type, outputs, lm);
|
||||
let out_file = link_binary_output(sess, trans, crate_type, outputs, id);
|
||||
out_filenames.push(out_file);
|
||||
}
|
||||
|
||||
|
|
@ -807,8 +807,8 @@ fn is_writeable(p: &Path) -> bool {
|
|||
}
|
||||
|
||||
pub fn filename_for_input(sess: &Session, crate_type: session::CrateType,
|
||||
lm: &LinkMeta, out_filename: &Path) -> Path {
|
||||
let libname = output_lib_filename(lm);
|
||||
id: &CrateId, out_filename: &Path) -> Path {
|
||||
let libname = output_lib_filename(id);
|
||||
match crate_type {
|
||||
session::CrateTypeRlib => {
|
||||
out_filename.with_filename(format!("lib{}.rlib", libname))
|
||||
|
|
@ -834,13 +834,13 @@ fn link_binary_output(sess: Session,
|
|||
trans: &CrateTranslation,
|
||||
crate_type: session::CrateType,
|
||||
outputs: &OutputFilenames,
|
||||
lm: &LinkMeta) -> Path {
|
||||
id: &CrateId) -> Path {
|
||||
let obj_filename = outputs.temp_path(OutputTypeObject);
|
||||
let out_filename = match outputs.single_output_file {
|
||||
Some(ref file) => file.clone(),
|
||||
None => {
|
||||
let out_filename = outputs.path(OutputTypeExe);
|
||||
filename_for_input(&sess, crate_type, lm, &out_filename)
|
||||
filename_for_input(&sess, crate_type, id, &out_filename)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
112
src/librustc/back/svh.rs
Normal file
112
src/librustc/back/svh.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// Copyright 2012-2014 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.
|
||||
|
||||
//! Calculation and management of a Strict Version Hash for crates
|
||||
//!
|
||||
//! # Today's ABI problem
|
||||
//!
|
||||
//! In today's implementation of rustc, it is incredibly difficult to achieve
|
||||
//! forward binary compatibility without resorting to C-like interfaces. Within
|
||||
//! rust code itself, abi details such as symbol names suffer from a variety of
|
||||
//! unrelated factors to code changing such as the "def id drift" problem. This
|
||||
//! ends up yielding confusing error messages about metadata mismatches and
|
||||
//! such.
|
||||
//!
|
||||
//! The core of this problem is when when an upstream dependency changes and
|
||||
//! downstream dependants are not recompiled. This causes compile errors because
|
||||
//! the upstream crate's metadata has changed but the downstream crates are
|
||||
//! still referencing the older crate's metadata.
|
||||
//!
|
||||
//! This problem exists for many reasons, the primary of which is that rust does
|
||||
//! not currently support forwards ABI compatibility (in place upgrades of a
|
||||
//! crate).
|
||||
//!
|
||||
//! # SVH and how it alleviates the problem
|
||||
//!
|
||||
//! With all of this knowledge on hand, this module contains the implementation
|
||||
//! of a notion of a "Strict Version Hash" for a crate. This is essentially a
|
||||
//! hash of all contents of a crate which can somehow be exposed to downstream
|
||||
//! crates.
|
||||
//!
|
||||
//! This hash is currently calculated by just hashing the AST, but this is
|
||||
//! obviously wrong (doc changes should not result in an incompatible ABI).
|
||||
//! Implementation-wise, this is required at this moment in time.
|
||||
//!
|
||||
//! By encoding this strict version hash into all crate's metadata, stale crates
|
||||
//! can be detected immediately and error'd about by rustc itself.
|
||||
//!
|
||||
//! # Relevant links
|
||||
//!
|
||||
//! Original issue: https://github.com/mozilla/rust/issues/10207
|
||||
|
||||
use std::fmt;
|
||||
use std::hash::Hash;
|
||||
use std::hash::sip::SipState;
|
||||
use std::iter::range_step;
|
||||
use syntax::ast;
|
||||
|
||||
#[deriving(Clone, Eq)]
|
||||
pub struct Svh {
|
||||
priv hash: ~str,
|
||||
}
|
||||
|
||||
impl Svh {
|
||||
pub fn new(hash: &str) -> Svh {
|
||||
assert!(hash.len() == 16);
|
||||
Svh { hash: hash.to_owned() }
|
||||
}
|
||||
|
||||
pub fn as_str<'a>(&'a self) -> &'a str {
|
||||
self.hash.as_slice()
|
||||
}
|
||||
|
||||
pub fn calculate(krate: &ast::Crate) -> Svh {
|
||||
// FIXME: see above for why this is wrong, it shouldn't just hash the
|
||||
// crate. Fixing this would require more in-depth analysis in
|
||||
// this function about what portions of the crate are reachable
|
||||
// in tandem with bug fixes throughout the rest of the compiler.
|
||||
//
|
||||
// Note that for now we actually exclude some top-level things
|
||||
// from the crate like the CrateConfig/span. The CrateConfig
|
||||
// contains command-line `--cfg` flags, so this means that the
|
||||
// stage1/stage2 AST for libstd and such is different hash-wise
|
||||
// when it's actually the exact same representation-wise.
|
||||
//
|
||||
// As a first stab at only hashing the relevant parts of the
|
||||
// AST, this only hashes the module/attrs, not the CrateConfig
|
||||
// field.
|
||||
//
|
||||
// FIXME: this should use SHA1, not SipHash. SipHash is not built to
|
||||
// avoid collisions.
|
||||
let mut state = SipState::new();
|
||||
krate.module.hash(&mut state);
|
||||
krate.attrs.hash(&mut state);
|
||||
|
||||
let hash = state.result();
|
||||
return Svh {
|
||||
hash: range_step(0, 64, 4).map(|i| hex(hash >> i)).collect()
|
||||
};
|
||||
|
||||
fn hex(b: u64) -> char {
|
||||
let b = (b & 0xf) as u8;
|
||||
let b = match b {
|
||||
0 .. 9 => '0' as u8 + b,
|
||||
_ => 'a' as u8 + b - 10,
|
||||
};
|
||||
b as char
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Show for Svh {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.pad(self.as_str())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue