about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--mk/clean.mk1
-rw-r--r--src/librustc/back/link.rs62
-rw-r--r--src/librustc/back/svh.rs112
-rw-r--r--src/librustc/driver/driver.rs7
-rw-r--r--src/librustc/lib.rs38
-rw-r--r--src/librustc/metadata/common.rs5
-rw-r--r--src/librustc/metadata/creader.rs30
-rw-r--r--src/librustc/metadata/cstore.rs3
-rw-r--r--src/librustc/metadata/decoder.rs15
-rw-r--r--src/librustc/metadata/encoder.rs9
-rw-r--r--src/librustc/metadata/loader.rs88
-rw-r--r--src/librustc/middle/trans/base.rs25
-rw-r--r--src/librustc/middle/trans/intrinsic.rs2
-rw-r--r--src/librustc/middle/ty.rs7
-rw-r--r--src/test/auxiliary/changing-crates-a1.rs13
-rw-r--r--src/test/auxiliary/changing-crates-a2.rs14
-rw-r--r--src/test/auxiliary/changing-crates-b.rs15
-rw-r--r--src/test/compile-fail/bad-crate-id.rs14
-rw-r--r--src/test/compile-fail/changing-crates.rs20
19 files changed, 343 insertions, 137 deletions
diff --git a/mk/clean.mk b/mk/clean.mk
index 002db59ad38..bc5961a9981 100644
--- a/mk/clean.mk
+++ b/mk/clean.mk
@@ -58,6 +58,7 @@ clean-generic-$(2)-$(1):
          -name '*.[odasS]' -o \
          -name '*.so' -o      \
          -name '*.dylib' -o   \
+         -name '*.rlib' -o   \
          -name 'stamp.*' -o   \
          -name '*.lib' -o     \
          -name '*.dll' -o     \
diff --git a/src/librustc/back/link.rs b/src/librustc/back/link.rs
index 33d3a1c67f2..bacc98a0135 100644
--- a/src/librustc/back/link.rs
+++ b/src/librustc/back/link.rs
@@ -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,28 +499,31 @@ 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);
+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()
+}
 
-    LinkMeta {
-        crateid: crateid,
-        crate_hash: hash,
-    }
+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 {
@@ -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)
         }
     };
 
