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