From 38482b67924ae970aad05db7a2b89be8617f6a9c Mon Sep 17 00:00:00 2001 From: qaijuang <237468078+qaijuang@users.noreply.github.com> Date: Mon, 11 May 2026 11:10:00 -0400 Subject: [PATCH 1/2] Move DuplicateEiiImpls to rustc_middle --- compiler/rustc_middle/src/error.rs | 29 +++++++++++++++++++++++++++++ compiler/rustc_passes/src/eii.rs | 3 ++- compiler/rustc_passes/src/errors.rs | 29 ----------------------------- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_middle/src/error.rs b/compiler/rustc_middle/src/error.rs index 2823b7ba4e22e..440634f376301 100644 --- a/compiler/rustc_middle/src/error.rs +++ b/compiler/rustc_middle/src/error.rs @@ -160,3 +160,32 @@ pub(crate) struct IncrementCompilation { pub run_cmd: String, pub dep_node: String, } + +#[derive(Diagnostic)] +#[diag("multiple implementations of `#[{$name}]`")] +pub struct DuplicateEiiImpls { + pub name: Symbol, + + #[primary_span] + #[label("first implemented here in crate `{$first_crate}`")] + pub first_span: Span, + pub first_crate: Symbol, + + #[label("also implemented here in crate `{$second_crate}`")] + pub second_span: Span, + pub second_crate: Symbol, + + #[note("in addition to these two, { $num_additional_crates -> + [one] another implementation was found in crate {$additional_crate_names} + *[other] more implementations were also found in the following crates: {$additional_crate_names} + }")] + pub additional_crates: Option<()>, + + pub num_additional_crates: usize, + pub additional_crate_names: String, + + #[help( + "an \"externally implementable item\" can only have a single implementation in the final artifact. When multiple implementations are found, also in different crates, they conflict" + )] + pub help: (), +} diff --git a/compiler/rustc_passes/src/eii.rs b/compiler/rustc_passes/src/eii.rs index f3e84665f21dc..9c7a2b5be5a38 100644 --- a/compiler/rustc_passes/src/eii.rs +++ b/compiler/rustc_passes/src/eii.rs @@ -6,10 +6,11 @@ use std::iter; use rustc_data_structures::fx::FxIndexMap; use rustc_hir::attrs::{EiiDecl, EiiImpl}; use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE}; +use rustc_middle::error::DuplicateEiiImpls; use rustc_middle::ty::TyCtxt; use rustc_session::config::CrateType; -use crate::errors::{DuplicateEiiImpls, EiiWithoutImpl}; +use crate::errors::EiiWithoutImpl; #[derive(Clone, Copy, Debug)] enum CheckingMode { diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index b87a43e9fcde0..62f415d7bda64 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1224,35 +1224,6 @@ pub(crate) struct EiiWithoutImpl { pub help: (), } -#[derive(Diagnostic)] -#[diag("multiple implementations of `#[{$name}]`")] -pub(crate) struct DuplicateEiiImpls { - pub name: Symbol, - - #[primary_span] - #[label("first implemented here in crate `{$first_crate}`")] - pub first_span: Span, - pub first_crate: Symbol, - - #[label("also implemented here in crate `{$second_crate}`")] - pub second_span: Span, - pub second_crate: Symbol, - - #[note("in addition to these two, { $num_additional_crates -> - [one] another implementation was found in crate {$additional_crate_names} - *[other] more implementations were also found in the following crates: {$additional_crate_names} - }")] - pub additional_crates: Option<()>, - - pub num_additional_crates: usize, - pub additional_crate_names: String, - - #[help( - "an \"externally implementable item\" can only have a single implementation in the final artifact. When multiple implementations are found, also in different crates, they conflict" - )] - pub help: (), -} - #[derive(Diagnostic)] #[diag("function doesn't have a default implementation")] pub(crate) struct FunctionNotHaveDefaultImplementation { From b41cef3ee336edd95ae708fe981e5777378f246f Mon Sep 17 00:00:00 2001 From: qaijuang <237468078+qaijuang@users.noreply.github.com> Date: Mon, 11 May 2026 11:10:36 -0400 Subject: [PATCH 2/2] Reject linked dylib EII default overrides --- compiler/rustc_codegen_ssa/src/back/link.rs | 77 ++++++++++++++++++ compiler/rustc_codegen_ssa/src/base.rs | 80 +++++++++++++++++-- compiler/rustc_codegen_ssa/src/lib.rs | 18 ++++- .../eii/duplicate/auxiliary/dylib_default.rs | 5 ++ .../eii/duplicate/dylib_default_duplicate.rs | 20 +++++ .../duplicate/dylib_default_duplicate.stderr | 15 ++++ 6 files changed, 207 insertions(+), 8 deletions(-) create mode 100644 tests/ui/eii/duplicate/auxiliary/dylib_default.rs create mode 100644 tests/ui/eii/duplicate/dylib_default_duplicate.rs create mode 100644 tests/ui/eii/duplicate/dylib_default_duplicate.stderr diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index c527dd95e2db4..e306c2b48a54c 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -30,6 +30,7 @@ use rustc_metadata::{ walk_native_lib_search_dirs, }; use rustc_middle::bug; +use rustc_middle::error::DuplicateEiiImpls; use rustc_middle::lint::emit_lint_base; use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile; use rustc_middle::middle::dependency_format::Linkage; @@ -70,6 +71,73 @@ pub fn ensure_removed(dcx: DiagCtxtHandle<'_>, path: &Path) { } } +fn eii_impl_crate_name(crate_info: &CrateInfo, cnum: CrateNum) -> Symbol { + if cnum == LOCAL_CRATE { crate_info.local_crate_name } else { crate_info.crate_name[&cnum] } +} + +fn check_externally_implementable_item_linkage(sess: &Session, crate_info: &CrateInfo) { + let Some(eii_linkage) = &crate_info.eii_linkage else { + return; + }; + + // A crate can request multiple linked outputs with overlapping dependency + // formats, so report each underlying conflict once. + let mut emitted = FxHashSet::default(); + + // This needs the dependency formats selected for the final artifact. The + // earlier EII pass still handles missing impls and duplicate explicit impls. + for dependency_formats in crate_info.dependency_formats.values() { + for (eii_index, eii) in eii_linkage.iter().enumerate() { + let mut explicit_impls = + eii.impls.iter().enumerate().filter(|(_, imp)| !imp.is_default); + let Some((explicit_index, explicit_impl)) = explicit_impls.next() else { + continue; + }; + // If the explicit impl is already coming from a dylib, that dylib + // has already resolved the default-vs-explicit choice. + if explicit_impls.next().is_some() + || matches!( + dependency_formats.get(explicit_impl.impl_crate), + Some(Linkage::Dynamic | Linkage::IncludedFromDylib) + ) + { + continue; + } + + let mut finalized_default_impls = eii.impls.iter().enumerate().filter(|(_, imp)| { + imp.is_default + && matches!( + dependency_formats.get(imp.impl_crate), + Some(Linkage::Dynamic | Linkage::IncludedFromDylib) + ) + }); + let Some((default_index, default_impl)) = finalized_default_impls.next() else { + continue; + }; + + if !emitted.insert((eii_index, explicit_index, default_index)) { + continue; + } + + let additional_crate_names = finalized_default_impls + .map(|(_, imp)| format!("`{}`", eii_impl_crate_name(crate_info, imp.impl_crate))) + .collect::>(); + + sess.dcx().emit_err(DuplicateEiiImpls { + name: eii.name, + first_span: explicit_impl.span, + first_crate: eii_impl_crate_name(crate_info, explicit_impl.impl_crate), + second_span: default_impl.span, + second_crate: eii_impl_crate_name(crate_info, default_impl.impl_crate), + help: (), + additional_crates: (!additional_crate_names.is_empty()).then_some(()), + num_additional_crates: additional_crate_names.len(), + additional_crate_names: additional_crate_names.join(", "), + }); + } + } +} + /// Performs the linkage portion of the compilation phase. This will generate all /// of the requested outputs for this compilation session. pub fn link_binary( @@ -84,6 +152,7 @@ pub fn link_binary( let _timer = sess.timer("link_binary"); let output_metadata = sess.opts.output_types.contains_key(&OutputType::Metadata); let mut tempfiles_for_stdout_output: Vec = Vec::new(); + let mut checked_eii_linkage = false; for &crate_type in &crate_info.crate_types { // Ignore executable crates if we have -Z no-codegen, as they will error. if (sess.opts.unstable_opts.no_codegen || !sess.opts.output_types.should_codegen()) @@ -104,6 +173,14 @@ pub fn link_binary( }); if outputs.outputs.should_link() { + if !checked_eii_linkage { + sess.time("check_externally_implementable_item_linkage", || { + check_externally_implementable_item_linkage(sess, &crate_info); + }); + sess.dcx().abort_if_errors(); + checked_eii_linkage = true; + } + let output = out_filename(sess, crate_type, outputs, crate_info.local_crate_name); let tmpdir = TempDirBuilder::new() .prefix("rustc") diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index b0ce3833446d3..8cabff0458e29 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -1,7 +1,7 @@ -use std::cmp; use std::collections::BTreeSet; use std::sync::Arc; use std::time::{Duration, Instant}; +use std::{cmp, iter}; use itertools::Itertools; use rustc_abi::FIRST_VARIANT; @@ -9,17 +9,17 @@ use rustc_ast::expand::allocator::{ ALLOC_ERROR_HANDLER, ALLOCATOR_METHODS, AllocatorKind, AllocatorMethod, AllocatorMethodInput, AllocatorTy, }; -use rustc_data_structures::fx::{FxHashMap, FxIndexSet}; +use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet}; use rustc_data_structures::profiling::{get_resident_set_size, print_time_passes_entry}; use rustc_data_structures::sync::{IntoDynSyncSend, par_map}; use rustc_data_structures::unord::UnordMap; -use rustc_hir::attrs::{DebuggerVisualizerType, OptimizeAttr}; -use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_hir::attrs::{DebuggerVisualizerType, EiiDecl, EiiImpl, OptimizeAttr}; +use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE}; use rustc_hir::lang_items::LangItem; use rustc_hir::{ItemId, Target, find_attr}; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs; use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile; -use rustc_middle::middle::dependency_format::Dependencies; +use rustc_middle::middle::dependency_format::{Dependencies, Linkage}; use rustc_middle::middle::exported_symbols::{self, SymbolExportKind}; use rustc_middle::middle::lang_items; use rustc_middle::mir::BinOp; @@ -50,7 +50,8 @@ use crate::mir::operand::OperandValue; use crate::mir::place::PlaceRef; use crate::traits::*; use crate::{ - CachedModuleCodegen, CodegenLintLevelSpecs, CrateInfo, ModuleCodegen, errors, meth, mir, + CachedModuleCodegen, CodegenLintLevelSpecs, CrateInfo, EiiLinkageImplInfo, EiiLinkageInfo, + ModuleCodegen, errors, meth, mir, }; pub(crate) fn bin_op_to_icmp_predicate(op: BinOp, signed: bool) -> IntPredicate { @@ -897,6 +898,63 @@ pub fn is_call_from_compiler_builtins_to_upstream_monomorphization<'tcx>( && !tcx.should_codegen_locally(instance) } +fn collect_eii_linkage(tcx: TyCtxt<'_>) -> Vec { + #[derive(Debug)] + struct FoundImpl { + imp: EiiImpl, + impl_crate: CrateNum, + } + + #[derive(Debug)] + struct FoundEii { + decl: EiiDecl, + impls: FxIndexMap, + } + + let mut eiis = FxIndexMap::::default(); + + for &cnum in tcx.crates(()).iter().chain(iter::once(&LOCAL_CRATE)) { + for (did, (decl, impls)) in tcx.externally_implementable_items(cnum) { + eiis.entry(*did) + .or_insert_with(|| FoundEii { decl: *decl, impls: Default::default() }) + .impls + .extend( + impls + .into_iter() + .map(|(did, imp)| (*did, FoundImpl { imp: *imp, impl_crate: cnum })), + ); + } + } + + eiis.into_iter() + .filter_map(|(_, FoundEii { decl, impls })| { + let explicit_impl_count = impls.values().filter(|imp| !imp.imp.is_default).count(); + let has_default_impl = impls.values().any(|imp| imp.imp.is_default); + // Only this case needs the link-time check. Missing impls and + // duplicate explicit impls are handled in `rustc_passes`. + (explicit_impl_count == 1 && has_default_impl).then(|| EiiLinkageInfo { + name: decl.name.name, + impls: impls + .into_iter() + .map(|(impl_did, FoundImpl { imp, impl_crate })| EiiLinkageImplInfo { + span: tcx.def_span(impl_did), + impl_crate, + is_default: imp.is_default, + }) + .collect(), + }) + }) + .collect() +} + +fn eii_linkage_needed(dependency_formats: &Dependencies) -> bool { + dependency_formats.values().any(|formats| { + formats + .iter() + .any(|&linkage| matches!(linkage, Linkage::Dynamic | Linkage::IncludedFromDylib)) + }) +} + impl CrateInfo { pub fn new(tcx: TyCtxt<'_>, target_cpu: String) -> CrateInfo { let crate_types = tcx.crate_types().to_vec(); @@ -908,6 +966,13 @@ impl CrateInfo { crate_types.iter().map(|&c| (c, crate::back::linker::linked_symbols(tcx, c))).collect(); let local_crate_name = tcx.crate_name(LOCAL_CRATE); let windows_subsystem = find_attr!(tcx, crate, WindowsSubsystem(kind) => *kind); + let dependency_formats = Arc::clone(tcx.dependency_formats(())); + let eii_linkage = if eii_linkage_needed(&dependency_formats) { + let eii_linkage = collect_eii_linkage(tcx); + (!eii_linkage.is_empty()).then_some(eii_linkage) + } else { + None + }; // This list is used when generating the command line to pass through to // system linker. The linker expects undefined symbols on the left of the @@ -952,7 +1017,8 @@ impl CrateInfo { crate_name: UnordMap::with_capacity(n_crates), used_crates, used_crate_source: UnordMap::with_capacity(n_crates), - dependency_formats: Arc::clone(tcx.dependency_formats(())), + dependency_formats, + eii_linkage, windows_subsystem, natvis_debugger_visualizers: Default::default(), lint_level_specs: CodegenLintLevelSpecs::from_tcx(tcx), diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index ec1eb7eeb6fc0..1358cdc3dac6a 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -38,7 +38,7 @@ use rustc_session::Session; use rustc_session::config::{CrateType, OutputFilenames, OutputType}; use rustc_session::cstore::{self, CrateSource}; use rustc_session::lint::builtin::LINKER_MESSAGES; -use rustc_span::Symbol; +use rustc_span::{Span, Symbol}; pub mod assert_module_sources; pub mod back; @@ -195,6 +195,19 @@ impl From<&cstore::NativeLib> for NativeLib { } } +#[derive(Clone, Debug, Encodable, Decodable)] +pub struct EiiLinkageImplInfo { + pub span: Span, + pub impl_crate: CrateNum, + pub is_default: bool, +} + +#[derive(Clone, Debug, Encodable, Decodable)] +pub struct EiiLinkageInfo { + pub name: Symbol, + pub impls: Vec, +} + /// Misc info we load from metadata to persist beyond the tcx. /// /// Note: though `CrateNum` is only meaningful within the same tcx, information within `CrateInfo` @@ -221,6 +234,9 @@ pub struct CrateInfo { pub used_crate_source: UnordMap>, pub used_crates: Vec, pub dependency_formats: Arc, + /// EII implementations used by the link-time duplicate check, so `-Zno-link` can serialize the data needed by a + /// later `-Zlink-only` invocation. + pub eii_linkage: Option>, pub windows_subsystem: Option, pub natvis_debugger_visualizers: BTreeSet, pub lint_level_specs: CodegenLintLevelSpecs, diff --git a/tests/ui/eii/duplicate/auxiliary/dylib_default.rs b/tests/ui/eii/duplicate/auxiliary/dylib_default.rs new file mode 100644 index 0000000000000..d1136b0ebd636 --- /dev/null +++ b/tests/ui/eii/duplicate/auxiliary/dylib_default.rs @@ -0,0 +1,5 @@ +#![crate_type = "dylib"] +#![feature(extern_item_impls)] + +#[eii(eii1)] +fn decl1(x: u64) {} diff --git a/tests/ui/eii/duplicate/dylib_default_duplicate.rs b/tests/ui/eii/duplicate/dylib_default_duplicate.rs new file mode 100644 index 0000000000000..0ac3669715f85 --- /dev/null +++ b/tests/ui/eii/duplicate/dylib_default_duplicate.rs @@ -0,0 +1,20 @@ +//@ aux-build: dylib_default.rs +//@ needs-crate-type: dylib +//@ compile-flags: --emit link +//@ ignore-backends: gcc +// FIXME: linking on windows (specifically mingw) not yet supported, see tracking issue #125418 +//@ ignore-windows +// Regression test for https://github.com/rust-lang/rust/issues/156320. +// A default implementation from an upstream dylib has already been selected and +// must not be overridden by a downstream explicit implementation. +#![feature(extern_item_impls)] + +extern crate dylib_default; + +#[unsafe(dylib_default::eii1)] +fn other(x: u64) { + //~^ ERROR multiple implementations of `#[eii1]` + println!("1{x}"); +} + +fn main() {} diff --git a/tests/ui/eii/duplicate/dylib_default_duplicate.stderr b/tests/ui/eii/duplicate/dylib_default_duplicate.stderr new file mode 100644 index 0000000000000..04c6a13710e28 --- /dev/null +++ b/tests/ui/eii/duplicate/dylib_default_duplicate.stderr @@ -0,0 +1,15 @@ +error: multiple implementations of `#[eii1]` + --> $DIR/dylib_default_duplicate.rs:15:1 + | +LL | fn other(x: u64) { + | ^^^^^^^^^^^^^^^^ first implemented here in crate `dylib_default_duplicate` + | + ::: $DIR/auxiliary/dylib_default.rs:5:1 + | +LL | fn decl1(x: u64) {} + | ---------------- also implemented here in crate `dylib_default` + | + = help: an "externally implementable item" can only have a single implementation in the final artifact. When multiple implementations are found, also in different crates, they conflict + +error: aborting due to 1 previous error +