diff --git a/src/librustc/back/svh.rs b/src/librustc/back/svh.rs
new file mode 100644
index 00000000000..5f8a12b022a
--- /dev/null
+++ b/src/librustc/back/svh.rs
@@ -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())
+    }
+}
diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs
index 4c552acc936..d5ee736b6fb 100644
--- a/src/librustc/driver/driver.rs
+++ b/src/librustc/driver/driver.rs
@@ -433,7 +433,7 @@ pub fn phase_6_link_output(sess: Session,
          link::link_binary(sess,
                            trans,
                            outputs,
-                           &trans.link));
+                           &trans.link.crateid));
 }
 
 pub fn stop_after_phase_3(sess: Session) -> bool {
@@ -472,8 +472,7 @@ fn write_out_deps(sess: Session,
                   input: &Input,
                   outputs: &OutputFilenames,
                   krate: &ast::Crate) -> io::IoResult<()> {
-    let lm = link::build_link_meta(krate.attrs, outputs,
-                                   &mut ::util::sha2::Sha256::new());
+    let id = link::find_crate_id(krate.attrs, outputs);
 
     let mut out_filenames = ~[];
     for output_type in sess.opts.output_types.iter() {
@@ -482,7 +481,7 @@ fn write_out_deps(sess: Session,
             link::OutputTypeExe => {
                 let crate_types = sess.crate_types.borrow();
                 for output in crate_types.get().iter() {
-                    let p = link::filename_for_input(&sess, *output, &lm, &file);
+                    let p = link::filename_for_input(&sess, *output, &id, &file);
                     out_filenames.push(p);
                 }
             }
diff --git a/src/librustc/lib.rs b/src/librustc/lib.rs
index ff1a6bb7f7e..2e647e5ca82 100644
--- a/src/librustc/lib.rs
+++ b/src/librustc/lib.rs
@@ -55,7 +55,6 @@ use std::str;
 use std::task;
 use std::vec;
 use syntax::ast;
-use syntax::attr;
 use syntax::diagnostic::Emitter;
 use syntax::diagnostic;
 use syntax::parse;
@@ -104,16 +103,17 @@ pub mod front {
 }
 
 pub mod back {
-    pub mod archive;
-    pub mod link;
     pub mod abi;
+    pub mod archive;
     pub mod arm;
+    pub mod link;
+    pub mod lto;
     pub mod mips;
-    pub mod x86;
-    pub mod x86_64;
     pub mod rpath;
+    pub mod svh;
     pub mod target_strs;
-    pub mod lto;
+    pub mod x86;
+    pub mod x86_64;
 }
 
 pub mod metadata;
@@ -312,28 +312,18 @@ pub fn run_compiler(args: &[~str]) {
         let attrs = parse_crate_attrs(sess, &input);
         let t_outputs = d::build_output_filenames(&input, &odir, &ofile,
                                                   attrs, sess);
-        if crate_id || crate_name {
-            let crateid = match attr::find_crateid(attrs) {
-                Some(crateid) => crateid,
-                None => {
-                    sess.fatal("No crate_id and --crate-id or \
-                                --crate-name requested")
-                }
-            };
-            if crate_id {
-                println!("{}", crateid.to_str());
-            }
-            if crate_name {
-                println!("{}", crateid.name);
-            }
-        }
+        let id = link::find_crate_id(attrs, &t_outputs);
 
+        if crate_id {
+            println!("{}", id.to_str());
+        }
+        if crate_name {
+            println!("{}", id.name);
+        }
         if crate_file_name {
-            let lm = link::build_link_meta(attrs, &t_outputs,
-                                           &mut ::util::sha2::Sha256::new());
             let crate_types = session::collect_crate_types(&sess, attrs);
             for &style in crate_types.iter() {
-                let fname = link::filename_for_input(&sess, style, &lm,
+                let fname = link::filename_for_input(&sess, style, &id,
                                                      &t_outputs.with_extension(""));
                 println!("{}", fname.filename_display());
             }
diff --git a/src/librustc/metadata/common.rs b/src/librustc/metadata/common.rs
index 62f1dcedab4..7b7d526411c 100644
--- a/src/librustc/metadata/common.rs
+++ b/src/librustc/metadata/common.rs
@@ -12,6 +12,7 @@
 
 use std::cast;
 use syntax::crateid::CrateId;
+use back::svh::Svh;
 
 // EBML enum definitions and utils shared by the encoder and decoder
 
@@ -207,8 +208,8 @@ pub static tag_macro_registrar_fn: uint = 0x63;
 pub static tag_exported_macros: uint = 0x64;
 pub static tag_macro_def: uint = 0x65;
 
-#[deriving(Clone)]
+#[deriving(Clone, Show)]
 pub struct LinkMeta {
     crateid: CrateId,
-    crate_hash: ~str,
+    crate_hash: Svh,
 }
diff --git a/src/librustc/metadata/creader.rs b/src/librustc/metadata/creader.rs
index 1108917cdb1..165c1abdeed 100644
--- a/src/librustc/metadata/creader.rs
+++ b/src/librustc/metadata/creader.rs
@@ -12,6 +12,8 @@
 
 //! Validates all used crates and extern libraries and loads their metadata
 
+use back::link;
+use back::svh::Svh;
 use driver::{driver, session};
 use driver::session::Session;
 use metadata::csearch;
@@ -78,7 +80,7 @@ impl<'a> visit::Visitor<()> for ReadCrateVisitor<'a> {
 struct cache_entry {
     cnum: ast::CrateNum,
     span: Span,
-    hash: ~str,
+    hash: Svh,
     crate_id: CrateId,
 }
 
@@ -148,7 +150,7 @@ fn visit_view_item(e: &mut Env, i: &ast::ViewItem) {
 
     match extract_crate_info(e, i) {
         Some(info) => {
-            let cnum = resolve_crate(e, None, info.ident, &info.crate_id, "",
+            let cnum = resolve_crate(e, None, info.ident, &info.crate_id, None,
                                      i.span);
             e.sess.cstore.add_extern_mod_stmt_cnum(info.id, cnum);
         }
@@ -276,12 +278,13 @@ fn visit_item(e: &Env, i: &ast::Item) {
 }
 
 fn existing_match(e: &Env, crate_id: &CrateId,
-                  hash: &str) -> Option<ast::CrateNum> {
+                  hash: Option<&Svh>) -> Option<ast::CrateNum> {
     let crate_cache = e.crate_cache.borrow();
     for c in crate_cache.get().iter() {
-        if crate_id.matches(&c.crate_id) &&
-           (hash.is_empty() || hash == c.hash.as_slice()) {
-            return Some(c.cnum)
+        if !crate_id.matches(&c.crate_id) { continue }
+        match hash {
+            Some(hash) if *hash != c.hash => {}
+            Some(..) | None => return Some(c.cnum)
         }
     }
     None
@@ -291,19 +294,22 @@ fn resolve_crate(e: &mut Env,
                  root_ident: Option<&str>,
                  ident: &str,
                  crate_id: &CrateId,
-                 hash: &str,
+                 hash: Option<&Svh>,
                  span: Span)
               -> ast::CrateNum {
     match existing_match(e, crate_id, hash) {
         None => {
-            let load_ctxt = loader::Context {
+            let id_hash = link::crate_id_hash(crate_id);
+            let mut load_ctxt = loader::Context {
                 sess: e.sess,
                 span: span,
                 ident: ident,
                 crate_id: crate_id,
-                hash: hash,
+                id_hash: id_hash,
+                hash: hash.map(|a| &*a),
                 os: e.os,
-                intr: e.intr
+                intr: e.intr,
+                rejected_via_hash: false,
             };
             let loader::Library {
                 dylib, rlib, metadata
@@ -375,7 +381,7 @@ fn resolve_crate_deps(e: &mut Env,
         let local_cnum = resolve_crate(e, root_ident,
                                        dep.crate_id.name.as_slice(),
                                        &dep.crate_id,
-                                       dep.hash,
+                                       Some(&dep.hash),
                                        span);
         cnum_map.insert(extrn_cnum, local_cnum);
     }
@@ -406,7 +412,7 @@ impl CrateLoader for Loader {
     fn load_crate(&mut self, krate: &ast::ViewItem) -> MacroCrate {
         let info = extract_crate_info(&self.env, krate).unwrap();
         let cnum = resolve_crate(&mut self.env, None, info.ident,
-                                 &info.crate_id, "", krate.span);
+                                 &info.crate_id, None, krate.span);
         let library = self.env.sess.cstore.get_used_crate_source(cnum).unwrap();
         MacroCrate {
             lib: library.dylib,
diff --git a/src/librustc/metadata/cstore.rs b/src/librustc/metadata/cstore.rs
index 12461ddbe71..baca85d50ae 100644
--- a/src/librustc/metadata/cstore.rs
+++ b/src/librustc/metadata/cstore.rs
@@ -13,6 +13,7 @@
 // The crate store - a central repo for information collected about external
 // crates and libraries
 
+use back::svh::Svh;
 use metadata::decoder;
 use metadata::loader;
 
@@ -92,7 +93,7 @@ impl CStore {
         *metas.get().get(&cnum)
     }
 
-    pub fn get_crate_hash(&self, cnum: ast::CrateNum) -> ~str {
+    pub fn get_crate_hash(&self, cnum: ast::CrateNum) -> Svh {
         let cdata = self.get_crate_data(cnum);
         decoder::get_crate_hash(cdata.data())
     }
diff --git a/src/librustc/metadata/decoder.rs b/src/librustc/metadata/decoder.rs
index f365ddef94f..42754aedba7 100644
--- a/src/librustc/metadata/decoder.rs
+++ b/src/librustc/metadata/decoder.rs
@@ -12,6 +12,7 @@
 
 #[allow(non_camel_case_types)];
 
+use back::svh::Svh;
 use metadata::cstore::crate_metadata;
 use metadata::common::*;
 use metadata::csearch::StaticMethodInfo;
@@ -1089,9 +1090,9 @@ fn get_attributes(md: ebml::Doc) -> ~[ast::Attribute] {
     return attrs;
 }
 
-fn list_crate_attributes(md: ebml::Doc, hash: &str,
+fn list_crate_attributes(md: ebml::Doc, hash: &Svh,
                          out: &mut io::Writer) -> io::IoResult<()> {
-    try!(write!(out, "=Crate Attributes ({})=\n", hash));
+    try!(write!(out, "=Crate Attributes ({})=\n", *hash));
 
     let r = get_attributes(md);
     for attr in r.iter() {
@@ -1109,7 +1110,7 @@ pub fn get_crate_attributes(data: &[u8]) -> ~[ast::Attribute] {
 pub struct CrateDep {
     cnum: ast::CrateNum,
     crate_id: CrateId,
-    hash: ~str,
+    hash: Svh,
 }
 
 pub fn get_crate_deps(data: &[u8]) -> ~[CrateDep] {
@@ -1123,7 +1124,7 @@ pub fn get_crate_deps(data: &[u8]) -> ~[CrateDep] {
     }
     reader::tagged_docs(depsdoc, tag_crate_dep, |depdoc| {
         let crate_id = from_str(docstr(depdoc, tag_crate_dep_crateid)).unwrap();
-        let hash = docstr(depdoc, tag_crate_dep_hash);
+        let hash = Svh::new(docstr(depdoc, tag_crate_dep_hash));
         deps.push(CrateDep {
             cnum: crate_num,
             crate_id: crate_id,
@@ -1144,10 +1145,10 @@ fn list_crate_deps(data: &[u8], out: &mut io::Writer) -> io::IoResult<()> {
     Ok(())
 }
 
-pub fn get_crate_hash(data: &[u8]) -> ~str {
+pub fn get_crate_hash(data: &[u8]) -> Svh {
     let cratedoc = reader::Doc(data);
     let hashdoc = reader::get_doc(cratedoc, tag_crate_hash);
-    hashdoc.as_str_slice().to_str()
+    Svh::new(hashdoc.as_str_slice())
 }
 
 pub fn get_crate_id(data: &[u8]) -> CrateId {
@@ -1159,7 +1160,7 @@ pub fn get_crate_id(data: &[u8]) -> CrateId {
 pub fn list_crate_metadata(bytes: &[u8], out: &mut io::Writer) -> io::IoResult<()> {
     let hash = get_crate_hash(bytes);
     let md = reader::Doc(bytes);
-    try!(list_crate_attributes(md, hash, out));
+    try!(list_crate_attributes(md, &hash, out));
     list_crate_deps(bytes, out)
 }
 
diff --git a/src/librustc/metadata/encoder.rs b/src/librustc/metadata/encoder.rs
index 976c1ee92d3..5bcc113ef94 100644
--- a/src/librustc/metadata/encoder.rs
+++ b/src/librustc/metadata/encoder.rs
@@ -13,6 +13,7 @@
 #[allow(unused_must_use)]; // everything is just a MemWriter, can't fail
 #[allow(non_camel_case_types)];
 
+use back::svh::Svh;
 use metadata::common::*;
 use metadata::cstore;
 use metadata::decoder;
@@ -1733,14 +1734,14 @@ fn encode_crate_dep(ebml_w: &mut writer::Encoder,
     ebml_w.writer.write(dep.crate_id.to_str().as_bytes());
     ebml_w.end_tag();
     ebml_w.start_tag(tag_crate_dep_hash);
-    ebml_w.writer.write(dep.hash.as_bytes());
+    ebml_w.writer.write(dep.hash.as_str().as_bytes());
     ebml_w.end_tag();
     ebml_w.end_tag();
 }
 
-fn encode_hash(ebml_w: &mut writer::Encoder, hash: &str) {
+fn encode_hash(ebml_w: &mut writer::Encoder, hash: &Svh) {
     ebml_w.start_tag(tag_crate_hash);
-    ebml_w.writer.write(hash.as_bytes());
+    ebml_w.writer.write(hash.as_str().as_bytes());
     ebml_w.end_tag();
 }
 
@@ -1809,7 +1810,7 @@ fn encode_metadata_inner(wr: &mut MemWriter, parms: EncodeParams, krate: &Crate)
     let mut ebml_w = writer::Encoder(wr);
 
     encode_crate_id(&mut ebml_w, &ecx.link_meta.crateid);
-    encode_hash(&mut ebml_w, ecx.link_meta.crate_hash);
+    encode_hash(&mut ebml_w, &ecx.link_meta.crate_hash);
 
     let mut i = ebml_w.writer.tell().unwrap();
     let crate_attrs = synthesize_crate_attrs(&ecx, krate);
diff --git a/src/librustc/metadata/loader.rs b/src/librustc/metadata/loader.rs
index faef2412e78..9c61191ff99 100644
--- a/src/librustc/metadata/loader.rs
+++ b/src/librustc/metadata/loader.rs
@@ -11,6 +11,7 @@
 //! Finds crate binaries and loads their metadata
 
 use back::archive::{ArchiveRO, METADATA_FILENAME};
+use back::svh::Svh;
 use driver::session::Session;
 use lib::llvm::{False, llvm, ObjectFile, mk_section_iter};
 use metadata::cstore::{MetadataBlob, MetadataVec, MetadataArchive};
@@ -21,7 +22,6 @@ use syntax::codemap::Span;
 use syntax::diagnostic::SpanHandler;
 use syntax::parse::token::IdentInterner;
 use syntax::crateid::CrateId;
-use syntax::attr;
 use syntax::attr::AttrMetaMethods;
 
 use std::c_str::ToCStr;
@@ -49,9 +49,11 @@ pub struct Context<'a> {
     span: Span,
     ident: &'a str,
     crate_id: &'a CrateId,
-    hash: &'a str,
+    id_hash: &'a str,
+    hash: Option<&'a Svh>,
     os: Os,
-    intr: @IdentInterner
+    intr: @IdentInterner,
+    rejected_via_hash: bool,
 }
 
 pub struct Library {
@@ -79,23 +81,34 @@ fn realpath(p: &Path) -> Path {
 }
 
 impl<'a> Context<'a> {
-    pub fn load_library_crate(&self, root_ident: Option<&str>) -> Library {
+    pub fn load_library_crate(&mut self, root_ident: Option<&str>) -> Library {
         match self.find_library_crate() {
             Some(t) => t,
             None => {
                 self.sess.abort_if_errors();
+                let message = if self.rejected_via_hash {
+                    format!("found possibly newer version of crate `{}`",
+                            self.ident)
+                } else {
+                    format!("can't find crate for `{}`", self.ident)
+                };
                 let message = match root_ident {
-                    None => format!("can't find crate for `{}`", self.ident),
-                    Some(c) => format!("can't find crate for `{}` which `{}` depends on",
-                                       self.ident,
-                                       c)
+                    None => message,
+                    Some(c) => format!("{} which `{}` depends on", message, c),
                 };
-                self.sess.span_fatal(self.span, message);
+                self.sess.span_err(self.span, message);
+
+                if self.rejected_via_hash {
+                    self.sess.span_note(self.span, "perhaps this crate needs \
+                                                    to be recompiled?");
+                }
+                self.sess.abort_if_errors();
+                unreachable!()
             }
         }
     }
 
-    fn find_library_crate(&self) -> Option<Library> {
+    fn find_library_crate(&mut self) -> Option<Library> {
         let filesearch = self.sess.filesearch;
         let (dyprefix, dysuffix) = self.dylibname();
 
@@ -212,13 +225,8 @@ impl<'a> Context<'a> {
                         None => {}
                     }
                     let data = lib.metadata.as_slice();
-                    let attrs = decoder::get_crate_attributes(data);
-                    match attr::find_crateid(attrs) {
-                        None => {}
-                        Some(crateid) => {
-                            note_crateid_attr(self.sess.diagnostic(), &crateid);
-                        }
-                    }
+                    let crate_id = decoder::get_crate_id(data);
+                    note_crateid_attr(self.sess.diagnostic(), &crate_id);
                 }
                 None
             }
@@ -240,18 +248,21 @@ impl<'a> Context<'a> {
         debug!("matching -- {}, middle: {}", file, middle);
         let mut parts = middle.splitn('-', 1);
         let hash = match parts.next() { Some(h) => h, None => return None };
-        debug!("matching -- {}, hash: {}", file, hash);
+        debug!("matching -- {}, hash: {} (want {})", file, hash, self.id_hash);
         let vers = match parts.next() { Some(v) => v, None => return None };
-        debug!("matching -- {}, vers: {}", file, vers);
+        debug!("matching -- {}, vers: {} (want {})", file, vers,
+               self.crate_id.version);
         match self.crate_id.version {
             Some(ref version) if version.as_slice() != vers => return None,
-            Some(..) | None => {}
+            Some(..) => {} // check the hash
+
+            // hash is irrelevant, no version specified
+            None => return Some(hash.to_owned())
         }
-        debug!("matching -- {}, vers ok (requested {})", file,
-               self.crate_id.version);
+        debug!("matching -- {}, vers ok", file);
         // hashes in filenames are prefixes of the "true hash"
-        if self.hash.is_empty() || self.hash.starts_with(hash) {
-            debug!("matching -- {}, hash ok (requested {})", file, self.hash);
+        if self.id_hash == hash.as_slice() {
+            debug!("matching -- {}, hash ok", file);
             Some(hash.to_owned())
         } else {
             None
@@ -270,7 +281,7 @@ impl<'a> Context<'a> {
     // FIXME(#10786): for an optimization, we only read one of the library's
     //                metadata sections. In theory we should read both, but
     //                reading dylib metadata is quite slow.
-    fn extract_one(&self, m: HashSet<Path>, flavor: &str,
+    fn extract_one(&mut self, m: HashSet<Path>, flavor: &str,
                    slot: &mut Option<MetadataBlob>) -> Option<Path> {
         if m.len() == 0 { return None }
         if m.len() > 1 {
@@ -290,7 +301,7 @@ impl<'a> Context<'a> {
             info!("{} reading meatadata from: {}", flavor, lib.display());
             match get_metadata_section(self.os, &lib) {
                 Some(blob) => {
-                    if crate_matches(blob.as_slice(), self.crate_id, self.hash){
+                    if self.crate_matches(blob.as_slice()) {
                         *slot = Some(blob);
                     } else {
                         info!("metadata mismatch");
@@ -306,6 +317,22 @@ impl<'a> Context<'a> {
         return Some(lib);
     }
 
+    fn crate_matches(&mut self, crate_data: &[u8]) -> bool {
+        let other_id = decoder::get_crate_id(crate_data);
+        if !self.crate_id.matches(&other_id) { return false }
+        match self.hash {
+            None => true,
+            Some(hash) => {
+                if *hash != decoder::get_crate_hash(crate_data) {
+                    self.rejected_via_hash = true;
+                    false
+                } else {
+                    true
+                }
+            }
+        }
+    }
+
     // Returns the corresponding (prefix, suffix) that files need to have for
     // dynamic libraries
     fn dylibname(&self) -> (&'static str, &'static str) {
@@ -323,15 +350,6 @@ pub fn note_crateid_attr(diag: @SpanHandler, crateid: &CrateId) {
     diag.handler().note(format!("crate_id: {}", crateid.to_str()));
 }
 
-fn crate_matches(crate_data: &[u8], crate_id: &CrateId, hash: &str) -> bool {
-    let other_id = decoder::get_crate_id(crate_data);
-    if !crate_id.matches(&other_id) { return false }
-    if hash != "" && hash != decoder::get_crate_hash(crate_data).as_slice() {
-        return false
-    }
-    return true;
-}
-
 impl ArchiveMetadata {
     fn new(ar: ArchiveRO) -> Option<ArchiveMetadata> {
         let data: &'static [u8] = {
diff --git a/src/librustc/middle/trans/base.rs b/src/librustc/middle/trans/base.rs
index 553dbe2ae6e..683246f3333 100644
--- a/src/librustc/middle/trans/base.rs
+++ b/src/librustc/middle/trans/base.rs
@@ -2453,7 +2453,8 @@ pub fn decl_crate_map(sess: session::Session, mapmeta: LinkMeta,
     let sym_name = if is_top {
         ~"_rust_crate_map_toplevel"
     } else {
-        symname("_rust_crate_map_" + mapmeta.crateid.name, mapmeta.crate_hash,
+        symname("_rust_crate_map_" + mapmeta.crateid.name,
+                mapmeta.crate_hash.as_str(),
                 mapmeta.crateid.version_or_default())
     };
 
@@ -2487,7 +2488,7 @@ pub fn fill_crate_map(ccx: @CrateContext, map: ValueRef) {
     while cstore.have_crate_data(i) {
         let cdata = cstore.get_crate_data(i);
         let nm = symname(format!("_rust_crate_map_{}", cdata.name),
-                         cstore.get_crate_hash(i),
+                         cstore.get_crate_hash(i).as_str(),
                          cstore.get_crate_id(i).version_or_default());
         let cr = nm.with_c_str(|buf| {
             unsafe {
@@ -2609,9 +2610,7 @@ pub fn trans_crate(sess: session::Session,
         }
     }
 
-    let mut symbol_hasher = Sha256::new();
-    let link_meta = link::build_link_meta(krate.attrs, output,
-                                          &mut symbol_hasher);
+    let link_meta = link::build_link_meta(&krate, output);
 
     // Append ".rs" to crate name as LLVM module identifier.
     //
@@ -2621,16 +2620,16 @@ pub fn trans_crate(sess: session::Session,
     // crashes if the module identifer is same as other symbols
     // such as a function name in the module.
     // 1. http://llvm.org/bugs/show_bug.cgi?id=11479
-    let llmod_id = link_meta.crateid.name.clone() + ".rs";
+    let llmod_id = link_meta.crateid.name + ".rs";
 
     let ccx = @CrateContext::new(sess,
-                                     llmod_id,
-                                     analysis.ty_cx,
-                                     analysis.exp_map2,
-                                     analysis.maps,
-                                     symbol_hasher,
-                                     link_meta,
-                                     analysis.reachable);
+                                 llmod_id,
+                                 analysis.ty_cx,
+                                 analysis.exp_map2,
+                                 analysis.maps,
+                                 Sha256::new(),
+                                 link_meta,
+                                 analysis.reachable);
     {
         let _icx = push_ctxt("text");
         trans_mod(ccx, &krate.module);
diff --git a/src/librustc/middle/trans/intrinsic.rs b/src/librustc/middle/trans/intrinsic.rs
index 4abc114fef6..8350b24c451 100644
--- a/src/librustc/middle/trans/intrinsic.rs
+++ b/src/librustc/middle/trans/intrinsic.rs
@@ -329,7 +329,7 @@ pub fn trans_intrinsic(ccx: @CrateContext,
             let hash = ty::hash_crate_independent(
                 ccx.tcx,
                 substs.tys[0],
-                ccx.link_meta.crate_hash.clone());
+                &ccx.link_meta.crate_hash);
             // NB: This needs to be kept in lockstep with the TypeId struct in
             //     libstd/unstable/intrinsics.rs
             let val = C_named_struct(type_of::type_of(ccx, output_type), [C_u64(hash)]);
diff --git a/src/librustc/middle/ty.rs b/src/librustc/middle/ty.rs
index 6b26e268057..2ca19f0b61d 100644
--- a/src/librustc/middle/ty.rs
+++ b/src/librustc/middle/ty.rs
@@ -10,6 +10,7 @@
 
 #[allow(non_camel_case_types)];
 
+use back::svh::Svh;
 use driver::session;
 use metadata::csearch;
 use metadata;
@@ -4882,7 +4883,7 @@ pub fn trait_method_of_method(tcx: ctxt,
 
 /// Creates a hash of the type `t` which will be the same no matter what crate
 /// context it's calculated within. This is used by the `type_id` intrinsic.
-pub fn hash_crate_independent(tcx: ctxt, t: t, local_hash: ~str) -> u64 {
+pub fn hash_crate_independent(tcx: ctxt, t: t, svh: &Svh) -> u64 {
     let mut state = sip::SipState::new();
     macro_rules! byte( ($b:expr) => { ($b as u8).hash(&mut state) } );
     macro_rules! hash( ($e:expr) => { $e.hash(&mut state) } );
@@ -4913,11 +4914,11 @@ pub fn hash_crate_independent(tcx: ctxt, t: t, local_hash: ~str) -> u64 {
     };
     let did = |state: &mut sip::SipState, did: DefId| {
         let h = if ast_util::is_local(did) {
-            local_hash.clone()
+            svh.clone()
         } else {
             tcx.sess.cstore.get_crate_hash(did.krate)
         };
-        h.as_bytes().hash(state);
+        h.as_str().hash(state);
         did.node.hash(state);
     };
     let mt = |state: &mut sip::SipState, mt: mt| {
diff --git a/src/test/auxiliary/changing-crates-a1.rs b/src/test/auxiliary/changing-crates-a1.rs
new file mode 100644
index 00000000000..c0bdbf81772
--- /dev/null
+++ b/src/test/auxiliary/changing-crates-a1.rs
@@ -0,0 +1,13 @@
+// Copyright 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.
+
+#[crate_id = "a"];
+
+pub fn foo<T>() {}
diff --git a/src/test/auxiliary/changing-crates-a2.rs b/src/test/auxiliary/changing-crates-a2.rs
new file mode 100644
index 00000000000..cc123c0f65d
--- /dev/null
+++ b/src/test/auxiliary/changing-crates-a2.rs
@@ -0,0 +1,14 @@
+// Copyright 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.
+
+#[crate_id = "a"];
+
+pub fn foo<T>() { println!("hello!"); }
+
diff --git a/src/test/auxiliary/changing-crates-b.rs b/src/test/auxiliary/changing-crates-b.rs
new file mode 100644
index 00000000000..9b80583bb84
--- /dev/null
+++ b/src/test/auxiliary/changing-crates-b.rs
@@ -0,0 +1,15 @@
+// Copyright 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.
+
+#[crate_id = "b"];
+
+extern crate a;
+
+pub fn foo() { a::foo::<int>(); }
diff --git a/src/test/compile-fail/bad-crate-id.rs b/src/test/compile-fail/bad-crate-id.rs
new file mode 100644
index 00000000000..43956752cd9
--- /dev/null
+++ b/src/test/compile-fail/bad-crate-id.rs
@@ -0,0 +1,14 @@
+// Copyright 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.
+
+extern crate foo = ""; //~ ERROR: malformed crate id
+extern crate bar = "#a"; //~ ERROR: malformed crate id
+
+fn main() {}
diff --git a/src/test/compile-fail/changing-crates.rs b/src/test/compile-fail/changing-crates.rs
new file mode 100644
index 00000000000..ae3ef760667
--- /dev/null
+++ b/src/test/compile-fail/changing-crates.rs
@@ -0,0 +1,20 @@
+// Copyright 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.
+
+// note that these aux-build directives must be in this order
+// aux-build:changing-crates-a1.rs
+// aux-build:changing-crates-b.rs
+// aux-build:changing-crates-a2.rs
+
+extern crate a;
+extern crate b; //~ ERROR: found possibly newer version of crate `a` which `b` depends on
+//~^ NOTE: perhaps this crate needs to be recompiled
+
+fn main() {}