Skip to content

Commit 94d274d

Browse files
authored
feature(SourceId): use stable hash from rustc-stable-hash (#14917)
### What does this PR try to resolve? This helps `-Ztrim-paths` build a stable cross-platform path for the registry and git sources. Sources files then can be found from the same path when debugging. It also helps cache registry index all at once for all platforms, for example the use case in #14795 (despite they should use `cargo vendor` instead IMO). Some caveats: * Newer cargo will need to re-download files for global caches (index files, git/registry sources). The old cache is still kept and used when running with older cargoes. * Absolute paths on windows iarenot really covered by the "cross-platform" hash, because path prefix components like `C:` are always there. That means hashes of some sources kind, like local registry and local path, are not going to be real cross-platform stable. #### Security concern There might be hash collisions if you have two registries under the same domain. This won't happen to crates.io, as the infra would have to intentionally put another registry on index.crates.io to collide. We don't consider this is an actual threat model, so we are not going to use any cryptographically secure hash algorithm like BLAKE3. At least, the current unstable SipHash isn't in a better situation. We might switch to a cryptographic secure one when needed. See also <#13171 (comment)> ### How should we test and review this PR? We have an FCP in <#14795 (comment)>. This PR implements the proposal, The path-length concern in <#14795 (comment)> is automatically addressed because we don't need cryptographically secure hash for now. ### Additional information See more information and benchmark results in <#14116>.
2 parents 7ec959e + 5eb7480 commit 94d274d

File tree

11 files changed

+89
-68
lines changed

11 files changed

+89
-68
lines changed

Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ rand = "0.8.5"
8282
regex = "1.10.5"
8383
rusqlite = { version = "0.32.0", features = ["bundled"] }
8484
rustc-hash = "2.0.0"
85+
rustc-stable-hash = "0.1.1"
8586
rustfix = { version = "0.9.0", path = "crates/rustfix" }
8687
same-file = "1.0.6"
8788
schemars = "0.8.21"
@@ -194,6 +195,7 @@ rand.workspace = true
194195
regex.workspace = true
195196
rusqlite.workspace = true
196197
rustc-hash.workspace = true
198+
rustc-stable-hash.workspace = true
197199
rustfix.workspace = true
198200
same-file.workspace = true
199201
semver.workspace = true

src/cargo/core/compiler/build_runner/compilation_files.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -718,8 +718,8 @@ fn compute_metadata(
718718
}
719719
}
720720

721-
let c_metadata = UnitHash(c_metadata_hasher.finish());
722-
let c_extra_filename = UnitHash(c_extra_filename_hasher.finish());
721+
let c_metadata = UnitHash(Hasher::finish(&c_metadata_hasher));
722+
let c_extra_filename = UnitHash(Hasher::finish(&c_extra_filename_hasher));
723723
let unit_id = c_extra_filename;
724724

725725
let c_extra_filename = use_extra_filename.then_some(c_extra_filename);

src/cargo/core/compiler/compile_kind.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,6 @@ impl CompileTarget {
195195
self.name.hash(&mut hasher);
196196
}
197197
}
198-
hasher.finish()
198+
Hasher::finish(&hasher)
199199
}
200200
}

