rustc_metadata/
native_libs.rs

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