Skip to content

Commit 44e8bb8

Browse files
committed
Reject linked dylib EII default overrides
1 parent 3f0b9a1 commit 44e8bb8

6 files changed

Lines changed: 209 additions & 8 deletions

File tree

compiler/rustc_codegen_ssa/src/back/link.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use rustc_metadata::{
3030
walk_native_lib_search_dirs,
3131
};
3232
use rustc_middle::bug;
33+
use rustc_middle::error::DuplicateEiiImpls;
3334
use rustc_middle::lint::emit_lint_base;
3435
use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile;
3536
use rustc_middle::middle::dependency_format::Linkage;
@@ -70,6 +71,73 @@ pub fn ensure_removed(dcx: DiagCtxtHandle<'_>, path: &Path) {
7071
}
7172
}
7273

74+
fn eii_impl_crate_name(crate_info: &CrateInfo, cnum: CrateNum) -> Symbol {
75+
if cnum == LOCAL_CRATE { crate_info.local_crate_name } else { crate_info.crate_name[&cnum] }
76+
}
77+
78+
fn check_externally_implementable_item_linkage(sess: &Session, crate_info: &CrateInfo) {
79+
let Some(eii_linkage) = &crate_info.eii_linkage else {
80+
return;
81+
};
82+
83+
// A crate can request multiple linked outputs with overlapping dependency
84+
// formats, so report each underlying conflict once.
85+
let mut emitted = FxHashSet::default();
86+
87+
// This needs the dependency formats selected for the final artifact. The
88+
// earlier EII pass still handles missing impls and duplicate explicit impls.
89+
for dependency_formats in crate_info.dependency_formats.values() {
90+
for (eii_index, eii) in eii_linkage.iter().enumerate() {
91+
let mut explicit_impls =
92+
eii.impls.iter().enumerate().filter(|(_, imp)| !imp.is_default);
93+
let Some((explicit_index, explicit_impl)) = explicit_impls.next() else {
94+
continue;
95+
};
96+
// If the explicit impl is already coming from a dylib, that dylib
97+
// has already resolved the default-vs-explicit choice.
98+
if explicit_impls.next().is_some()
99+
|| matches!(
100+
dependency_formats.get(explicit_impl.impl_crate),
101+
Some(Linkage::Dynamic | Linkage::IncludedFromDylib)
102+
)
103+
{
104+
continue;
105+
}
106+
107+
let mut finalized_default_impls = eii.impls.iter().enumerate().filter(|(_, imp)| {
108+
imp.is_default
109+
&& matches!(
110+
dependency_formats.get(imp.impl_crate),
111+
Some(Linkage::Dynamic | Linkage::IncludedFromDylib)
112+
)
113+
});
114+
let Some((default_index, default_impl)) = finalized_default_impls.next() else {
115+
continue;
116+
};
117+
118+
if !emitted.insert((eii_index, explicit_index, default_index)) {
119+
continue;
120+
}
121+
122+
let additional_crate_names = finalized_default_impls
123+
.map(|(_, imp)| format!("`{}`", eii_impl_crate_name(crate_info, imp.impl_crate)))
124+
.collect::<Vec<_>>();
125+
126+
sess.dcx().emit_err(DuplicateEiiImpls {
127+
name: eii.name,
128+
first_span: explicit_impl.span,
129+
first_crate: eii_impl_crate_name(crate_info, explicit_impl.impl_crate),
130+
second_span: default_impl.span,
131+
second_crate: eii_impl_crate_name(crate_info, default_impl.impl_crate),
132+
help: (),
133+
additional_crates: (!additional_crate_names.is_empty()).then_some(()),
134+
num_additional_crates: additional_crate_names.len(),
135+
additional_crate_names: additional_crate_names.join(", "),
136+
});
137+
}
138+
}
139+
}
140+
73141
/// Performs the linkage portion of the compilation phase. This will generate all
74142
/// of the requested outputs for this compilation session.
75143
pub fn link_binary(
@@ -84,6 +152,7 @@ pub fn link_binary(
84152
let _timer = sess.timer("link_binary");
85153
let output_metadata = sess.opts.output_types.contains_key(&OutputType::Metadata);
86154
let mut tempfiles_for_stdout_output: Vec<PathBuf> = Vec::new();
155+
let mut checked_eii_linkage = false;
87156
for &crate_type in &crate_info.crate_types {
88157
// Ignore executable crates if we have -Z no-codegen, as they will error.
89158
if (sess.opts.unstable_opts.no_codegen || !sess.opts.output_types.should_codegen())
@@ -104,6 +173,14 @@ pub fn link_binary(
104173
});
105174

106175
if outputs.outputs.should_link() {
176+
if !checked_eii_linkage {
177+
sess.time("check_externally_implementable_item_linkage", || {
178+
check_externally_implementable_item_linkage(sess, &crate_info);
179+
});
180+
sess.dcx().abort_if_errors();
181+
checked_eii_linkage = true;
182+
}
183+
107184
let output = out_filename(sess, crate_type, outputs, crate_info.local_crate_name);
108185
let tmpdir = TempDirBuilder::new()
109186
.prefix("rustc")

compiler/rustc_codegen_ssa/src/base.rs

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
1-
use std::cmp;
21
use std::collections::BTreeSet;
32
use std::sync::Arc;
43
use std::time::{Duration, Instant};
4+
use std::{cmp, iter};
55

66
use itertools::Itertools;
77
use rustc_abi::FIRST_VARIANT;
88
use rustc_ast::expand::allocator::{
99
ALLOC_ERROR_HANDLER, ALLOCATOR_METHODS, AllocatorKind, AllocatorMethod, AllocatorMethodInput,
1010
AllocatorTy,
1111
};
12-
use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
12+
use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet};
1313
use rustc_data_structures::profiling::{get_resident_set_size, print_time_passes_entry};
1414
use rustc_data_structures::sync::{IntoDynSyncSend, par_map};
1515
use rustc_data_structures::unord::UnordMap;
16-
use rustc_hir::attrs::{DebuggerVisualizerType, OptimizeAttr};
17-
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
16+
use rustc_hir::attrs::{DebuggerVisualizerType, EiiDecl, EiiImpl, OptimizeAttr};
17+
use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE};
1818
use rustc_hir::lang_items::LangItem;
1919
use rustc_hir::{ItemId, Target, find_attr};
2020
use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
2121
use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile;
22-
use rustc_middle::middle::dependency_format::Dependencies;
22+
use rustc_middle::middle::dependency_format::{Dependencies, Linkage};
2323
use rustc_middle::middle::exported_symbols::{self, SymbolExportKind};
2424
use rustc_middle::middle::lang_items;
2525
use rustc_middle::mir::BinOp;
@@ -49,7 +49,10 @@ use crate::meth::load_vtable;
4949
use crate::mir::operand::OperandValue;
5050
use crate::mir::place::PlaceRef;
5151
use crate::traits::*;
52-
use crate::{CachedModuleCodegen, CodegenLintLevels, CrateInfo, ModuleCodegen, errors, meth, mir};
52+
use crate::{
53+
CachedModuleCodegen, CodegenLintLevels, CrateInfo, EiiLinkageImplInfo, EiiLinkageInfo,
54+
ModuleCodegen, errors, meth, mir,
55+
};
5356

5457
pub(crate) fn bin_op_to_icmp_predicate(op: BinOp, signed: bool) -> IntPredicate {
5558
match (op, signed) {
@@ -895,6 +898,63 @@ pub fn is_call_from_compiler_builtins_to_upstream_monomorphization<'tcx>(
895898
&& !tcx.should_codegen_locally(instance)
896899
}
897900

901+
fn collect_eii_linkage(tcx: TyCtxt<'_>) -> Vec<EiiLinkageInfo> {
902+
#[derive(Debug)]
903+
struct FoundImpl {
904+
imp: EiiImpl,
905+
impl_crate: CrateNum,
906+
}
907+
908+
#[derive(Debug)]
909+
struct FoundEii {
910+
decl: EiiDecl,
911+
impls: FxIndexMap<DefId, FoundImpl>,
912+
}
913+
914+
let mut eiis = FxIndexMap::<DefId, FoundEii>::default();
915+
916+
for &cnum in tcx.crates(()).iter().chain(iter::once(&LOCAL_CRATE)) {
917+
for (did, (decl, impls)) in tcx.externally_implementable_items(cnum) {
918+
eiis.entry(*did)
919+
.or_insert_with(|| FoundEii { decl: *decl, impls: Default::default() })
920+
.impls
921+
.extend(
922+
impls
923+
.into_iter()
924+
.map(|(did, imp)| (*did, FoundImpl { imp: *imp, impl_crate: cnum })),
925+
);
926+
}
927+
}
928+
929+
eiis.into_iter()
930+
.filter_map(|(_, FoundEii { decl, impls })| {
931+
let explicit_impl_count = impls.values().filter(|imp| !imp.imp.is_default).count();
932+
let has_default_impl = impls.values().any(|imp| imp.imp.is_default);
933+
// Only this case needs the link-time check. Missing impls and
934+
// duplicate explicit impls are handled in `rustc_passes`.
935+
(explicit_impl_count == 1 && has_default_impl).then(|| EiiLinkageInfo {
936+
name: decl.name.name,
937+
impls: impls
938+
.into_iter()
939+
.map(|(impl_did, FoundImpl { imp, impl_crate })| EiiLinkageImplInfo {
940+
span: tcx.def_span(impl_did),
941+
impl_crate,
942+
is_default: imp.is_default,
943+
})
944+
.collect(),
945+
})
946+
})
947+
.collect()
948+
}
949+
950+
fn eii_linkage_needed(dependency_formats: &Dependencies) -> bool {
951+
dependency_formats.values().any(|formats| {
952+
formats
953+
.iter()
954+
.any(|&linkage| matches!(linkage, Linkage::Dynamic | Linkage::IncludedFromDylib))
955+
})
956+
}
957+
898958
impl CrateInfo {
899959
pub fn new(tcx: TyCtxt<'_>, target_cpu: String) -> CrateInfo {
900960
let crate_types = tcx.crate_types().to_vec();
@@ -906,6 +966,13 @@ impl CrateInfo {
906966
crate_types.iter().map(|&c| (c, crate::back::linker::linked_symbols(tcx, c))).collect();
907967
let local_crate_name = tcx.crate_name(LOCAL_CRATE);
908968
let windows_subsystem = find_attr!(tcx, crate, WindowsSubsystem(kind) => *kind);
969+
let dependency_formats = Arc::clone(tcx.dependency_formats(()));
970+
let eii_linkage = if eii_linkage_needed(&dependency_formats) {
971+
let eii_linkage = collect_eii_linkage(tcx);
972+
(!eii_linkage.is_empty()).then_some(eii_linkage)
973+
} else {
974+
None
975+
};
909976

910977
// This list is used when generating the command line to pass through to
911978
// system linker. The linker expects undefined symbols on the left of the
@@ -950,7 +1017,8 @@ impl CrateInfo {
9501017
crate_name: UnordMap::with_capacity(n_crates),
9511018
used_crates,
9521019
used_crate_source: UnordMap::with_capacity(n_crates),
953-
dependency_formats: Arc::clone(tcx.dependency_formats(())),
1020+
dependency_formats,
1021+
eii_linkage,
9541022
windows_subsystem,
9551023
natvis_debugger_visualizers: Default::default(),
9561024
lint_levels: CodegenLintLevels::from_tcx(tcx),

compiler/rustc_codegen_ssa/src/lib.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use rustc_session::Session;
3838
use rustc_session::config::{CrateType, OutputFilenames, OutputType};
3939
use rustc_session::cstore::{self, CrateSource};
4040
use rustc_session::lint::builtin::LINKER_MESSAGES;
41-
use rustc_span::Symbol;
41+
use rustc_span::{Span, Symbol};
4242

4343
pub mod assert_module_sources;
4444
pub mod back;
@@ -195,6 +195,19 @@ impl From<&cstore::NativeLib> for NativeLib {
195195
}
196196
}
197197

198+
#[derive(Clone, Debug, Encodable, Decodable)]
199+
pub struct EiiLinkageImplInfo {
200+
pub span: Span,
201+
pub impl_crate: CrateNum,
202+
pub is_default: bool,
203+
}
204+
205+
#[derive(Clone, Debug, Encodable, Decodable)]
206+
pub struct EiiLinkageInfo {
207+
pub name: Symbol,
208+
pub impls: Vec<EiiLinkageImplInfo>,
209+
}
210+
198211
/// Misc info we load from metadata to persist beyond the tcx.
199212
///
200213
/// Note: though `CrateNum` is only meaningful within the same tcx, information within `CrateInfo`
@@ -221,6 +234,9 @@ pub struct CrateInfo {
221234
pub used_crate_source: UnordMap<CrateNum, Arc<CrateSource>>,
222235
pub used_crates: Vec<CrateNum>,
223236
pub dependency_formats: Arc<Dependencies>,
237+
/// EII implementations used by the link-time duplicate check, so `-Zno-link` can serialize the data needed by a
238+
/// later `-Zlink-only` invocation.
239+
pub eii_linkage: Option<Vec<EiiLinkageInfo>>,
224240
pub windows_subsystem: Option<WindowsSubsystemKind>,
225241
pub natvis_debugger_visualizers: BTreeSet<DebuggerVisualizerFile>,
226242
pub lint_levels: CodegenLintLevels,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#![crate_type = "dylib"]
2+
#![feature(extern_item_impls)]
3+
4+
#[eii(eii1)]
5+
fn decl1(x: u64) {}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//@ aux-build: dylib_default.rs
2+
//@ needs-crate-type: dylib
3+
//@ compile-flags: --emit link
4+
//@ ignore-backends: gcc
5+
// FIXME: linking on windows (specifically mingw) not yet supported, see tracking issue #125418
6+
//@ ignore-windows
7+
// Regression test for https://github.com/rust-lang/rust/issues/156320.
8+
// A default implementation from an upstream dylib has already been selected and
9+
// must not be overridden by a downstream explicit implementation.
10+
#![feature(extern_item_impls)]
11+
12+
extern crate dylib_default;
13+
14+
#[unsafe(dylib_default::eii1)]
15+
fn other(x: u64) {
16+
//~^ ERROR multiple implementations of `#[eii1]`
17+
println!("1{x}");
18+
}
19+
20+
fn main() {}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: multiple implementations of `#[eii1]`
2+
--> $DIR/dylib_default_duplicate.rs:15:1
3+
|
4+
LL | fn other(x: u64) {
5+
| ^^^^^^^^^^^^^^^^ first implemented here in crate `dylib_default_duplicate`
6+
|
7+
::: $DIR/auxiliary/dylib_default.rs:5:1
8+
|
9+
LL | fn decl1(x: u64) {}
10+
| ---------------- also implemented here in crate `dylib_default`
11+
|
12+
= 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
13+
14+
error: aborting due to 1 previous error
15+

0 commit comments

Comments
 (0)