src/cargo/core/compiler/fingerprint/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1561,7 +1561,7 @@ fn calculate_normal(
15611561
local: Mutex::new(local),
15621562
memoized_hash: Mutex::new(None),
15631563
metadata,
1564-
config: config.finish(),
1564+
config: Hasher::finish(&config),
15651565
compile_kind,
15661566
rustflags: extra_flags,
15671567
fs_status: FsStatus::Stale,

src/cargo/core/source_id.rs

+55-36
Original file line numberDiff line numberDiff line change
@@ -782,70 +782,89 @@ mod tests {
782782
// Otherwise please just leave a comment in your PR as to why the hash value is
783783
// changing and why the old value can't be easily preserved.
784784
//
785-
// The hash value depends on endianness and bit-width, so we only run this test on
786-
// little-endian 64-bit CPUs (such as x86-64 and ARM64) where it matches the
787-
// well-known value.
785+
// The hash value should be stable across platforms, and doesn't depend on
786+
// endianness and bit-width. One caveat is that absolute paths on Windows
787+
// are inherently different than on Unix-like platforms. Unless we omit or
788+
// strip the prefix components (e.g. `C:`), there is not way to have a true
789+
// cross-platform stable hash for absolute paths.
788790
#[test]
789-
#[cfg(all(target_endian = "little", target_pointer_width = "64"))]
790-
fn test_cratesio_hash() {
791-
let gctx = GlobalContext::default().unwrap();
792-
let crates_io = SourceId::crates_io(&gctx).unwrap();
793-
assert_eq!(crate::util::hex::short_hash(&crates_io), "1ecc6299db9ec823");
794-
}
795-
796-
// See the comment in `test_cratesio_hash`.
797-
//
798-
// Only test on non-Windows as paths on Windows will get different hashes.
799-
#[test]
800-
#[cfg(all(target_endian = "little", target_pointer_width = "64", not(windows)))]
801791
fn test_stable_hash() {
802792
use std::hash::Hasher;
803793
use std::path::Path;
804794

795+
use crate::util::StableHasher;
796+
797+
#[cfg(not(windows))]
798+
let ws_root = Path::new("/tmp/ws");
799+
#[cfg(windows)]
800+
let ws_root = Path::new(r"C:\\tmp\ws");
801+
805802
let gen_hash = |source_id: SourceId| {
806-
let mut hasher = std::collections::hash_map::DefaultHasher::new();
807-
source_id.stable_hash(Path::new("/tmp/ws"), &mut hasher);
808-
hasher.finish()
803+
let mut hasher = StableHasher::new();
804+
source_id.stable_hash(ws_root, &mut hasher);
805+
Hasher::finish(&hasher)
809806
};
810807

808+
let source_id = SourceId::crates_io(&GlobalContext::default().unwrap()).unwrap();
809+
assert_eq!(gen_hash(source_id), 7062945687441624357);
810+
assert_eq!(crate::util::hex::short_hash(&source_id), "25cdd57fae9f0462");
811+
811812
let url = "https://my-crates.io".into_url().unwrap();
812813
let source_id = SourceId::for_registry(&url).unwrap();
813-
assert_eq!(gen_hash(source_id), 18108075011063494626);
814-
assert_eq!(crate::util::hex::short_hash(&source_id), "fb60813d6cb8df79");
814+
assert_eq!(gen_hash(source_id), 8310250053664888498);
815+
assert_eq!(crate::util::hex::short_hash(&source_id), "b2d65deb64f05373");
815816

816817
let url = "https://your-crates.io".into_url().unwrap();
817818
let source_id = SourceId::for_alt_registry(&url, "alt").unwrap();
818-
assert_eq!(gen_hash(source_id), 12862859764592646184);
819-
assert_eq!(crate::util::hex::short_hash(&source_id), "09c10fd0cbd74bce");
819+
assert_eq!(gen_hash(source_id), 14149534903000258933);
820+
assert_eq!(crate::util::hex::short_hash(&source_id), "755952de063f5dc4");
820821

821822
let url = "sparse+https://my-crates.io".into_url().unwrap();
822823
let source_id = SourceId::for_registry(&url).unwrap();
823-
assert_eq!(gen_hash(source_id), 8763561830438022424);
824-
assert_eq!(crate::util::hex::short_hash(&source_id), "d1ea0d96f6f759b5");
824+
assert_eq!(gen_hash(source_id), 16249512552851930162);
825+
assert_eq!(crate::util::hex::short_hash(&source_id), "327cfdbd92dd81e1");
825826

826827
let url = "sparse+https://your-crates.io".into_url().unwrap();
827828
let source_id = SourceId::for_alt_registry(&url, "alt").unwrap();
828-
assert_eq!(gen_hash(source_id), 5159702466575482972);
829-
assert_eq!(crate::util::hex::short_hash(&source_id), "135d23074253cb78");
829+
assert_eq!(gen_hash(source_id), 6156697384053352292);
830+
assert_eq!(crate::util::hex::short_hash(&source_id), "64a713b6a6fb7055");
830831

831832
let url = "file:///tmp/ws/crate".into_url().unwrap();
832833
let source_id = SourceId::for_git(&url, GitReference::DefaultBranch).unwrap();
833-
assert_eq!(gen_hash(source_id), 15332537265078583985);
834-
assert_eq!(crate::util::hex::short_hash(&source_id), "73a808694abda756");
835-
836-
let path = Path::new("/tmp/ws/crate");
834+
assert_eq!(gen_hash(source_id), 473480029881867801);
835+
assert_eq!(crate::util::hex::short_hash(&source_id), "199e591d94239206");
837836

837+
let path = &ws_root.join("crate");
838838
let source_id = SourceId::for_local_registry(path).unwrap();
839-
assert_eq!(gen_hash(source_id), 18446533307730842837);
840-
assert_eq!(crate::util::hex::short_hash(&source_id), "52a84cc73f6fd48b");
839+
#[cfg(not(windows))]
840+
{
841+
assert_eq!(gen_hash(source_id), 11515846423845066584);
842+
assert_eq!(crate::util::hex::short_hash(&source_id), "58d73c154f81d09f");
843+
}
844+
#[cfg(windows)]
845+
{
846+
assert_eq!(gen_hash(source_id), 6146331155906064276);
847+
assert_eq!(crate::util::hex::short_hash(&source_id), "946fb2239f274c55");
848+
}
841849

842850
let source_id = SourceId::for_path(path).unwrap();
843-
assert_eq!(gen_hash(source_id), 8764714075439899829);
844-
assert_eq!(crate::util::hex::short_hash(&source_id), "e1ddd48578620fc1");
851+
assert_eq!(gen_hash(source_id), 215644081443634269);
852+
#[cfg(not(windows))]
853+
assert_eq!(crate::util::hex::short_hash(&source_id), "64bace89c92b101f");
854+
#[cfg(windows)]
855+
assert_eq!(crate::util::hex::short_hash(&source_id), "01e1e6c391813fb6");
845856

846857
let source_id = SourceId::for_directory(path).unwrap();
847-
assert_eq!(gen_hash(source_id), 17459999773908528552);
848-
assert_eq!(crate::util::hex::short_hash(&source_id), "6568fe2c2fab5bfe");
858+
#[cfg(not(windows))]
859+
{
860+
assert_eq!(gen_hash(source_id), 6127590343904940368);
861+
assert_eq!(crate::util::hex::short_hash(&source_id), "505191d1f3920955");
862+
}
863+
#[cfg(windows)]
864+
{
865+
assert_eq!(gen_hash(source_id), 10423446877655960172);
866+
assert_eq!(crate::util::hex::short_hash(&source_id), "6c8ad69db585a790");
867+
}
849868
}
850869

851870
#[test]

src/cargo/ops/cargo_compile/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,7 @@ fn traverse_and_share(
657657
.collect();
658658
// Here, we have recursively traversed this unit's dependencies, and hashed them: we can
659659
// finalize the dep hash.
660-
let new_dep_hash = dep_hash.finish();
660+
let new_dep_hash = Hasher::finish(&dep_hash);
661661

662662
// This is the key part of the sharing process: if the unit is a runtime dependency, whose
663663
// target is the same as the host, we canonicalize the compile kind to `CompileKind::Host`.

src/cargo/util/hasher.rs

+2-21
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,6 @@
1-
//! Implementation of a hasher that produces the same values across releases.
1+
//! A hasher that produces the same values across releases and platforms.
22
//!
33
//! The hasher should be fast and have a low chance of collisions (but is not
44
//! sufficient for cryptographic purposes).
5-
#![allow(deprecated)]
65
7-
use std::hash::{Hasher, SipHasher};
8-
9-
#[derive(Clone)]
10-
pub struct StableHasher(SipHasher);
11-
12-
impl StableHasher {
13-
pub fn new() -> StableHasher {
14-
StableHasher(SipHasher::new())
15-
}
16-
}
17-
18-
impl Hasher for StableHasher {
19-
fn finish(&self) -> u64 {
20-
self.0.finish()
21-
}
22-
fn write(&mut self, bytes: &[u8]) {
23-
self.0.write(bytes)
24-
}
25-
}
6+
pub use rustc_stable_hash::StableSipHasher128 as StableHasher;

src/cargo/util/hex.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub fn to_hex(num: u64) -> String {
1010
pub fn hash_u64<H: Hash>(hashable: H) -> u64 {
1111
let mut hasher = StableHasher::new();
1212
hashable.hash(&mut hasher);
13-
hasher.finish()
13+
Hasher::finish(&hasher)
1414
}
1515

1616
pub fn hash_u64_file(mut file: &File) -> std::io::Result<u64> {
@@ -23,7 +23,7 @@ pub fn hash_u64_file(mut file: &File) -> std::io::Result<u64> {
2323
}
2424
hasher.write(&buf[..n]);
2525
}
26-
Ok(hasher.finish())
26+
Ok(Hasher::finish(&hasher))
2727
}
2828

2929
pub fn short_hash<H: Hash>(hashable: &H) -> String {

src/cargo/util/rustc.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ fn rustc_fingerprint(
381381
_ => (),
382382
}
383383

384-
Ok(hasher.finish())
384+
Ok(Hasher::finish(&hasher))
385385
}
386386

387387
fn process_fingerprint(cmd: &ProcessBuilder, extra_fingerprint: u64) -> u64 {
@@ -391,5 +391,5 @@ fn process_fingerprint(cmd: &ProcessBuilder, extra_fingerprint: u64) -> u64 {
391391
let mut env = cmd.get_envs().iter().collect::<Vec<_>>();
392392
env.sort_unstable();
393393
env.hash(&mut hasher);
394-
hasher.finish()
394+
Hasher::finish(&hasher)
395395
}

tests/testsuite/global_cache_tracker.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -2004,7 +2004,16 @@ fn compatible_with_older_cargo() {
20042004
assert_eq!(get_registry_names("src"), ["middle-1.0.0", "new-1.0.0"]);
20052005
assert_eq!(
20062006
get_registry_names("cache"),
2007-
["middle-1.0.0.crate", "new-1.0.0.crate", "old-1.0.0.crate"]
2007+
// Duplicate crates from two different cache location
2008+
// because we're changing how SourceId is hashed.
2009+
// This change should be reverted once rust-lang/cargo#14917 lands.
2010+
[
2011+
"middle-1.0.0.crate",
2012+
"middle-1.0.0.crate",
2013+
"new-1.0.0.crate",
2014+
"new-1.0.0.crate",
2015+
"old-1.0.0.crate"
2016+
]
20082017
);
20092018

20102019
// T-0 months: Current version, make sure it can read data from stable,
@@ -2027,7 +2036,10 @@ fn compatible_with_older_cargo() {
20272036
assert_eq!(get_registry_names("src"), ["new-1.0.0"]);
20282037
assert_eq!(
20292038
get_registry_names("cache"),
2030-
["middle-1.0.0.crate", "new-1.0.0.crate"]
2039+
// Duplicate crates from two different cache location
2040+
// because we're changing how SourceId is hashed.
2041+
// This change should be reverted once rust-lang/cargo#14917 lands.
2042+
["middle-1.0.0.crate", "new-1.0.0.crate", "new-1.0.0.crate"]
20312043
);
20322044
}
20332045

0 commit comments

Comments
 (0)