Skip to main content

rustc_metadata/
native_libs.rs

1use std::ops::ControlFlow;
2use std::path::{Path, PathBuf};
3
4use rustc_abi::ExternAbi;
5use rustc_attr_parsing::eval_config_entry;
6use rustc_data_structures::fx::FxHashSet;
7use rustc_hir::attrs::{NativeLibKind, PeImportNameType};
8use rustc_hir::def::DefKind;
9use rustc_hir::find_attr;
10use rustc_middle::bug;
11use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
12use rustc_middle::query::LocalCrate;
13use rustc_middle::ty::{self, List, Ty, TyCtxt};
14use rustc_session::Session;
15use rustc_session::cstore::{
16    DllCallingConvention, DllImport, DllImportSymbolType, ForeignModule, NativeLib,
17};
18use rustc_session::search_paths::PathKind;
19use rustc_span::Symbol;
20use rustc_span::def_id::{DefId, LOCAL_CRATE};
21use rustc_target::spec::{Arch, BinaryFormat, CfgAbi, Env, LinkSelfContainedComponents, Os};
22
23use crate::diagnostics;
24
25/// The fallback directories are passed to linker, but not used when rustc does the search,
26/// because in the latter case the set of fallback directories cannot always be determined
27/// consistently at the moment.
28pub struct NativeLibSearchFallback<'a> {
29    pub self_contained_components: LinkSelfContainedComponents,
30    pub apple_sdk_root: Option<&'a Path>,
31}
32
33pub fn walk_native_lib_search_dirs<R>(
34    sess: &Session,
35    fallback: Option<NativeLibSearchFallback<'_>>,
36    mut f: impl FnMut(&Path, bool /*is_framework*/) -> ControlFlow<R>,
37) -> ControlFlow<R> {
38    // Library search paths explicitly supplied by user (`-L` on the command line).
39    for search_path in sess.target_filesearch().cli_search_paths(PathKind::Native) {
40        f(&search_path.dir, false)?;
41    }
42    for search_path in sess.target_filesearch().cli_search_paths(PathKind::Framework) {
43        // Frameworks are looked up strictly in framework-specific paths.
44        if search_path.kind != PathKind::All {
45            f(&search_path.dir, true)?;
46        }
47    }
48
49    let Some(NativeLibSearchFallback { self_contained_components, apple_sdk_root }) = fallback
50    else {
51        return ControlFlow::Continue(());
52    };
53
54    // The toolchain ships some native library components and self-contained linking was enabled.
55    // Add the self-contained library directory to search paths.
56    if self_contained_components.intersects(
57        LinkSelfContainedComponents::LIBC
58            | LinkSelfContainedComponents::UNWIND
59            | LinkSelfContainedComponents::MINGW,
60    ) {
61        f(&sess.target_tlib_path.dir.join("self-contained"), false)?;
62    }
63
64    let has_shared_llvm_apple_darwin =
65        sess.target.is_like_darwin && sess.target_tlib_path.dir.join("libLLVM.dylib").exists();
66
67    // Toolchains for some targets may ship `libunwind.a`, but place it into the main sysroot
68    // library directory instead of the self-contained directories.
69    // Sanitizer libraries have the same issue and are also linked by name on Apple targets.
70    // The targets here should be in sync with `copy_third_party_objects` in bootstrap.
71    // On Apple targets, shared LLVM is linked by name, so when `libLLVM.dylib` is
72    // present in the target libdir, add that directory to the linker search path.
73    // FIXME: implement `-Clink-self-contained=+/-unwind,+/-sanitizers`, move the shipped libunwind
74    // and sanitizers to self-contained directory, and stop adding this search path.
75    // FIXME: On AIX this also has the side-effect of making the list of library search paths
76    // non-empty, which is needed or the linker may decide to record the LIBPATH env, if
77    // defined, as the search path instead of appending the default search paths.
78    if sess.target.cfg_abi == CfgAbi::Fortanix
79        || sess.target.os == Os::Linux
80        || sess.target.os == Os::Fuchsia
81        || sess.target.is_like_aix
82        || sess.target.is_like_darwin
83            && (!sess.sanitizers().is_empty() || has_shared_llvm_apple_darwin)
84        || sess.target.os == Os::Windows
85            && sess.target.env == Env::Gnu
86            && sess.target.cfg_abi == CfgAbi::Llvm
87    {
88        f(&sess.target_tlib_path.dir, false)?;
89    }
90
91    // Mac Catalyst uses the macOS SDK, but to link to iOS-specific frameworks
92    // we must have the support library stubs in the library search path (#121430).
93    if let Some(sdk_root) = apple_sdk_root
94        && sess.target.env == Env::MacAbi
95    {
96        f(&sdk_root.join("System/iOSSupport/usr/lib"), false)?;
97        f(&sdk_root.join("System/iOSSupport/System/Library/Frameworks"), true)?;
98    }
99
100    ControlFlow::Continue(())
101}
102
103pub fn try_find_native_static_library(
104    sess: &Session,
105    name: &str,
106    verbatim: bool,
107) -> Option<PathBuf> {
108    let default = sess.staticlib_components(verbatim);
109    let formats = if verbatim {
110        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [default]))vec![default]
111    } else {
112        // On Windows, static libraries sometimes show up as libfoo.a and other
113        // times show up as foo.lib
114        let unix = ("lib", ".a");
115        if default == unix { ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [default]))vec![default] } else { ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [default, unix]))vec![default, unix] }
116    };
117
118    walk_native_lib_search_dirs(sess, None, |dir, is_framework| {
119        if !is_framework {
120            for (prefix, suffix) in &formats {
121                let test = dir.join(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}{1}{2}", prefix, name, suffix))
    })format!("{prefix}{name}{suffix}"));
122                if test.exists() {
123                    return ControlFlow::Break(test);
124                }
125            }
126        }
127        ControlFlow::Continue(())
128    })
129    .break_value()
130}
131
132pub fn try_find_native_dynamic_library(
133    sess: &Session,
134    name: &str,
135    verbatim: bool,
136) -> Option<PathBuf> {
137    let default = sess.staticlib_components(verbatim);
138    let formats = if verbatim {
139        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [default]))vec![default]
140    } else {
141        // While the official naming convention for MSVC import libraries
142        // is foo.lib, Meson follows the libfoo.dll.a convention to
143        // disambiguate .a for static libraries
144        let meson = ("lib", ".dll.a");
145        // and MinGW uses .a altogether
146        let mingw = ("lib", ".a");
147        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [default, meson, mingw]))vec![default, meson, mingw]
148    };
149
150    walk_native_lib_search_dirs(sess, None, |dir, is_framework| {
151        if !is_framework {
152            for (prefix, suffix) in &formats {
153                let test = dir.join(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}{1}{2}", prefix, name, suffix))
    })format!("{prefix}{name}{suffix}"));
154                if test.exists() {
155                    return ControlFlow::Break(test);
156                }
157            }
158        }
159        ControlFlow::Continue(())
160    })
161    .break_value()
162}
163
164pub fn find_native_static_library(name: &str, verbatim: bool, sess: &Session) -> PathBuf {
165    try_find_native_static_library(sess, name, verbatim).unwrap_or_else(|| {
166        sess.dcx().emit_fatal(diagnostics::MissingNativeLibrary::new(name, verbatim))
167    })
168}
169
170pub(crate) fn collect(tcx: TyCtxt<'_>, LocalCrate: LocalCrate) -> Vec<NativeLib> {
171    let mut collector = Collector { tcx, libs: Vec::new() };
172    if tcx.sess.opts.unstable_opts.link_directives {
173        for module in tcx.foreign_modules(LOCAL_CRATE).values() {
174            collector.process_module(module);
175        }
176    }
177    collector.process_command_line();
178    for lib in &mut collector.libs {
179        // FIXME(jchlanda) Pauthtest does not support static linking. It must be dynamically linked,
180        // with a dynamic linker acting as the ELF interpreter that can resolve pauth relocations
181        // and enforce pointer authentication constraints.
182        if tcx.sess.target.cfg_abi == CfgAbi::Pauthtest {
183            if let NativeLibKind::Static { .. } = lib.kind {
184                if !tcx.sess.opts.unstable_opts.ui_testing {
185                    let diag = if lib.foreign_module.is_none() {
186                        diagnostics::StaticLinkingNotSupported::UserRequested {
187                            lib_name: lib.name,
188                            target: tcx.sess.target.llvm_target.as_ref(),
189                        }
190                    } else {
191                        diagnostics::StaticLinkingNotSupported::FromDependency {
192                            lib_name: lib.name,
193                            target: tcx.sess.target.llvm_target.as_ref(),
194                        }
195                    };
196                    tcx.dcx().emit_warn(diag);
197                }
198
199                lib.kind = NativeLibKind::Dylib { as_needed: None };
200            }
201        }
202    }
203    collector.libs
204}
205
206pub(crate) fn relevant_lib(sess: &Session, lib: &NativeLib) -> bool {
207    match lib.cfg {
208        Some(ref cfg) => eval_config_entry(sess, cfg).as_bool(),
209        None => true,
210    }
211}
212
213struct Collector<'tcx> {
214    tcx: TyCtxt<'tcx>,
215    libs: Vec<NativeLib>,
216}
217
218impl<'tcx> Collector<'tcx> {
219    fn process_module(&mut self, module: &ForeignModule) {
220        let ForeignModule { def_id, abi, ref foreign_items } = *module;
221        let def_id = def_id.expect_local();
222
223        let sess = self.tcx.sess;
224
225        if #[allow(non_exhaustive_omitted_patterns)] match abi {
    ExternAbi::Rust => true,
    _ => false,
}matches!(abi, ExternAbi::Rust) {
226            return;
227        }
228
229        for attr in {
    {
        'done:
            {
            for i in
                ::rustc_hir::attrs::HasAttrs::get_attrs(def_id, &self.tcx) {
                #[allow(unused_imports)]
                use rustc_hir::attrs::AttributeKind::*;
                let i: &rustc_hir::Attribute = i;
                match i {
                    rustc_hir::Attribute::Parsed(Link(links, _)) => {
                        break 'done Some(links);
                    }
                    rustc_hir::Attribute::Unparsed(..) =>
                        {}
                        #[deny(unreachable_patterns)]
                        _ => {}
                }
            }
            None
        }
    }
}find_attr!(self.tcx, def_id, Link(links, _) => links).into_flat_iter() {
230            let dll_imports = match attr.kind {
231                NativeLibKind::RawDylib { .. } => foreign_items
232                    .iter()
233                    .filter_map(|&child_item| {
234                        self.build_dll_import(
235                            abi,
236                            attr.import_name_type.map(|(import_name_type, _)| import_name_type),
237                            child_item,
238                        )
239                    })
240                    .collect(),
241                _ => {
242                    for &child_item in foreign_items {
243                        if let Some(span) =
244                            {
    {
        'done:
            {
            for i in
                ::rustc_hir::attrs::HasAttrs::get_attrs(child_item, &self.tcx)
                {
                #[allow(unused_imports)]
                use rustc_hir::attrs::AttributeKind::*;
                let i: &rustc_hir::Attribute = i;
                match i {
                    rustc_hir::Attribute::Parsed(LinkOrdinal { span, .. }) => {
                        break 'done Some(*span);
                    }
                    rustc_hir::Attribute::Unparsed(..) =>
                        {}
                        #[deny(unreachable_patterns)]
                        _ => {}
                }
            }
            None
        }
    }
}find_attr!(self.tcx, child_item, LinkOrdinal {span, ..} => *span)
245                        {
246                            sess.dcx().emit_err(diagnostics::LinkOrdinalRawDylib { span });
247                        }
248                    }
249
250                    Vec::new()
251                }
252            };
253
254            self.libs.push(NativeLib {
255                name: attr.name,
256                kind: attr.kind,
257                cfg: attr.cfg.clone(),
258                foreign_module: Some(def_id.to_def_id()),
259                verbatim: attr.verbatim,
260                dll_imports,
261            });
262        }
263    }
264
265    // Process libs passed on the command line
266    fn process_command_line(&mut self) {
267        // First, check for errors
268        let mut renames = FxHashSet::default();
269        for lib in &self.tcx.sess.opts.libs {
270            if let NativeLibKind::Framework { .. } = lib.kind
271                && !self.tcx.sess.target.is_like_darwin
272            {
273                // Cannot check this when parsing options because the target is not yet available.
274                self.tcx.dcx().emit_err(diagnostics::LibFrameworkApple);
275            }
276            if let Some(ref new_name) = lib.new_name {
277                let any_duplicate = self.libs.iter().any(|n| n.name.as_str() == lib.name);
278                if new_name.is_empty() {
279                    self.tcx
280                        .dcx()
281                        .emit_err(diagnostics::EmptyRenamingTarget { lib_name: &lib.name });
282                } else if !any_duplicate {
283                    self.tcx.dcx().emit_err(diagnostics::RenamingNoLink { lib_name: &lib.name });
284                } else if !renames.insert(&lib.name) {
285                    self.tcx.dcx().emit_err(diagnostics::MultipleRenamings { lib_name: &lib.name });
286                }
287            }
288        }
289
290        // Update kind and, optionally, the name of all native libraries
291        // (there may be more than one) with the specified name. If any
292        // library is mentioned more than once, keep the latest mention
293        // of it, so that any possible dependent libraries appear before
294        // it. (This ensures that the linker is able to see symbols from
295        // all possible dependent libraries before linking in the library
296        // in question.)
297        for passed_lib in &self.tcx.sess.opts.libs {
298            // If we've already added any native libraries with the same
299            // name, they will be pulled out into `existing`, so that we
300            // can move them to the end of the list below.
301            let mut existing = self
302                .libs
303                .extract_if(.., |lib| {
304                    if lib.name.as_str() == passed_lib.name {
305                        // FIXME: This whole logic is questionable, whether modifiers are
306                        // involved or not, library reordering and kind overriding without
307                        // explicit `:rename` in particular.
308                        if lib.has_modifiers() || passed_lib.has_modifiers() {
309                            match lib.foreign_module {
310                                Some(def_id) => {
311                                    self.tcx.dcx().emit_err(diagnostics::NoLinkModOverride {
312                                        span: Some(self.tcx.def_span(def_id)),
313                                    })
314                                }
315                                None => self
316                                    .tcx
317                                    .dcx()
318                                    .emit_err(diagnostics::NoLinkModOverride { span: None }),
319                            };
320                        }
321                        if passed_lib.kind != NativeLibKind::Unspecified {
322                            lib.kind = passed_lib.kind;
323                        }
324                        if let Some(new_name) = &passed_lib.new_name {
325                            lib.name = Symbol::intern(new_name);
326                        }
327                        lib.verbatim = passed_lib.verbatim;
328                        return true;
329                    }
330                    false
331                })
332                .collect::<Vec<_>>();
333            if existing.is_empty() {
334                // Add if not found
335                let new_name: Option<&str> = passed_lib.new_name.as_deref();
336                let name = Symbol::intern(new_name.unwrap_or(&passed_lib.name));
337                self.libs.push(NativeLib {
338                    name,
339                    kind: passed_lib.kind,
340                    cfg: None,
341                    foreign_module: None,
342                    verbatim: passed_lib.verbatim,
343                    dll_imports: Vec::new(),
344                });
345            } else {
346                // Move all existing libraries with the same name to the
347                // end of the command line.
348                self.libs.append(&mut existing);
349            }
350        }
351    }
352
353    fn i686_arg_list_size(&self, item: DefId) -> usize {
354        let argument_types: &List<Ty<'_>> = self.tcx.instantiate_bound_regions_with_erased(
355            self.tcx
356                .type_of(item)
357                .instantiate_identity()
358                .skip_norm_wip()
359                .fn_sig(self.tcx)
360                .inputs()
361                .map_bound(|slice| self.tcx.mk_type_list(slice)),
362        );
363
364        argument_types
365            .iter()
366            .map(|ty| {
367                let layout = self
368                    .tcx
369                    .layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(ty))
370                    .expect("layout")
371                    .layout;
372                // In both stdcall and fastcall, we always round up the argument size to the
373                // nearest multiple of 4 bytes.
374                (layout.size().bytes_usize() + 3) & !3
375            })
376            .sum()
377    }
378
379    fn build_dll_import(
380        &self,
381        abi: ExternAbi,
382        import_name_type: Option<PeImportNameType>,
383        item: DefId,
384    ) -> Option<DllImport> {
385        let span = self.tcx.def_span(item);
386
387        // This `extern` block should have been checked for general ABI support before, but let's
388        // double-check that.
389        if !self.tcx.sess.target.is_abi_supported(abi) {
    ::core::panicking::panic("assertion failed: self.tcx.sess.target.is_abi_supported(abi)")
};assert!(self.tcx.sess.target.is_abi_supported(abi));
390
391        // This logic is similar to `AbiMap::canonize_abi` (in rustc_target/src/spec/abi_map.rs) but
392        // we need more detail than those adjustments, and we can't support all ABIs that are
393        // generally supported.
394        let calling_convention = if self.tcx.sess.target.arch == Arch::X86 {
395            match abi {
396                ExternAbi::C { .. } | ExternAbi::Cdecl { .. } => DllCallingConvention::C,
397                ExternAbi::Stdcall { .. } => {
398                    DllCallingConvention::Stdcall(self.i686_arg_list_size(item))
399                }
400                // On Windows, `extern "system"` behaves like msvc's `__stdcall`.
401                // `__stdcall` only applies on x86 and on non-variadic functions:
402                // https://learn.microsoft.com/en-us/cpp/cpp/stdcall?view=msvc-170
403                ExternAbi::System { .. } => {
404                    let c_variadic = self
405                        .tcx
406                        .type_of(item)
407                        .instantiate_identity()
408                        .skip_norm_wip()
409                        .fn_sig(self.tcx)
410                        .c_variadic();
411
412                    if c_variadic {
413                        DllCallingConvention::C
414                    } else {
415                        DllCallingConvention::Stdcall(self.i686_arg_list_size(item))
416                    }
417                }
418                ExternAbi::Fastcall { .. } => {
419                    DllCallingConvention::Fastcall(self.i686_arg_list_size(item))
420                }
421                ExternAbi::Vectorcall { .. } => {
422                    DllCallingConvention::Vectorcall(self.i686_arg_list_size(item))
423                }
424                _ => {
425                    self.tcx.dcx().emit_fatal(diagnostics::RawDylibUnsupportedAbi { span });
426                }
427            }
428        } else {
429            match abi {
430                ExternAbi::C { .. } | ExternAbi::Win64 { .. } | ExternAbi::System { .. } => {
431                    DllCallingConvention::C
432                }
433                _ => {
434                    self.tcx.dcx().emit_fatal(diagnostics::RawDylibUnsupportedAbi { span });
435                }
436            }
437        };
438
439        let codegen_fn_attrs = self.tcx.codegen_fn_attrs(item);
440        let import_name_type = codegen_fn_attrs
441            .link_ordinal
442            .map_or(import_name_type, |ord| Some(PeImportNameType::Ordinal(ord)));
443
444        let name = codegen_fn_attrs.symbol_name.unwrap_or_else(|| self.tcx.item_name(item));
445
446        if self.tcx.sess.target.binary_format == BinaryFormat::Elf {
447            let name = name.as_str();
448            if name.contains('\0') {
449                self.tcx.dcx().emit_err(diagnostics::RawDylibMalformed { span });
450            } else if let Some((left, right)) = name.split_once('@')
451                && (left.is_empty() || right.is_empty() || right.contains('@'))
452            {
453                self.tcx.dcx().emit_err(diagnostics::RawDylibMalformed { span });
454            }
455        }
456
457        let def_kind = self.tcx.def_kind(item);
458        let symbol_type = if def_kind.is_fn_like() {
459            DllImportSymbolType::Function
460        } else if #[allow(non_exhaustive_omitted_patterns)] match def_kind {
    DefKind::Static { .. } => true,
    _ => false,
}matches!(def_kind, DefKind::Static { .. }) {
461            if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::THREAD_LOCAL) {
462                DllImportSymbolType::ThreadLocal
463            } else {
464                DllImportSymbolType::Static
465            }
466        } else if def_kind == DefKind::ForeignTy {
467            return None;
468        } else {
469            ::rustc_middle::util::bug::bug_fmt(format_args!("Unexpected type for raw-dylib: {0}",
        def_kind.descr(item)));bug!("Unexpected type for raw-dylib: {}", def_kind.descr(item));
470        };
471
472        let size = match symbol_type {
473            // We cannot determine the size of a function at compile time, but it shouldn't matter anyway.
474            DllImportSymbolType::Function => rustc_abi::Size::ZERO,
475            DllImportSymbolType::Static | DllImportSymbolType::ThreadLocal => {
476                let ty = self.tcx.type_of(item).instantiate_identity().skip_norm_wip();
477                self.tcx
478                    .layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(ty))
479                    .ok()
480                    .map(|layout| layout.size)
481                    .unwrap_or_else(|| ::rustc_middle::util::bug::bug_fmt(format_args!("Non-function symbols must have a size"))bug!("Non-function symbols must have a size"))
482            }
483        };
484
485        Some(DllImport { name, import_name_type, calling_convention, span, symbol_type, size })
486    }
487}