1use std::ops::ControlFlow;
2use std::path::{Path, PathBuf};
3
4use rustc_abi::ExternAbi;
5use rustc_ast::CRATE_NODE_ID;
6use rustc_attr_parsing as attr;
7use rustc_data_structures::fx::FxHashSet;
8use rustc_hir::attrs::AttributeKind;
9use rustc_hir::find_attr;
10use rustc_middle::query::LocalCrate;
11use rustc_middle::ty::{self, List, Ty, TyCtxt};
12use rustc_session::Session;
13use rustc_session::config::CrateType;
14use rustc_session::cstore::{
15 DllCallingConvention, DllImport, ForeignModule, NativeLib, PeImportNameType,
16};
17use rustc_session::parse::feature_err;
18use rustc_session::search_paths::PathKind;
19use rustc_session::utils::NativeLibKind;
20use rustc_span::def_id::{DefId, LOCAL_CRATE};
21use rustc_span::{Symbol, sym};
22use rustc_target::spec::{BinaryFormat, LinkSelfContainedComponents};
23
24use crate::{errors, fluent_generated};
25
26pub struct NativeLibSearchFallback<'a> {
30 pub self_contained_components: LinkSelfContainedComponents,
31 pub apple_sdk_root: Option<&'a Path>,
32}
33
34pub fn walk_native_lib_search_dirs<R>(
35 sess: &Session,
36 fallback: Option<NativeLibSearchFallback<'_>>,
37 mut f: impl FnMut(&Path, bool ) -> ControlFlow<R>,
38) -> ControlFlow<R> {
39 for search_path in sess.target_filesearch().cli_search_paths(PathKind::Native) {
41 f(&search_path.dir, false)?;
42 }
43 for search_path in sess.target_filesearch().cli_search_paths(PathKind::Framework) {
44 if search_path.kind != PathKind::All {
46 f(&search_path.dir, true)?;
47 }
48 }
49
50 let Some(NativeLibSearchFallback { self_contained_components, apple_sdk_root }) = fallback
51 else {
52 return ControlFlow::Continue(());
53 };
54
55 if self_contained_components.intersects(
58 LinkSelfContainedComponents::LIBC
59 | LinkSelfContainedComponents::UNWIND
60 | LinkSelfContainedComponents::MINGW,
61 ) {
62 f(&sess.target_tlib_path.dir.join("self-contained"), false)?;
63 }
64
65 if sess.target.vendor == "fortanix"
75 || sess.target.os == "linux"
76 || sess.target.os == "fuchsia"
77 || sess.target.is_like_aix
78 || sess.target.is_like_darwin && !sess.opts.unstable_opts.sanitizer.is_empty()
79 {
80 f(&sess.target_tlib_path.dir, false)?;
81 }
82
83 if let Some(sdk_root) = apple_sdk_root
86 && sess.target.env == "macabi"
87 {
88 f(&sdk_root.join("System/iOSSupport/usr/lib"), false)?;
89 f(&sdk_root.join("System/iOSSupport/System/Library/Frameworks"), true)?;
90 }
91
92 ControlFlow::Continue(())
93}
94
95pub fn try_find_native_static_library(
96 sess: &Session,
97 name: &str,
98 verbatim: bool,
99) -> Option<PathBuf> {
100 let default = sess.staticlib_components(verbatim);
101 let formats = if verbatim {
102 vec![default]
103 } else {
104 let unix = ("lib", ".a");
107 if default == unix { vec![default] } else { vec![default, unix] }
108 };
109
110 walk_native_lib_search_dirs(sess, None, |dir, is_framework| {
111 if !is_framework {
112 for (prefix, suffix) in &formats {
113 let test = dir.join(format!("{prefix}{name}{suffix}"));
114 if test.exists() {
115 return ControlFlow::Break(test);
116 }
117 }
118 }
119 ControlFlow::Continue(())
120 })
121 .break_value()
122}
123
124pub fn try_find_native_dynamic_library(
125 sess: &Session,
126 name: &str,
127 verbatim: bool,
128) -> Option<PathBuf> {
129 let default = sess.staticlib_components(verbatim);
130 let formats = if verbatim {
131 vec![default]
132 } else {
133 let meson = ("lib", ".dll.a");
137 let mingw = ("lib", ".a");
139 vec![default, meson, mingw]
140 };
141
142 walk_native_lib_search_dirs(sess, None, |dir, is_framework| {
143 if !is_framework {
144 for (prefix, suffix) in &formats {
145 let test = dir.join(format!("{prefix}{name}{suffix}"));
146 if test.exists() {
147 return ControlFlow::Break(test);
148 }
149 }
150 }
151 ControlFlow::Continue(())
152 })
153 .break_value()
154}
155
156pub fn find_native_static_library(name: &str, verbatim: bool, sess: &Session) -> PathBuf {
157 try_find_native_static_library(sess, name, verbatim)
158 .unwrap_or_else(|| sess.dcx().emit_fatal(errors::MissingNativeLibrary::new(name, verbatim)))
159}
160
161fn find_bundled_library(
162 name: Symbol,
163 verbatim: Option<bool>,
164 kind: NativeLibKind,
165 has_cfg: bool,
166 tcx: TyCtxt<'_>,
167) -> Option<Symbol> {
168 let sess = tcx.sess;
169 if let NativeLibKind::Static { bundle: Some(true) | None, whole_archive } = kind
170 && tcx.crate_types().iter().any(|t| matches!(t, &CrateType::Rlib | CrateType::Staticlib))
171 && (sess.opts.unstable_opts.packed_bundled_libs || has_cfg || whole_archive == Some(true))
172 {
173 let verbatim = verbatim.unwrap_or(false);
174 return find_native_static_library(name.as_str(), verbatim, sess)
175 .file_name()
176 .and_then(|s| s.to_str())
177 .map(Symbol::intern);
178 }
179 None
180}
181
182pub(crate) fn collect(tcx: TyCtxt<'_>, LocalCrate: LocalCrate) -> Vec<NativeLib> {
183 let mut collector = Collector { tcx, libs: Vec::new() };
184 if tcx.sess.opts.unstable_opts.link_directives {
185 for module in tcx.foreign_modules(LOCAL_CRATE).values() {
186 collector.process_module(module);
187 }
188 }
189 collector.process_command_line();
190 collector.libs
191}
192
193pub(crate) fn relevant_lib(sess: &Session, lib: &NativeLib) -> bool {
194 match lib.cfg {
195 Some(ref cfg) => attr::cfg_matches(cfg, sess, CRATE_NODE_ID, None),
196 None => true,
197 }
198}
199
200struct Collector<'tcx> {
201 tcx: TyCtxt<'tcx>,
202 libs: Vec<NativeLib>,
203}
204
205impl<'tcx> Collector<'tcx> {
206 fn process_module(&mut self, module: &ForeignModule) {
207 let ForeignModule { def_id, abi, ref foreign_items } = *module;
208 let def_id = def_id.expect_local();
209
210 let sess = self.tcx.sess;
211
212 if matches!(abi, ExternAbi::Rust) {
213 return;
214 }
215
216 let features = self.tcx.features();
218
219 for m in self.tcx.get_attrs(def_id, sym::link) {
220 let Some(items) = m.meta_item_list() else {
221 continue;
222 };
223
224 let mut name = None;
225 let mut kind = None;
226 let mut modifiers = None;
227 let mut cfg = None;
228 let mut wasm_import_module = None;
229 let mut import_name_type = None;
230 for item in items.iter() {
231 match item.name() {
232 Some(sym::name) => {
233 if name.is_some() {
234 sess.dcx().emit_err(errors::MultipleNamesInLink { span: item.span() });
235 continue;
236 }
237 let Some(link_name) = item.value_str() else {
238 sess.dcx().emit_err(errors::LinkNameForm { span: item.span() });
239 continue;
240 };
241 let span = item.name_value_literal_span().unwrap();
242 if link_name.is_empty() {
243 sess.dcx().emit_err(errors::EmptyLinkName { span });
244 }
245 name = Some((link_name, span));
246 }
247 Some(sym::kind) => {
248 if kind.is_some() {
249 sess.dcx().emit_err(errors::MultipleKindsInLink { span: item.span() });
250 continue;
251 }
252 let Some(link_kind) = item.value_str() else {
253 sess.dcx().emit_err(errors::LinkKindForm { span: item.span() });
254 continue;
255 };
256
257 let span = item.name_value_literal_span().unwrap();
258 let link_kind = match link_kind.as_str() {
259 "static" => NativeLibKind::Static { bundle: None, whole_archive: None },
260 "dylib" => NativeLibKind::Dylib { as_needed: None },
261 "framework" => {
262 if !sess.target.is_like_darwin {
263 sess.dcx().emit_err(errors::LinkFrameworkApple { span });
264 }
265 NativeLibKind::Framework { as_needed: None }
266 }
267 "raw-dylib" => {
268 if sess.target.is_like_windows {
269 } else if sess.target.binary_format == BinaryFormat::Elf
271 && features.raw_dylib_elf()
272 {
273 } else if sess.target.binary_format == BinaryFormat::Elf
275 && sess.is_nightly_build()
276 {
277 feature_err(
278 sess,
279 sym::raw_dylib_elf,
280 span,
281 fluent_generated::metadata_raw_dylib_elf_unstable,
282 )
283 .emit();
284 } else {
285 sess.dcx().emit_err(errors::RawDylibOnlyWindows { span });
286 }
287
288 NativeLibKind::RawDylib
289 }
290 "link-arg" => {
291 if !features.link_arg_attribute() {
292 feature_err(
293 sess,
294 sym::link_arg_attribute,
295 span,
296 fluent_generated::metadata_link_arg_unstable,
297 )
298 .emit();
299 }
300 NativeLibKind::LinkArg
301 }
302 kind => {
303 sess.dcx().emit_err(errors::UnknownLinkKind { span, kind });
304 continue;
305 }
306 };
307 kind = Some(link_kind);
308 }
309 Some(sym::modifiers) => {
310 if modifiers.is_some() {
311 sess.dcx()
312 .emit_err(errors::MultipleLinkModifiers { span: item.span() });
313 continue;
314 }
315 let Some(link_modifiers) = item.value_str() else {
316 sess.dcx().emit_err(errors::LinkModifiersForm { span: item.span() });
317 continue;
318 };
319 modifiers = Some((link_modifiers, item.name_value_literal_span().unwrap()));
320 }
321 Some(sym::cfg) => {
322 if cfg.is_some() {
323 sess.dcx().emit_err(errors::MultipleCfgs { span: item.span() });
324 continue;
325 }
326 let Some(link_cfg) = item.meta_item_list() else {
327 sess.dcx().emit_err(errors::LinkCfgForm { span: item.span() });
328 continue;
329 };
330 let [link_cfg] = link_cfg else {
331 sess.dcx()
332 .emit_err(errors::LinkCfgSinglePredicate { span: item.span() });
333 continue;
334 };
335 let Some(link_cfg) = link_cfg.meta_item_or_bool() else {
336 sess.dcx()
337 .emit_err(errors::LinkCfgSinglePredicate { span: item.span() });
338 continue;
339 };
340 if !features.link_cfg() {
341 feature_err(
342 sess,
343 sym::link_cfg,
344 item.span(),
345 fluent_generated::metadata_link_cfg_unstable,
346 )
347 .emit();
348 }
349 cfg = Some(link_cfg.clone());
350 }
351 Some(sym::wasm_import_module) => {
352 if wasm_import_module.is_some() {
353 sess.dcx().emit_err(errors::MultipleWasmImport { span: item.span() });
354 continue;
355 }
356 let Some(link_wasm_import_module) = item.value_str() else {
357 sess.dcx().emit_err(errors::WasmImportForm { span: item.span() });
358 continue;
359 };
360 wasm_import_module = Some((link_wasm_import_module, item.span()));
361 }
362 Some(sym::import_name_type) => {
363 if import_name_type.is_some() {
364 sess.dcx()
365 .emit_err(errors::MultipleImportNameType { span: item.span() });
366 continue;
367 }
368 let Some(link_import_name_type) = item.value_str() else {
369 sess.dcx().emit_err(errors::ImportNameTypeForm { span: item.span() });
370 continue;
371 };
372 if self.tcx.sess.target.arch != "x86" {
373 sess.dcx().emit_err(errors::ImportNameTypeX86 { span: item.span() });
374 continue;
375 }
376
377 let link_import_name_type = match link_import_name_type.as_str() {
378 "decorated" => PeImportNameType::Decorated,
379 "noprefix" => PeImportNameType::NoPrefix,
380 "undecorated" => PeImportNameType::Undecorated,
381 import_name_type => {
382 sess.dcx().emit_err(errors::UnknownImportNameType {
383 span: item.span(),
384 import_name_type,
385 });
386 continue;
387 }
388 };
389 import_name_type = Some((link_import_name_type, item.span()));
390 }
391 _ => {
392 sess.dcx().emit_err(errors::UnexpectedLinkArg { span: item.span() });
393 }
394 }
395 }
396
397 let mut verbatim = None;
399 if let Some((modifiers, span)) = modifiers {
400 for modifier in modifiers.as_str().split(',') {
401 let (modifier, value) = match modifier.strip_prefix(&['+', '-']) {
402 Some(m) => (m, modifier.starts_with('+')),
403 None => {
404 sess.dcx().emit_err(errors::InvalidLinkModifier { span });
405 continue;
406 }
407 };
408
409 macro report_unstable_modifier($feature: ident) {
410 if !features.$feature() {
411 #[expect(rustc::untranslatable_diagnostic)]
413 feature_err(
414 sess,
415 sym::$feature,
416 span,
417 format!("linking modifier `{modifier}` is unstable"),
418 )
419 .emit();
420 }
421 }
422 let assign_modifier = |dst: &mut Option<bool>| {
423 if dst.is_some() {
424 sess.dcx().emit_err(errors::MultipleModifiers { span, modifier });
425 } else {
426 *dst = Some(value);
427 }
428 };
429 match (modifier, &mut kind) {
430 ("bundle", Some(NativeLibKind::Static { bundle, .. })) => {
431 assign_modifier(bundle)
432 }
433 ("bundle", _) => {
434 sess.dcx().emit_err(errors::BundleNeedsStatic { span });
435 }
436
437 ("verbatim", _) => assign_modifier(&mut verbatim),
438
439 ("whole-archive", Some(NativeLibKind::Static { whole_archive, .. })) => {
440 assign_modifier(whole_archive)
441 }
442 ("whole-archive", _) => {
443 sess.dcx().emit_err(errors::WholeArchiveNeedsStatic { span });
444 }
445
446 ("as-needed", Some(NativeLibKind::Dylib { as_needed }))
447 | ("as-needed", Some(NativeLibKind::Framework { as_needed })) => {
448 report_unstable_modifier!(native_link_modifiers_as_needed);
449 assign_modifier(as_needed)
450 }
451 ("as-needed", _) => {
452 sess.dcx().emit_err(errors::AsNeededCompatibility { span });
453 }
454
455 _ => {
456 sess.dcx().emit_err(errors::UnknownLinkModifier { span, modifier });
457 }
458 }
459 }
460 }
461
462 if let Some((_, span)) = wasm_import_module {
463 if name.is_some() || kind.is_some() || modifiers.is_some() || cfg.is_some() {
464 sess.dcx().emit_err(errors::IncompatibleWasmLink { span });
465 }
466 }
467
468 if wasm_import_module.is_some() {
469 (name, kind) = (wasm_import_module, Some(NativeLibKind::WasmImportModule));
470 }
471 let Some((name, name_span)) = name else {
472 sess.dcx().emit_err(errors::LinkRequiresName { span: m.span() });
473 continue;
474 };
475
476 if let Some((_, span)) = import_name_type {
478 if kind != Some(NativeLibKind::RawDylib) {
479 sess.dcx().emit_err(errors::ImportNameTypeRaw { span });
480 }
481 }
482
483 let dll_imports = match kind {
484 Some(NativeLibKind::RawDylib) => {
485 if name.as_str().contains('\0') {
486 sess.dcx().emit_err(errors::RawDylibNoNul { span: name_span });
487 }
488 foreign_items
489 .iter()
490 .map(|&child_item| {
491 self.build_dll_import(
492 abi,
493 import_name_type.map(|(import_name_type, _)| import_name_type),
494 child_item,
495 )
496 })
497 .collect()
498 }
499 _ => {
500 for &child_item in foreign_items {
501 if let Some(span) = find_attr!(self.tcx.get_all_attrs(child_item), AttributeKind::LinkOrdinal {span, ..} => *span)
502 {
503 sess.dcx().emit_err(errors::LinkOrdinalRawDylib { span });
504 }
505 }
506
507 Vec::new()
508 }
509 };
510
511 let kind = kind.unwrap_or(NativeLibKind::Unspecified);
512 let filename = find_bundled_library(name, verbatim, kind, cfg.is_some(), self.tcx);
513 self.libs.push(NativeLib {
514 name,
515 filename,
516 kind,
517 cfg,
518 foreign_module: Some(def_id.to_def_id()),
519 verbatim,
520 dll_imports,
521 });
522 }
523 }
524
525 fn process_command_line(&mut self) {
527 let mut renames = FxHashSet::default();
529 for lib in &self.tcx.sess.opts.libs {
530 if let NativeLibKind::Framework { .. } = lib.kind
531 && !self.tcx.sess.target.is_like_darwin
532 {
533 self.tcx.dcx().emit_err(errors::LibFrameworkApple);
535 }
536 if let Some(ref new_name) = lib.new_name {
537 let any_duplicate = self.libs.iter().any(|n| n.name.as_str() == lib.name);
538 if new_name.is_empty() {
539 self.tcx.dcx().emit_err(errors::EmptyRenamingTarget { lib_name: &lib.name });
540 } else if !any_duplicate {
541 self.tcx.dcx().emit_err(errors::RenamingNoLink { lib_name: &lib.name });
542 } else if !renames.insert(&lib.name) {
543 self.tcx.dcx().emit_err(errors::MultipleRenamings { lib_name: &lib.name });
544 }
545 }
546 }
547
548 for passed_lib in &self.tcx.sess.opts.libs {
556 let mut existing = self
560 .libs
561 .extract_if(.., |lib| {
562 if lib.name.as_str() == passed_lib.name {
563 if lib.has_modifiers() || passed_lib.has_modifiers() {
567 match lib.foreign_module {
568 Some(def_id) => {
569 self.tcx.dcx().emit_err(errors::NoLinkModOverride {
570 span: Some(self.tcx.def_span(def_id)),
571 })
572 }
573 None => self
574 .tcx
575 .dcx()
576 .emit_err(errors::NoLinkModOverride { span: None }),
577 };
578 }
579 if passed_lib.kind != NativeLibKind::Unspecified {
580 lib.kind = passed_lib.kind;
581 }
582 if let Some(new_name) = &passed_lib.new_name {
583 lib.name = Symbol::intern(new_name);
584 }
585 lib.verbatim = passed_lib.verbatim;
586 return true;
587 }
588 false
589 })
590 .collect::<Vec<_>>();
591 if existing.is_empty() {
592 let new_name: Option<&str> = passed_lib.new_name.as_deref();
594 let name = Symbol::intern(new_name.unwrap_or(&passed_lib.name));
595 let filename = find_bundled_library(
596 name,
597 passed_lib.verbatim,
598 passed_lib.kind,
599 false,
600 self.tcx,
601 );
602 self.libs.push(NativeLib {
603 name,
604 filename,
605 kind: passed_lib.kind,
606 cfg: None,
607 foreign_module: None,
608 verbatim: passed_lib.verbatim,
609 dll_imports: Vec::new(),
610 });
611 } else {
612 self.libs.append(&mut existing);
615 }
616 }
617 }
618
619 fn i686_arg_list_size(&self, item: DefId) -> usize {
620 let argument_types: &List<Ty<'_>> = self.tcx.instantiate_bound_regions_with_erased(
621 self.tcx
622 .type_of(item)
623 .instantiate_identity()
624 .fn_sig(self.tcx)
625 .inputs()
626 .map_bound(|slice| self.tcx.mk_type_list(slice)),
627 );
628
629 argument_types
630 .iter()
631 .map(|ty| {
632 let layout = self
633 .tcx
634 .layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(ty))
635 .expect("layout")
636 .layout;
637 (layout.size().bytes_usize() + 3) & !3
640 })
641 .sum()
642 }
643
644 fn build_dll_import(
645 &self,
646 abi: ExternAbi,
647 import_name_type: Option<PeImportNameType>,
648 item: DefId,
649 ) -> DllImport {
650 let span = self.tcx.def_span(item);
651
652 assert!(self.tcx.sess.target.is_abi_supported(abi));
655
656 let calling_convention = if self.tcx.sess.target.arch == "x86" {
660 match abi {
661 ExternAbi::C { .. } | ExternAbi::Cdecl { .. } => DllCallingConvention::C,
662 ExternAbi::Stdcall { .. } => {
663 DllCallingConvention::Stdcall(self.i686_arg_list_size(item))
664 }
665 ExternAbi::System { .. } => {
669 let c_variadic =
670 self.tcx.type_of(item).instantiate_identity().fn_sig(self.tcx).c_variadic();
671
672 if c_variadic {
673 DllCallingConvention::C
674 } else {
675 DllCallingConvention::Stdcall(self.i686_arg_list_size(item))
676 }
677 }
678 ExternAbi::Fastcall { .. } => {
679 DllCallingConvention::Fastcall(self.i686_arg_list_size(item))
680 }
681 ExternAbi::Vectorcall { .. } => {
682 DllCallingConvention::Vectorcall(self.i686_arg_list_size(item))
683 }
684 _ => {
685 self.tcx.dcx().emit_fatal(errors::RawDylibUnsupportedAbi { span });
686 }
687 }
688 } else {
689 match abi {
690 ExternAbi::C { .. } | ExternAbi::Win64 { .. } | ExternAbi::System { .. } => {
691 DllCallingConvention::C
692 }
693 _ => {
694 self.tcx.dcx().emit_fatal(errors::RawDylibUnsupportedAbi { span });
695 }
696 }
697 };
698
699 let codegen_fn_attrs = self.tcx.codegen_fn_attrs(item);
700 let import_name_type = codegen_fn_attrs
701 .link_ordinal
702 .map_or(import_name_type, |ord| Some(PeImportNameType::Ordinal(ord)));
703
704 let name = codegen_fn_attrs.symbol_name.unwrap_or_else(|| self.tcx.item_name(item));
705
706 if self.tcx.sess.target.binary_format == BinaryFormat::Elf {
707 let name = name.as_str();
708 if name.contains('\0') {
709 self.tcx.dcx().emit_err(errors::RawDylibMalformed { span });
710 } else if let Some((left, right)) = name.split_once('@')
711 && (left.is_empty() || right.is_empty() || right.contains('@'))
712 {
713 self.tcx.dcx().emit_err(errors::RawDylibMalformed { span });
714 }
715 }
716
717 DllImport {
718 name,
719 import_name_type,
720 calling_convention,
721 span,
722 is_fn: self.tcx.def_kind(item).is_fn_like(),
723 }
724 }
725}