From 6a11d0be8996ac56aa6b3d6905bbc99a1a0ecca5 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:54:22 +0000 Subject: [PATCH 1/8] Don't leak sysroot crates through dependencies Previously if a dependency of the current crate depended on a sysroot crate, then extern crate would in the current crate would pick the first loaded version of said sysroot crate even in case of an ambiguity. This is surprising and brittle. For -Ldependency= we already blocked this, but the fix didn't account for sysroot crates. --- compiler/rustc_metadata/src/creader.rs | 29 ++++---------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 4e2e1e21ec6dd..85fa8cda22292 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -519,7 +519,6 @@ impl CStore { externs: &Externs, name: Symbol, hash: Option, - kind: PathKind, ) -> Option { for (cnum, data) in self.iter_crate_data() { if data.name() != name { @@ -561,27 +560,9 @@ impl CStore { continue; } - // Alright, so we've gotten this far which means that `data` has the - // right name, we don't have a hash, and we don't have a --extern - // pointing for ourselves. We're still not quite yet done because we - // have to make sure that this crate was found in the crate lookup - // path (this is a top-level dependency) as we don't want to - // implicitly load anything inside the dependency lookup path. - let prev_kind = source - .dylib - .as_ref() - .or(source.rlib.as_ref()) - .or(source.rmeta.as_ref()) - .expect("No sources for crate") - .1; - if kind.matches(prev_kind) { - return Some(cnum); - } else { - debug!( - "failed to load existing crate {}; kind {:?} did not match prev_kind {:?}", - name, kind, prev_kind - ); - } + // While the crate name matched, no --extern crate_name=path matched. It is possible + // that we have already loaded the target crate, but if that happens CStore::load will + // indicate so and we gracefully handle this, just potentially wasting a bit of time. } None @@ -818,9 +799,7 @@ impl CStore { let path_kind = if dep.is_some() { PathKind::Dependency } else { PathKind::Crate }; let private_dep = origin.private_dep(); - let result = if let Some(cnum) = - self.existing_match(&tcx.sess.opts.externs, name, hash, path_kind) - { + let result = if let Some(cnum) = self.existing_match(&tcx.sess.opts.externs, name, hash) { (LoadResult::Previous(cnum), None) } else { info!("falling back to a load"); From 00ec159c71740764de2c4d39744e99cdb8c9572c Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Mon, 24 Nov 2025 15:07:32 +0000 Subject: [PATCH 2/8] Remove PathKind from CrateSource It is never used anywhere anymore now that existing_matches doesn't use it anymore. And there is no good reason for any other code to use it in the future either. --- compiler/rustc_codegen_ssa/src/back/link.rs | 18 +++----- compiler/rustc_interface/src/passes.rs | 6 +-- compiler/rustc_metadata/src/creader.rs | 16 +++---- compiler/rustc_metadata/src/locator.rs | 50 ++++++++++----------- compiler/rustc_session/src/cstore.rs | 12 +++-- compiler/rustc_session/src/search_paths.rs | 1 - 6 files changed, 47 insertions(+), 56 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 820f7ba4a6f23..dd497667a4d9f 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -278,7 +278,7 @@ pub fn each_linked_rlib( } let crate_name = info.crate_name[&cnum]; let used_crate_source = &info.used_crate_source[&cnum]; - if let Some((path, _)) = &used_crate_source.rlib { + if let Some(path) = &used_crate_source.rlib { f(cnum, path); } else if used_crate_source.rmeta.is_some() { return Err(errors::LinkRlibError::OnlyRmetaFound { crate_name }); @@ -542,7 +542,7 @@ fn link_staticlib( }; let crate_name = codegen_results.crate_info.crate_name[&cnum]; let used_crate_source = &codegen_results.crate_info.used_crate_source[&cnum]; - if let Some((path, _)) = &used_crate_source.dylib { + if let Some(path) = &used_crate_source.dylib { all_rust_dylibs.push(&**path); } else if used_crate_source.rmeta.is_some() { sess.dcx().emit_fatal(errors::LinkRlibError::OnlyRmetaFound { crate_name }); @@ -620,7 +620,6 @@ fn link_dwarf_object(sess: &Session, cg_results: &CodegenResults, executable_out .used_crate_source .items() .filter_map(|(_, csource)| csource.rlib.as_ref()) - .map(|(path, _)| path) .into_sorted_stable_ord(); for input_rlib in input_rlibs { @@ -2178,12 +2177,7 @@ fn add_rpath_args( .crate_info .used_crates .iter() - .filter_map(|cnum| { - codegen_results.crate_info.used_crate_source[cnum] - .dylib - .as_ref() - .map(|(path, _)| &**path) - }) + .filter_map(|cnum| codegen_results.crate_info.used_crate_source[cnum].dylib.as_deref()) .collect::>(); let rpath_config = RPathConfig { libs: &*libs, @@ -2661,7 +2655,7 @@ fn add_native_libs_from_crate( if link_static && cnum != LOCAL_CRATE && !bundled_libs.is_empty() { // If rlib contains native libs as archives, unpack them to tmpdir. - let rlib = &codegen_results.crate_info.used_crate_source[&cnum].rlib.as_ref().unwrap().0; + let rlib = codegen_results.crate_info.used_crate_source[&cnum].rlib.as_ref().unwrap(); archive_builder_builder .extract_bundled_libs(rlib, tmpdir, bundled_libs) .unwrap_or_else(|e| sess.dcx().emit_fatal(e)); @@ -2832,7 +2826,7 @@ fn add_upstream_rust_crates( } Linkage::Dynamic => { let src = &codegen_results.crate_info.used_crate_source[&cnum]; - add_dynamic_crate(cmd, sess, &src.dylib.as_ref().unwrap().0); + add_dynamic_crate(cmd, sess, src.dylib.as_ref().unwrap()); } } @@ -2960,7 +2954,7 @@ fn add_static_crate( bundled_lib_file_names: &FxIndexSet, ) { let src = &codegen_results.crate_info.used_crate_source[&cnum]; - let cratepath = &src.rlib.as_ref().unwrap().0; + let cratepath = src.rlib.as_ref().unwrap(); let mut link_upstream = |path: &Path| cmd.link_staticlib_by_path(&rehome_lib_path(sess, path), false); diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index ddfec9f886a6a..b7273dd695614 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -684,19 +684,19 @@ fn write_out_deps(tcx: TyCtxt<'_>, outputs: &OutputFilenames, out_filenames: &[P for &cnum in tcx.crates(()) { let source = tcx.used_crate_source(cnum); - if let Some((path, _)) = &source.dylib { + if let Some(path) = &source.dylib { files.extend(hash_iter_files( iter::once(escape_dep_filename(&path.display().to_string())), checksum_hash_algo, )); } - if let Some((path, _)) = &source.rlib { + if let Some(path) = &source.rlib { files.extend(hash_iter_files( iter::once(escape_dep_filename(&path.display().to_string())), checksum_hash_algo, )); } - if let Some((path, _)) = &source.rmeta { + if let Some(path) = &source.rmeta { files.extend(hash_iter_files( iter::once(escape_dep_filename(&path.display().to_string())), checksum_hash_algo, diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 85fa8cda22292..250aa0769024b 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -135,16 +135,16 @@ impl<'a> std::fmt::Debug for CrateDump<'a> { writeln!(fmt, " priv: {:?}", data.is_private_dep())?; let CrateSource { dylib, rlib, rmeta, sdylib_interface } = data.source(); if let Some(dylib) = dylib { - writeln!(fmt, " dylib: {}", dylib.0.display())?; + writeln!(fmt, " dylib: {}", dylib.display())?; } if let Some(rlib) = rlib { - writeln!(fmt, " rlib: {}", rlib.0.display())?; + writeln!(fmt, " rlib: {}", rlib.display())?; } if let Some(rmeta) = rmeta { - writeln!(fmt, " rmeta: {}", rmeta.0.display())?; + writeln!(fmt, " rmeta: {}", rmeta.display())?; } if let Some(sdylib_interface) = sdylib_interface { - writeln!(fmt, " sdylib interface: {}", sdylib_interface.0.display())?; + writeln!(fmt, " sdylib interface: {}", sdylib_interface.display())?; } } Ok(()) @@ -550,9 +550,9 @@ impl CStore { if let Some(mut files) = entry.files() { if files.any(|l| { let l = l.canonicalized(); - source.dylib.as_ref().map(|(p, _)| p) == Some(l) - || source.rlib.as_ref().map(|(p, _)| p) == Some(l) - || source.rmeta.as_ref().map(|(p, _)| p) == Some(l) + source.dylib.as_ref() == Some(l) + || source.rlib.as_ref() == Some(l) + || source.rmeta.as_ref() == Some(l) }) { return Some(cnum); } @@ -658,7 +658,7 @@ impl CStore { None => (&source, &crate_root), }; let dlsym_dylib = dlsym_source.dylib.as_ref().expect("no dylib for a proc-macro crate"); - Some(self.dlsym_proc_macros(tcx.sess, &dlsym_dylib.0, dlsym_root.stable_crate_id())?) + Some(self.dlsym_proc_macros(tcx.sess, dlsym_dylib, dlsym_root.stable_crate_id())?) } else { None }; diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs index 9fef22f9558dd..c4083a27e72b2 100644 --- a/compiler/rustc_metadata/src/locator.rs +++ b/compiler/rustc_metadata/src/locator.rs @@ -218,7 +218,7 @@ use std::ops::Deref; use std::path::{Path, PathBuf}; use std::{cmp, fmt}; -use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; +use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_data_structures::memmap::Mmap; use rustc_data_structures::owned_slice::{OwnedSlice, slice_owned}; use rustc_data_structures::svh::Svh; @@ -401,7 +401,7 @@ impl<'a> CrateLocator<'a> { let mut candidates: FxIndexMap< _, - (FxIndexMap<_, _>, FxIndexMap<_, _>, FxIndexMap<_, _>, FxIndexMap<_, _>), + (FxIndexSet<_>, FxIndexSet<_>, FxIndexSet<_>, FxIndexSet<_>), > = Default::default(); // First, find all possible candidate rlibs and dylibs purely based on @@ -460,10 +460,10 @@ impl<'a> CrateLocator<'a> { // filesystem code should not care, but this is nicer for diagnostics. let path = spf.path.to_path_buf(); match kind { - CrateFlavor::Rlib => rlibs.insert(path, search_path.kind), - CrateFlavor::Rmeta => rmetas.insert(path, search_path.kind), - CrateFlavor::Dylib => dylibs.insert(path, search_path.kind), - CrateFlavor::SDylib => interfaces.insert(path, search_path.kind), + CrateFlavor::Rlib => rlibs.insert(path), + CrateFlavor::Rmeta => rmetas.insert(path), + CrateFlavor::Dylib => dylibs.insert(path), + CrateFlavor::SDylib => interfaces.insert(path), }; } } @@ -524,10 +524,10 @@ impl<'a> CrateLocator<'a> { fn extract_lib( &self, crate_rejections: &mut CrateRejections, - rlibs: FxIndexMap, - rmetas: FxIndexMap, - dylibs: FxIndexMap, - interfaces: FxIndexMap, + rlibs: FxIndexSet, + rmetas: FxIndexSet, + dylibs: FxIndexSet, + interfaces: FxIndexSet, ) -> Result, CrateError> { let mut slot = None; // Order here matters, rmeta should come first. @@ -575,10 +575,10 @@ impl<'a> CrateLocator<'a> { fn extract_one( &self, crate_rejections: &mut CrateRejections, - m: FxIndexMap, + m: FxIndexSet, flavor: CrateFlavor, slot: &mut Option<(Svh, MetadataBlob, PathBuf, CrateFlavor)>, - ) -> Result, CrateError> { + ) -> Result, CrateError> { // If we are producing an rlib, and we've already loaded metadata, then // we should not attempt to discover further crate sources (unless we're // locating a proc macro; exact logic is in needs_crate_flavor). This means @@ -594,9 +594,9 @@ impl<'a> CrateLocator<'a> { } } - let mut ret: Option<(PathBuf, PathKind)> = None; + let mut ret: Option = None; let mut err_data: Option> = None; - for (lib, kind) in m { + for lib in m { info!("{} reading metadata from: {}", flavor, lib.display()); if flavor == CrateFlavor::Rmeta && lib.metadata().is_ok_and(|m| m.len() == 0) { // Empty files will cause get_metadata_section to fail. Rmeta @@ -640,7 +640,7 @@ impl<'a> CrateLocator<'a> { info!("no metadata found: {}", err); // Metadata was loaded from interface file earlier. if let Some((.., CrateFlavor::SDylib)) = slot { - ret = Some((lib, kind)); + ret = Some(lib); continue; } // The file was present and created by the same compiler version, but we @@ -689,7 +689,7 @@ impl<'a> CrateLocator<'a> { // As a result, we favor the sysroot crate here. Note that the // candidates are all canonicalized, so we canonicalize the sysroot // as well. - if let Some((prev, _)) = &ret { + if let Some(prev) = &ret { let sysroot = self.sysroot; let sysroot = try_canonicalize(sysroot).unwrap_or_else(|_| sysroot.to_path_buf()); if prev.starts_with(&sysroot) { @@ -714,7 +714,7 @@ impl<'a> CrateLocator<'a> { } else { *slot = Some((hash, metadata, lib.clone(), flavor)); } - ret = Some((lib, kind)); + ret = Some(lib); } if let Some(candidates) = err_data { @@ -774,10 +774,10 @@ impl<'a> CrateLocator<'a> { // First, filter out all libraries that look suspicious. We only accept // files which actually exist that have the correct naming scheme for // rlibs/dylibs. - let mut rlibs = FxIndexMap::default(); - let mut rmetas = FxIndexMap::default(); - let mut dylibs = FxIndexMap::default(); - let mut sdylib_interfaces = FxIndexMap::default(); + let mut rlibs = FxIndexSet::default(); + let mut rmetas = FxIndexSet::default(); + let mut dylibs = FxIndexSet::default(); + let mut sdylib_interfaces = FxIndexSet::default(); for loc in &self.exact_paths { let loc_canon = loc.canonicalized(); let loc_orig = loc.original(); @@ -798,21 +798,21 @@ impl<'a> CrateLocator<'a> { }; if file.starts_with("lib") { if file.ends_with(".rlib") { - rlibs.insert(loc_canon.clone(), PathKind::ExternFlag); + rlibs.insert(loc_canon.clone()); continue; } if file.ends_with(".rmeta") { - rmetas.insert(loc_canon.clone(), PathKind::ExternFlag); + rmetas.insert(loc_canon.clone()); continue; } if file.ends_with(".rs") { - sdylib_interfaces.insert(loc_canon.clone(), PathKind::ExternFlag); + sdylib_interfaces.insert(loc_canon.clone()); } } let dll_prefix = self.target.dll_prefix.as_ref(); let dll_suffix = self.target.dll_suffix.as_ref(); if file.starts_with(dll_prefix) && file.ends_with(dll_suffix) { - dylibs.insert(loc_canon.clone(), PathKind::ExternFlag); + dylibs.insert(loc_canon.clone()); continue; } crate_rejections diff --git a/compiler/rustc_session/src/cstore.rs b/compiler/rustc_session/src/cstore.rs index 30f6256a75efa..378c32d22ae24 100644 --- a/compiler/rustc_session/src/cstore.rs +++ b/compiler/rustc_session/src/cstore.rs @@ -15,24 +15,22 @@ use rustc_hir::definitions::{DefKey, DefPath, DefPathHash, Definitions}; use rustc_macros::{Decodable, Encodable, HashStable_Generic}; use rustc_span::{Span, Symbol}; -use crate::search_paths::PathKind; - // lonely orphan structs and enums looking for a better home /// Where a crate came from on the local filesystem. One of these three options /// must be non-None. #[derive(PartialEq, Clone, Debug, HashStable_Generic, Encodable, Decodable)] pub struct CrateSource { - pub dylib: Option<(PathBuf, PathKind)>, - pub rlib: Option<(PathBuf, PathKind)>, - pub rmeta: Option<(PathBuf, PathKind)>, - pub sdylib_interface: Option<(PathBuf, PathKind)>, + pub dylib: Option, + pub rlib: Option, + pub rmeta: Option, + pub sdylib_interface: Option, } impl CrateSource { #[inline] pub fn paths(&self) -> impl Iterator { - self.dylib.iter().chain(self.rlib.iter()).chain(self.rmeta.iter()).map(|p| &p.0) + self.dylib.iter().chain(self.rlib.iter()).chain(self.rmeta.iter()) } } diff --git a/compiler/rustc_session/src/search_paths.rs b/compiler/rustc_session/src/search_paths.rs index 00e12b45bafb1..2ca390b50dd0e 100644 --- a/compiler/rustc_session/src/search_paths.rs +++ b/compiler/rustc_session/src/search_paths.rs @@ -71,7 +71,6 @@ pub enum PathKind { Crate, Dependency, Framework, - ExternFlag, All, } From d10216a3a8eec34104f6f897689245245e406a8a Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:22:37 +0000 Subject: [PATCH 3/8] Fix rustc_hash ambiguity in miri There are two rustc_hash crates in the sysroot when building miri and there is no way to disambiguate between the two. Instead use the re-exports of it in the rustc_data_structures crate. --- compiler/rustc_data_structures/src/fx.rs | 2 +- src/tools/miri/src/diagnostics.rs | 4 ++-- src/tools/miri/src/helpers.rs | 4 ++-- src/tools/miri/src/lib.rs | 1 - 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_data_structures/src/fx.rs b/compiler/rustc_data_structures/src/fx.rs index f0db9623b674e..c1a5c8ebc7645 100644 --- a/compiler/rustc_data_structures/src/fx.rs +++ b/compiler/rustc_data_structures/src/fx.rs @@ -1,6 +1,6 @@ use std::hash::BuildHasherDefault; -pub use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; +pub use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet, FxHasher}; pub type StdEntry<'a, K, V> = std::collections::hash_map::Entry<'a, K, V>; diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs index bb8ba196983c4..bf037e3e804fd 100644 --- a/src/tools/miri/src/diagnostics.rs +++ b/src/tools/miri/src/diagnostics.rs @@ -3,8 +3,8 @@ use std::num::NonZero; use std::sync::Mutex; use rustc_abi::{Align, Size}; +use rustc_data_structures::fx::{FxBuildHasher, FxHashSet}; use rustc_errors::{Diag, DiagMessage, Level}; -use rustc_hash::FxHashSet; use rustc_span::{DUMMY_SP, Span, SpanData, Symbol}; use crate::borrow_tracker::stacked_borrows::diagnostics::TagHistory; @@ -878,6 +878,6 @@ pub struct SpanDedupDiagnostic(Mutex>); impl SpanDedupDiagnostic { pub const fn new() -> Self { - Self(Mutex::new(FxHashSet::with_hasher(rustc_hash::FxBuildHasher))) + Self(Mutex::new(FxHashSet::with_hasher(FxBuildHasher))) } } diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs index 383a4e2ea4b04..2ceb12a250629 100644 --- a/src/tools/miri/src/helpers.rs +++ b/src/tools/miri/src/helpers.rs @@ -6,7 +6,7 @@ use std::{cmp, iter}; use rand::RngCore; use rustc_abi::{Align, ExternAbi, FieldIdx, FieldsShape, Size, Variants}; use rustc_apfloat::Float; -use rustc_hash::FxHashSet; +use rustc_data_structures::fx::{FxBuildHasher, FxHashSet}; use rustc_hir::Safety; use rustc_hir::def::{DefKind, Namespace}; use rustc_hir::def_id::{CRATE_DEF_INDEX, CrateNum, DefId, LOCAL_CRATE}; @@ -647,7 +647,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { RejectOpWith::WarningWithoutBacktrace => { // Deduplicate these warnings *by shim* (not by span) static DEDUP: Mutex> = - Mutex::new(FxHashSet::with_hasher(rustc_hash::FxBuildHasher)); + Mutex::new(FxHashSet::with_hasher(FxBuildHasher)); let mut emitted_warnings = DEDUP.lock().unwrap(); if !emitted_warnings.contains(op_name) { // First time we are seeing this. diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs index 5ec2e8edd857d..d6d54c5ce056b 100644 --- a/src/tools/miri/src/lib.rs +++ b/src/tools/miri/src/lib.rs @@ -57,7 +57,6 @@ extern crate rustc_codegen_ssa; extern crate rustc_const_eval; extern crate rustc_data_structures; extern crate rustc_errors; -extern crate rustc_hash; extern crate rustc_hir; extern crate rustc_index; extern crate rustc_log; From 0dc82f8967a302556619595cc35aba1a728a08c0 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 25 Nov 2025 11:26:09 +0000 Subject: [PATCH 4/8] Avoid multiple copies of proc macros ending up in the sysroot --- src/bootstrap/src/core/build_steps/compile.rs | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 01b757bae246b..004dc2c4a9440 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -1558,7 +1558,7 @@ impl Step for RustcLink { run.never() } - /// Same as `std_link`, only for librustc + /// Same as `StdLink`, only for librustc fn run(self, builder: &Builder<'_>) { let build_compiler = self.build_compiler; let sysroot_compiler = self.sysroot_compiler; @@ -2418,13 +2418,28 @@ pub fn add_to_sysroot( t!(fs::create_dir_all(sysroot_dst)); t!(fs::create_dir_all(sysroot_host_dst)); t!(fs::create_dir_all(self_contained_dst)); + + let mut rustc_crates = HashSet::new(); for (path, dependency_type) in builder.read_stamp_file(stamp) { + let filename = path.file_name().unwrap().to_str().unwrap(); let dst = match dependency_type { DependencyType::Host => sysroot_host_dst, DependencyType::Target => sysroot_dst, DependencyType::TargetSelfContained => self_contained_dst, }; - builder.copy_link(&path, &dst.join(path.file_name().unwrap()), FileType::Regular); + builder.copy_link(&path, &dst.join(filename), FileType::Regular); + + // Check that none of the rustc_* crates have multiple versions. Otherwise using them from + // the sysroot would cause ambiguity errors. We do allow rustc_hash however as it is an + // external dependency that we build multiple copies of. It is re-exported by + // rustc_data_structures, so not being able to use extern crate rustc_hash; is not a big + // issue. + if !filename.contains("rustc_") || filename.contains("rustc_hash") { + continue; + } + if !rustc_crates.insert(filename.split_once('-').unwrap().0.to_owned()) { + panic!("duplicate rustc crate at {}", path.display()); + } } } @@ -2511,7 +2526,13 @@ pub fn run_cargo( if filename.starts_with(&host_root_dir) { // Unless it's a proc macro used in the compiler if crate_types.iter().any(|t| t == "proc-macro") { - deps.push((filename.to_path_buf(), DependencyType::Host)); + // Cargo will compile proc-macros that are part of the rustc workspace twice. + // Once as libmacro-hash.so as build dependency and once as libmacro.so as + // output artifact. Only keep the former to avoid ambiguity when trying to use + // the proc macro from the sysroot. + if filename.file_name().unwrap().to_str().unwrap().contains("-") { + deps.push((filename.to_path_buf(), DependencyType::Host)); + } } continue; } From 3743df9b414494e111f9012123a223279ba9d330 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Thu, 27 Nov 2025 13:58:24 +0000 Subject: [PATCH 5/8] Add a test for not resolving ambiguity for multiple --extern with same name --- .../foo-v1.rs | 1 + .../foo-v2.rs | 1 + .../main.rs | 10 ++++ .../main.stderr | 12 +++++ .../rmake.rs | 54 +++++++++++++++++++ .../use-foo.rs | 1 + 6 files changed, 79 insertions(+) create mode 100644 tests/run-make/duplicate-dependency-no-disambiguate/foo-v1.rs create mode 100644 tests/run-make/duplicate-dependency-no-disambiguate/foo-v2.rs create mode 100644 tests/run-make/duplicate-dependency-no-disambiguate/main.rs create mode 100644 tests/run-make/duplicate-dependency-no-disambiguate/main.stderr create mode 100644 tests/run-make/duplicate-dependency-no-disambiguate/rmake.rs create mode 100644 tests/run-make/duplicate-dependency-no-disambiguate/use-foo.rs diff --git a/tests/run-make/duplicate-dependency-no-disambiguate/foo-v1.rs b/tests/run-make/duplicate-dependency-no-disambiguate/foo-v1.rs new file mode 100644 index 0000000000000..4a835673a596b --- /dev/null +++ b/tests/run-make/duplicate-dependency-no-disambiguate/foo-v1.rs @@ -0,0 +1 @@ +pub struct Foo; diff --git a/tests/run-make/duplicate-dependency-no-disambiguate/foo-v2.rs b/tests/run-make/duplicate-dependency-no-disambiguate/foo-v2.rs new file mode 100644 index 0000000000000..4a835673a596b --- /dev/null +++ b/tests/run-make/duplicate-dependency-no-disambiguate/foo-v2.rs @@ -0,0 +1 @@ +pub struct Foo; diff --git a/tests/run-make/duplicate-dependency-no-disambiguate/main.rs b/tests/run-make/duplicate-dependency-no-disambiguate/main.rs new file mode 100644 index 0000000000000..eae6b8b4527df --- /dev/null +++ b/tests/run-make/duplicate-dependency-no-disambiguate/main.rs @@ -0,0 +1,10 @@ +#![feature(custom_inner_attributes)] +#![rustfmt::skip] // use_foo must be referenced before foo + +// Load foo-v2 through use-foo +use use_foo as _; + +// Make sure we don't disambiguate this as foo-v2. +use foo as _; + +fn main() {} diff --git a/tests/run-make/duplicate-dependency-no-disambiguate/main.stderr b/tests/run-make/duplicate-dependency-no-disambiguate/main.stderr new file mode 100644 index 0000000000000..23caa2caaef6e --- /dev/null +++ b/tests/run-make/duplicate-dependency-no-disambiguate/main.stderr @@ -0,0 +1,12 @@ +error[E0464]: multiple candidates for `rlib` dependency `foo` found + --> main.rs:8:5 + | +LL | use foo as _; + | ^^^ + | + = note: candidate #1: /build-root/test/run-make/duplicate-dependency-no-disambiguate/rmake_out/libfoo-v1.rlib + = note: candidate #2: /build-root/test/run-make/duplicate-dependency-no-disambiguate/rmake_out/libfoo-v2.rlib + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0464`. diff --git a/tests/run-make/duplicate-dependency-no-disambiguate/rmake.rs b/tests/run-make/duplicate-dependency-no-disambiguate/rmake.rs new file mode 100644 index 0000000000000..7dd6f3840dfae --- /dev/null +++ b/tests/run-make/duplicate-dependency-no-disambiguate/rmake.rs @@ -0,0 +1,54 @@ +//@ needs-target-std + +use run_make_support::{Rustc, cwd, diff, regex, rust_lib_name, rustc}; + +fn rustc_with_common_args() -> Rustc { + let mut rustc = rustc(); + rustc.remap_path_prefix(cwd(), "$DIR"); + rustc.edition("2018"); // Don't require `extern crate` + rustc +} + +fn main() { + rustc_with_common_args() + .input("foo-v1.rs") + .crate_type("rlib") + .crate_name("foo") + .extra_filename("-v1") + .metadata("-v1") + .run(); + + rustc_with_common_args() + .input("foo-v2.rs") + .crate_type("rlib") + .crate_name("foo") + .extra_filename("-v2") + .metadata("-v2") + .run(); + + rustc_with_common_args() + .input("use-foo.rs") + .crate_type("rlib") + .extern_("foo", rust_lib_name("foo-v2")) + .run(); + + let stderr = rustc_with_common_args() + .input("main.rs") + .extern_("foo", rust_lib_name("foo-v1")) + .extern_("foo", rust_lib_name("foo-v2")) + .extern_("use_foo", rust_lib_name("use_foo")) + .library_search_path(cwd()) + .ui_testing() + .run_fail() + .stderr_utf8(); + + diff() + .expected_file("main.stderr") + .normalize( + regex::escape(run_make_support::build_root().canonicalize().unwrap().to_str().unwrap()), + "/build-root", + ) + .normalize(r"\\", "/") + .actual_text("(rustc)", &stderr) + .run(); +} diff --git a/tests/run-make/duplicate-dependency-no-disambiguate/use-foo.rs b/tests/run-make/duplicate-dependency-no-disambiguate/use-foo.rs new file mode 100644 index 0000000000000..15c1d27f1b953 --- /dev/null +++ b/tests/run-make/duplicate-dependency-no-disambiguate/use-foo.rs @@ -0,0 +1 @@ +use foo as _; From 871ec86d3cf080543f16ae202be64b8fe91ff9c3 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Thu, 27 Nov 2025 13:58:49 +0000 Subject: [PATCH 6/8] Only check for already loaded indirect dependencies in existing_match For direct dependencies it is a lot harder to not accidentally resolve an ambiguity through an indirect dependency edge and as it turns out this check was actually more expensive than the work it skipped. For indirect dependencies existing_match is still necessary due to an assert in load, but at the same time it also can't accidentally resolve an ambiguity given that we already know exactly which crate to look for based on the crate hash. --- compiler/rustc_metadata/src/creader.rs | 51 ++++---------------------- 1 file changed, 8 insertions(+), 43 deletions(-) diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs index 250aa0769024b..4400f9f68180e 100644 --- a/compiler/rustc_metadata/src/creader.rs +++ b/compiler/rustc_metadata/src/creader.rs @@ -514,55 +514,20 @@ impl CStore { } } - fn existing_match( - &self, - externs: &Externs, - name: Symbol, - hash: Option, - ) -> Option { + fn existing_match(&self, name: Symbol, hash: Option) -> Option { + let hash = hash?; + for (cnum, data) in self.iter_crate_data() { if data.name() != name { trace!("{} did not match {}", data.name(), name); continue; } - match hash { - Some(hash) if hash == data.hash() => return Some(cnum), - Some(hash) => { - debug!("actual hash {} did not match expected {}", hash, data.hash()); - continue; - } - None => {} - } - - // When the hash is None we're dealing with a top-level dependency - // in which case we may have a specification on the command line for - // this library. Even though an upstream library may have loaded - // something of the same name, we have to make sure it was loaded - // from the exact same location as well. - // - // We're also sure to compare *paths*, not actual byte slices. The - // `source` stores paths which are normalized which may be different - // from the strings on the command line. - let source = data.source(); - if let Some(entry) = externs.get(name.as_str()) { - // Only use `--extern crate_name=path` here, not `--extern crate_name`. - if let Some(mut files) = entry.files() { - if files.any(|l| { - let l = l.canonicalized(); - source.dylib.as_ref() == Some(l) - || source.rlib.as_ref() == Some(l) - || source.rmeta.as_ref() == Some(l) - }) { - return Some(cnum); - } - } - continue; + if hash == data.hash() { + return Some(cnum); + } else { + debug!("actual hash {} did not match expected {}", hash, data.hash()); } - - // While the crate name matched, no --extern crate_name=path matched. It is possible - // that we have already loaded the target crate, but if that happens CStore::load will - // indicate so and we gracefully handle this, just potentially wasting a bit of time. } None @@ -799,7 +764,7 @@ impl CStore { let path_kind = if dep.is_some() { PathKind::Dependency } else { PathKind::Crate }; let private_dep = origin.private_dep(); - let result = if let Some(cnum) = self.existing_match(&tcx.sess.opts.externs, name, hash) { + let result = if let Some(cnum) = self.existing_match(name, hash) { (LoadResult::Previous(cnum), None) } else { info!("falling back to a load"); From 1ba686927d3b5cd8708ccb8fe37476f7fe02bf8c Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sat, 29 Nov 2025 09:06:53 +0000 Subject: [PATCH 7/8] Handle windows producing two files for every dylib in the duplicate crate check --- src/bootstrap/src/core/build_steps/compile.rs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 004dc2c4a9440..6309952009575 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -2419,7 +2419,7 @@ pub fn add_to_sysroot( t!(fs::create_dir_all(sysroot_host_dst)); t!(fs::create_dir_all(self_contained_dst)); - let mut rustc_crates = HashSet::new(); + let mut crates = HashSet::new(); for (path, dependency_type) in builder.read_stamp_file(stamp) { let filename = path.file_name().unwrap().to_str().unwrap(); let dst = match dependency_type { @@ -2429,16 +2429,23 @@ pub fn add_to_sysroot( }; builder.copy_link(&path, &dst.join(filename), FileType::Regular); - // Check that none of the rustc_* crates have multiple versions. Otherwise using them from - // the sysroot would cause ambiguity errors. We do allow rustc_hash however as it is an - // external dependency that we build multiple copies of. It is re-exported by - // rustc_data_structures, so not being able to use extern crate rustc_hash; is not a big - // issue. - if !filename.contains("rustc_") || filename.contains("rustc_hash") { + // Only insert the part before the . to deduplicate different files for the same crate. + // For example foo-1234.dll and foo-1234.dll.lib. + crates.insert(filename.split_once('.').unwrap().0.to_owned()); + } + + // Check that none of the rustc_* crates have multiple versions. Otherwise using them from + // the sysroot would cause ambiguity errors. We do allow rustc_hash however as it is an + // external dependency that we build multiple copies of. It is re-exported by + // rustc_data_structures, so not being able to use extern crate rustc_hash; is not a big + // issue. + let mut seen_crates = HashSet::new(); + for filestem in crates { + if !filestem.contains("rustc_") || filestem.contains("rustc_hash") { continue; } - if !rustc_crates.insert(filename.split_once('-').unwrap().0.to_owned()) { - panic!("duplicate rustc crate at {}", path.display()); + if !seen_crates.insert(filestem.split_once('-').unwrap().0.to_owned()) { + panic!("duplicate rustc crate {filestem}"); } } } From 38054014b5095fee68ec8b4961b243196ee61a87 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:42:00 +0000 Subject: [PATCH 8/8] More useful error messages for duplicate rustc crates --- src/bootstrap/src/core/build_steps/compile.rs | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs index 6309952009575..a915cc15869e5 100644 --- a/src/bootstrap/src/core/build_steps/compile.rs +++ b/src/bootstrap/src/core/build_steps/compile.rs @@ -7,7 +7,7 @@ //! goes along from the output of the previous stage. use std::borrow::Cow; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::ffi::OsStr; use std::io::BufReader; use std::io::prelude::*; @@ -2419,7 +2419,7 @@ pub fn add_to_sysroot( t!(fs::create_dir_all(sysroot_host_dst)); t!(fs::create_dir_all(self_contained_dst)); - let mut crates = HashSet::new(); + let mut crates = HashMap::new(); for (path, dependency_type) in builder.read_stamp_file(stamp) { let filename = path.file_name().unwrap().to_str().unwrap(); let dst = match dependency_type { @@ -2431,7 +2431,7 @@ pub fn add_to_sysroot( // Only insert the part before the . to deduplicate different files for the same crate. // For example foo-1234.dll and foo-1234.dll.lib. - crates.insert(filename.split_once('.').unwrap().0.to_owned()); + crates.insert(filename.split_once('.').unwrap().0.to_owned(), path); } // Check that none of the rustc_* crates have multiple versions. Otherwise using them from @@ -2439,13 +2439,20 @@ pub fn add_to_sysroot( // external dependency that we build multiple copies of. It is re-exported by // rustc_data_structures, so not being able to use extern crate rustc_hash; is not a big // issue. - let mut seen_crates = HashSet::new(); - for filestem in crates { + let mut seen_crates = HashMap::new(); + for (filestem, path) in crates { if !filestem.contains("rustc_") || filestem.contains("rustc_hash") { continue; } - if !seen_crates.insert(filestem.split_once('-').unwrap().0.to_owned()) { - panic!("duplicate rustc crate {filestem}"); + if let Some(other_path) = + seen_crates.insert(filestem.split_once('-').unwrap().0.to_owned(), path.clone()) + { + panic!( + "duplicate rustc crate {}\n- first copy at {}\n- second copy at {}", + filestem.split_once('-').unwrap().0.to_owned(), + other_path.display(), + path.display(), + ); } } }