Skip to main content

rustc_codegen_ssa/back/
archive.rs

1use std::borrow::Cow;
2use std::env;
3use std::error::Error;
4use std::ffi::OsString;
5use std::fs::{self, File};
6use std::io::{self, BufWriter, Write};
7use std::path::{Path, PathBuf};
8
9use ar_archive_writer::{
10    ArchiveKind, COFFShortExport, MachineTypes, NewArchiveMember, write_archive_to_stream,
11};
12pub use ar_archive_writer::{DEFAULT_OBJECT_READER, ObjectReader};
13use object::read::archive::{ArchiveFile, ArchiveKind as ObjectArchiveKind};
14use object::read::macho::FatArch;
15use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
16use rustc_data_structures::memmap::Mmap;
17use rustc_fs_util::TempDirBuilder;
18use rustc_metadata::EncodedMetadata;
19use rustc_session::Session;
20use rustc_span::Symbol;
21use rustc_target::spec::Arch;
22use tracing::trace;
23
24use super::metadata::{create_compressed_metadata_file, search_for_section};
25use super::rmeta_link::{self, RmetaLinkCache};
26use super::symbol_edit::{apply_edits, collect_internal_names};
27use crate::common;
28// Public for ArchiveBuilderBuilder::extract_bundled_libs
29pub use crate::diagnostics::ExtractBundledLibsError;
30use crate::diagnostics::{
31    ArchiveBuildFailure, DlltoolFailImportLibrary, ErrorCallingDllTool, ErrorCreatingImportLibrary,
32    ErrorWritingDEFFile, UnknownArchiveKind,
33};
34
35/// An item to be included in an import library.
36/// This is a slimmed down version of `COFFShortExport` from `ar-archive-writer`.
37pub struct ImportLibraryItem {
38    /// The name to be exported.
39    pub name: String,
40    /// The ordinal to be exported, if any.
41    pub ordinal: Option<u16>,
42    /// The original, decorated name if `name` is not decorated.
43    pub symbol_name: Option<String>,
44    /// True if this is a data export, false if it is a function export.
45    pub is_data: bool,
46}
47
48impl ImportLibraryItem {
49    fn into_coff_short_export(self, sess: &Session) -> COFFShortExport {
50        let import_name = (sess.target.arch == Arch::Arm64EC).then(|| self.name.clone());
51        COFFShortExport {
52            name: self.name,
53            ext_name: None,
54            symbol_name: self.symbol_name,
55            import_name,
56            export_as: None,
57            ordinal: self.ordinal.unwrap_or(0),
58            noname: self.ordinal.is_some(),
59            data: self.is_data,
60            private: false,
61            constant: false,
62        }
63    }
64}
65
66pub trait ArchiveBuilderBuilder {
67    fn new_archive_builder<'a>(&self, sess: &'a Session) -> Box<dyn ArchiveBuilder + 'a>;
68
69    fn create_dylib_metadata_wrapper(
70        &self,
71        sess: &Session,
72        metadata: &EncodedMetadata,
73        symbol_name: &str,
74    ) -> Vec<u8> {
75        create_compressed_metadata_file(sess, metadata, symbol_name)
76    }
77
78    /// Creates a DLL Import Library <https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-creation#creating-an-import-library>.
79    /// and returns the path on disk to that import library.
80    /// This functions doesn't take `self` so that it can be called from
81    /// `linker_with_args`, which is specialized on `ArchiveBuilder` but
82    /// doesn't take or create an instance of that type.
83    fn create_dll_import_lib(
84        &self,
85        sess: &Session,
86        lib_name: &str,
87        items: Vec<ImportLibraryItem>,
88        output_path: &Path,
89    ) {
90        if common::is_using_dlltool(&sess.target) {
91            // The binutils linker used on -windows-gnu targets cannot read the import
92            // libraries generated by LLVM: in our attempts, the linker produced an .EXE
93            // that loaded but crashed with an AV upon calling one of the imported
94            // functions. Therefore, use binutils to create the import library instead,
95            // by writing a .DEF file to the temp dir and calling binutils's dlltool.
96            create_mingw_dll_import_lib(sess, lib_name, items, output_path);
97        } else {
98            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/12433c307b5a497388ffe2f4e49dbf1ab8c71fa0/compiler/rustc_codegen_ssa/src/back/archive.rs:98",
                        "rustc_codegen_ssa::back::archive", ::tracing::Level::TRACE,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/12433c307b5a497388ffe2f4e49dbf1ab8c71fa0/compiler/rustc_codegen_ssa/src/back/archive.rs"),
                        ::tracing_core::__macro_support::Option::Some(98u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_codegen_ssa::back::archive"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::TRACE <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("creating import library")
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};trace!("creating import library");
99            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/12433c307b5a497388ffe2f4e49dbf1ab8c71fa0/compiler/rustc_codegen_ssa/src/back/archive.rs:99",
                        "rustc_codegen_ssa::back::archive", ::tracing::Level::TRACE,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/12433c307b5a497388ffe2f4e49dbf1ab8c71fa0/compiler/rustc_codegen_ssa/src/back/archive.rs"),
                        ::tracing_core::__macro_support::Option::Some(99u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_codegen_ssa::back::archive"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::TRACE <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("  dll_name {0:#?}",
                                                    lib_name) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};trace!("  dll_name {:#?}", lib_name);
100            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/12433c307b5a497388ffe2f4e49dbf1ab8c71fa0/compiler/rustc_codegen_ssa/src/back/archive.rs:100",
                        "rustc_codegen_ssa::back::archive", ::tracing::Level::TRACE,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/12433c307b5a497388ffe2f4e49dbf1ab8c71fa0/compiler/rustc_codegen_ssa/src/back/archive.rs"),
                        ::tracing_core::__macro_support::Option::Some(100u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_codegen_ssa::back::archive"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::TRACE <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("  output_path {0}",
                                                    output_path.display()) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};trace!("  output_path {}", output_path.display());
101            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/12433c307b5a497388ffe2f4e49dbf1ab8c71fa0/compiler/rustc_codegen_ssa/src/back/archive.rs:101",
                        "rustc_codegen_ssa::back::archive", ::tracing::Level::TRACE,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/12433c307b5a497388ffe2f4e49dbf1ab8c71fa0/compiler/rustc_codegen_ssa/src/back/archive.rs"),
                        ::tracing_core::__macro_support::Option::Some(101u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_codegen_ssa::back::archive"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::TRACE <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("  import names: {0}",
                                                    items.iter().map(|ImportLibraryItem { name, .. }|
                                                                    name.clone()).collect::<Vec<_>>().join(", ")) as
                                            &dyn ::tracing::field::Value))])
            });
    } else { ; }
};trace!(
102                "  import names: {}",
103                items
104                    .iter()
105                    .map(|ImportLibraryItem { name, .. }| name.clone())
106                    .collect::<Vec<_>>()
107                    .join(", "),
108            );
109
110            // All import names are Rust identifiers and therefore cannot contain \0 characters.
111            // FIXME: when support for #[link_name] is implemented, ensure that the import names
112            // still don't contain any \0 characters. Also need to check that the names don't
113            // contain substrings like " @" or "NONAME" that are keywords or otherwise reserved
114            // in definition files.
115
116            let mut file = match fs::File::create_new(&output_path) {
117                Ok(file) => file,
118                Err(error) => sess
119                    .dcx()
120                    .emit_fatal(ErrorCreatingImportLibrary { lib_name, error: error.to_string() }),
121            };
122
123            let exports =
124                items.into_iter().map(|item| item.into_coff_short_export(sess)).collect::<Vec<_>>();
125            let machine = match &sess.target.arch {
126                Arch::X86_64 => MachineTypes::AMD64,
127                Arch::X86 => MachineTypes::I386,
128                Arch::AArch64 => MachineTypes::ARM64,
129                Arch::Arm64EC => MachineTypes::ARM64EC,
130                Arch::Arm => MachineTypes::ARMNT,
131                cpu => {
    ::core::panicking::panic_fmt(format_args!("unsupported cpu type {0}",
            cpu));
}panic!("unsupported cpu type {cpu}"),
132            };
133
134            if let Err(error) = ar_archive_writer::write_import_library(
135                &mut file,
136                lib_name,
137                &exports,
138                machine,
139                !sess.target.is_like_msvc,
140                // Enable compatibility with MSVC's `/WHOLEARCHIVE` flag.
141                // Without this flag a duplicate symbol error would be emitted
142                // when linking a rust staticlib using `/WHOLEARCHIVE`.
143                // See #129020
144                true,
145                &[],
146            ) {
147                sess.dcx()
148                    .emit_fatal(ErrorCreatingImportLibrary { lib_name, error: error.to_string() });
149            }
150        }
151    }
152
153    fn extract_bundled_libs<'a>(
154        &'a self,
155        rlib: &'a Path,
156        outdir: &Path,
157        bundled_lib_file_names: &FxIndexSet<Symbol>,
158    ) -> Result<(), ExtractBundledLibsError<'a>> {
159        let archive_map = unsafe {
160            Mmap::map(
161                File::open(rlib)
162                    .map_err(|e| ExtractBundledLibsError::OpenFile { rlib, error: Box::new(e) })?,
163            )
164            .map_err(|e| ExtractBundledLibsError::MmapFile { rlib, error: Box::new(e) })?
165        };
166        let archive = ArchiveFile::parse(&*archive_map)
167            .map_err(|e| ExtractBundledLibsError::ParseArchive { rlib, error: Box::new(e) })?;
168
169        for entry in archive.members() {
170            let entry = entry
171                .map_err(|e| ExtractBundledLibsError::ReadEntry { rlib, error: Box::new(e) })?;
172            let data = entry
173                .data(&*archive_map)
174                .map_err(|e| ExtractBundledLibsError::ArchiveMember { rlib, error: Box::new(e) })?;
175            let name = std::str::from_utf8(entry.name())
176                .map_err(|e| ExtractBundledLibsError::ConvertName { rlib, error: Box::new(e) })?;
177            if !bundled_lib_file_names.contains(&Symbol::intern(name)) {
178                continue; // We need to extract only native libraries.
179            }
180            let data = search_for_section(rlib, data, ".bundled_lib").map_err(|e| {
181                ExtractBundledLibsError::ExtractSection { rlib, error: Box::<dyn Error>::from(e) }
182            })?;
183            std::fs::write(&outdir.join(&name), data)
184                .map_err(|e| ExtractBundledLibsError::WriteFile { rlib, error: Box::new(e) })?;
185        }
186        Ok(())
187    }
188}
189
190fn create_mingw_dll_import_lib(
191    sess: &Session,
192    lib_name: &str,
193    items: Vec<ImportLibraryItem>,
194    output_path: &Path,
195) {
196    let def_file_path = output_path.with_extension("def");
197
198    let def_file_content = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("EXPORTS\n{0}",
                items.into_iter().map(|ImportLibraryItem { name, ordinal, ..
                                    }|
                                {
                                    match ordinal {
                                        Some(n) =>
                                            ::alloc::__export::must_use({
                                                    ::alloc::fmt::format(format_args!("{0} @{1} NONAME", name,
                                                            n))
                                                }),
                                        None => name,
                                    }
                                }).collect::<Vec<String>>().join("\n")))
    })format!(
199        "EXPORTS\n{}",
200        items
201            .into_iter()
202            .map(|ImportLibraryItem { name, ordinal, .. }| {
203                match ordinal {
204                    Some(n) => format!("{name} @{n} NONAME"),
205                    None => name,
206                }
207            })
208            .collect::<Vec<String>>()
209            .join("\n")
210    );
211
212    match std::fs::write(&def_file_path, def_file_content) {
213        Ok(_) => {}
214        Err(e) => {
215            sess.dcx().emit_fatal(ErrorWritingDEFFile { error: e });
216        }
217    };
218
219    // --no-leading-underscore: For the `import_name_type` feature to work, we need to be
220    // able to control the *exact* spelling of each of the symbols that are being imported:
221    // hence we don't want `dlltool` adding leading underscores automatically.
222    let dlltool = find_binutils_dlltool(sess);
223    // temp_prefix doesn't handle paths with spaces so
224    // use a relative path and set the current working directory
225    let cwd = output_path.parent().unwrap_or(output_path);
226    let temp_prefix = lib_name;
227    // dlltool target architecture args from:
228    // https://github.com/llvm/llvm-project-release-prs/blob/llvmorg-15.0.6/llvm/lib/ToolDrivers/llvm-dlltool/DlltoolDriver.cpp#L69
229    let (dlltool_target_arch, dlltool_target_bitness) = match &sess.target.arch {
230        Arch::X86_64 => ("i386:x86-64", "--64"),
231        Arch::X86 => ("i386", "--32"),
232        Arch::AArch64 => ("arm64", "--64"),
233        Arch::Arm => ("arm", "--32"),
234        arch => { ::core::panicking::panic_fmt(format_args!("unsupported arch {0}", arch)); }panic!("unsupported arch {arch}"),
235    };
236    let mut dlltool_cmd = std::process::Command::new(&dlltool);
237    dlltool_cmd
238        .arg("-d")
239        .arg(def_file_path)
240        .arg("-D")
241        .arg(lib_name)
242        .arg("-l")
243        .arg(&output_path)
244        .arg("-m")
245        .arg(dlltool_target_arch)
246        .arg("-f")
247        .arg(dlltool_target_bitness)
248        .arg("--no-leading-underscore")
249        .arg("--temp-prefix")
250        .arg(temp_prefix)
251        .current_dir(cwd);
252
253    match dlltool_cmd.output() {
254        Err(e) => {
255            sess.dcx().emit_fatal(ErrorCallingDllTool {
256                dlltool_path: dlltool.to_string_lossy(),
257                error: e,
258            });
259        }
260        // dlltool returns '0' on failure, so check for error output instead.
261        Ok(output) if !output.stderr.is_empty() => {
262            sess.dcx().emit_fatal(DlltoolFailImportLibrary {
263                dlltool_path: dlltool.to_string_lossy(),
264                dlltool_args: dlltool_cmd
265                    .get_args()
266                    .map(|arg| arg.to_string_lossy())
267                    .collect::<Vec<_>>()
268                    .join(" "),
269                stdout: String::from_utf8_lossy(&output.stdout),
270                stderr: String::from_utf8_lossy(&output.stderr),
271            })
272        }
273        _ => {}
274    }
275}
276
277fn find_binutils_dlltool(sess: &Session) -> OsString {
278    if !(sess.target.options.is_like_windows && !sess.target.options.is_like_msvc)
    {
    ::core::panicking::panic("assertion failed: sess.target.options.is_like_windows && !sess.target.options.is_like_msvc")
};assert!(sess.target.options.is_like_windows && !sess.target.options.is_like_msvc);
279    if let Some(dlltool_path) = &sess.opts.cg.dlltool {
280        return dlltool_path.clone().into_os_string();
281    }
282
283    let tool_name: OsString = if sess.host.options.is_like_windows {
284        // If we're compiling on Windows, always use "dlltool.exe".
285        "dlltool.exe"
286    } else {
287        // On other platforms, use the architecture-specific name.
288        match sess.target.arch {
289            Arch::X86_64 => "x86_64-w64-mingw32-dlltool",
290            Arch::X86 => "i686-w64-mingw32-dlltool",
291            Arch::AArch64 => "aarch64-w64-mingw32-dlltool",
292
293            // For non-standard architectures (e.g., aarch32) fallback to "dlltool".
294            _ => "dlltool",
295        }
296    }
297    .into();
298
299    // NOTE: it's not clear how useful it is to explicitly search PATH.
300    for dir in env::split_paths(&env::var_os("PATH").unwrap_or_default()) {
301        let full_path = dir.join(&tool_name);
302        if full_path.is_file() {
303            return full_path.into_os_string();
304        }
305    }
306
307    // The user didn't specify the location of the dlltool binary, and we weren't able
308    // to find the appropriate one on the PATH. Just return the name of the tool
309    // and let the invocation fail with a hopefully useful error message.
310    tool_name
311}
312
313pub enum AddArchiveKind<'a> {
314    Rlib(&'a mut RmetaLinkCache, /*skip*/ &'a dyn Fn(&str, ArchiveEntryKind) -> bool),
315    Other,
316}
317
318pub struct ArchiveSymbols {
319    pub exported: FxHashSet<String>,
320    pub rename_suffix: Option<String>,
321    pub hide: bool,
322}
323
324pub trait ArchiveBuilder {
325    fn add_file(&mut self, path: &Path, kind: ArchiveEntryKind);
326
327    fn add_archive(&mut self, archive: &Path, kind: AddArchiveKind<'_>) -> io::Result<()>;
328
329    fn build(self: Box<Self>, output: &Path, symbols: Option<ArchiveSymbols>) -> bool;
330}
331
332fn target_archive_format_to_object_kind(format: &str) -> Option<ObjectArchiveKind> {
333    match format {
334        "gnu" => Some(ObjectArchiveKind::Gnu),
335        "bsd" => Some(ObjectArchiveKind::Bsd),
336        "darwin" => Some(ObjectArchiveKind::Bsd64),
337        "coff" => Some(ObjectArchiveKind::Coff),
338        "aix_big" => Some(ObjectArchiveKind::AixBig),
339        _ => None,
340    }
341}
342
343fn archive_kinds_compatible(actual: ObjectArchiveKind, expected: ObjectArchiveKind) -> bool {
344    if actual == expected {
345        return true;
346    }
347    #[allow(non_exhaustive_omitted_patterns)] match (actual, expected) {
    (ObjectArchiveKind::Unknown, _) |
        (ObjectArchiveKind::Gnu64, ObjectArchiveKind::Gnu) |
        (ObjectArchiveKind::Gnu, ObjectArchiveKind::Gnu64) |
        (ObjectArchiveKind::Bsd64, ObjectArchiveKind::Bsd) |
        (ObjectArchiveKind::Bsd, ObjectArchiveKind::Bsd64) |
        (ObjectArchiveKind::Gnu, ObjectArchiveKind::Coff) |
        (ObjectArchiveKind::Coff, ObjectArchiveKind::Gnu) |
        (ObjectArchiveKind::Gnu64, ObjectArchiveKind::Coff) => true,
    _ => false,
}matches!(
348        (actual, expected),
349        // An archive without long filenames or symbol table is detected as Unknown;
350        // this is compatible with any target format.
351        (ObjectArchiveKind::Unknown, _)
352        // 64-bit symbol table variants are compatible with their 32-bit counterparts
353        | (ObjectArchiveKind::Gnu64, ObjectArchiveKind::Gnu)
354        | (ObjectArchiveKind::Gnu, ObjectArchiveKind::Gnu64)
355        | (ObjectArchiveKind::Bsd64, ObjectArchiveKind::Bsd)
356        | (ObjectArchiveKind::Bsd, ObjectArchiveKind::Bsd64)
357        // GNU and COFF archives share the same magic and member header format;
358        // only the symbol table layout differs.
359        | (ObjectArchiveKind::Gnu, ObjectArchiveKind::Coff)
360        | (ObjectArchiveKind::Coff, ObjectArchiveKind::Gnu)
361        | (ObjectArchiveKind::Gnu64, ObjectArchiveKind::Coff)
362    )
363}
364
365fn archive_kind_display_name(kind: ObjectArchiveKind) -> String {
366    match kind {
367        ObjectArchiveKind::Gnu | ObjectArchiveKind::Gnu64 => "GNU".to_string(),
368        ObjectArchiveKind::Bsd => "BSD".to_string(),
369        ObjectArchiveKind::Bsd64 => "Darwin".to_string(),
370        ObjectArchiveKind::Coff => "COFF".to_string(),
371        ObjectArchiveKind::AixBig => "AIX big".to_string(),
372        _ => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0:?}", kind))
    })format!("{kind:?}"),
373    }
374}
375
376pub struct ArArchiveBuilderBuilder;
377
378impl ArchiveBuilderBuilder for ArArchiveBuilderBuilder {
379    fn new_archive_builder<'a>(&self, sess: &'a Session) -> Box<dyn ArchiveBuilder + 'a> {
380        Box::new(ArArchiveBuilder::new(sess, &DEFAULT_OBJECT_READER))
381    }
382}
383
384#[must_use = "must call build() to finish building the archive"]
385pub struct ArArchiveBuilder<'a> {
386    sess: &'a Session,
387    object_reader: &'static ObjectReader,
388
389    src_archives: Vec<(PathBuf, Mmap)>,
390    // Don't use an `HashMap` here, as the order is important. `lib.rmeta` needs
391    // to be at the end of an archive in some cases for linkers to not get confused.
392    entries: Vec<(Vec<u8>, ArchiveEntry)>,
393}
394
395#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for ArchiveEntryKind { }
#[automatically_derived]
impl ::core::clone::Clone for ArchiveEntryKind {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ArchiveEntryKind { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for ArchiveEntryKind { }
#[automatically_derived]
impl ::core::cmp::PartialEq for ArchiveEntryKind {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::fmt::Debug for ArchiveEntryKind {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                ArchiveEntryKind::RustObj => "RustObj",
                ArchiveEntryKind::Other => "Other",
            })
    }
}Debug)]
396pub enum ArchiveEntryKind {
397    /// Object file produced from Rust code.
398    RustObj,
399    /// Anything else, introduce new variants as needed.
400    Other,
401}
402
403#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ArchiveEntrySource {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Self::Archive { archive_index: __self_0, file_range: __self_1 } =>
                ::core::fmt::Formatter::debug_struct_field2_finish(f,
                    "Archive", "archive_index", __self_0, "file_range",
                    &__self_1),
            Self::File(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "File",
                    &__self_0),
        }
    }
}Debug)]
404enum ArchiveEntrySource {
405    Archive { archive_index: usize, file_range: (u64, u64) },
406    File(PathBuf),
407}
408
409#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ArchiveEntry {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "ArchiveEntry",
            "source", &self.source, "kind", &&self.kind)
    }
}Debug)]
410struct ArchiveEntry {
411    source: ArchiveEntrySource,
412    kind: ArchiveEntryKind,
413}
414
415impl<'a> ArArchiveBuilder<'a> {
416    pub fn new(sess: &'a Session, object_reader: &'static ObjectReader) -> ArArchiveBuilder<'a> {
417        ArArchiveBuilder { sess, object_reader, src_archives: ::alloc::vec::Vec::new()vec![], entries: ::alloc::vec::Vec::new()vec![] }
418    }
419}
420
421fn try_filter_fat_archs(
422    archs: &[impl FatArch],
423    target_arch: object::Architecture,
424    archive_path: &Path,
425    archive_map_data: &[u8],
426) -> io::Result<Option<PathBuf>> {
427    let desired = match archs.iter().find(|a| a.architecture() == target_arch) {
428        Some(a) => a,
429        None => return Ok(None),
430    };
431
432    let (mut new_f, extracted_path) = tempfile::Builder::new()
433        .suffix(archive_path.file_name().unwrap())
434        .tempfile()?
435        .keep()
436        .unwrap();
437
438    new_f.write_all(
439        desired.data(archive_map_data).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?,
440    )?;
441
442    Ok(Some(extracted_path))
443}
444
445pub fn try_extract_macho_fat_archive(
446    sess: &Session,
447    archive_path: &Path,
448) -> io::Result<Option<PathBuf>> {
449    let archive_map = unsafe { Mmap::map(File::open(&archive_path)?)? };
450    let target_arch = match sess.target.arch {
451        Arch::AArch64 => object::Architecture::Aarch64,
452        Arch::X86_64 => object::Architecture::X86_64,
453        _ => return Ok(None),
454    };
455
456    if let Ok(h) = object::read::macho::MachOFatFile32::parse(&*archive_map) {
457        let archs = h.arches();
458        try_filter_fat_archs(archs, target_arch, archive_path, &*archive_map)
459    } else if let Ok(h) = object::read::macho::MachOFatFile64::parse(&*archive_map) {
460        let archs = h.arches();
461        try_filter_fat_archs(archs, target_arch, archive_path, &*archive_map)
462    } else {
463        // Not a FatHeader at all, just return None.
464        Ok(None)
465    }
466}
467
468impl<'a> ArchiveBuilder for ArArchiveBuilder<'a> {
469    fn add_archive(
470        &mut self,
471        archive_path: &Path,
472        mut ar_kind: AddArchiveKind<'_>,
473    ) -> io::Result<()> {
474        let mut archive_path = archive_path.to_path_buf();
475        if self.sess.target.llvm_target.contains("-apple-macosx")
476            && let Some(new_archive_path) = try_extract_macho_fat_archive(self.sess, &archive_path)?
477        {
478            archive_path = new_archive_path
479        }
480
481        if self.src_archives.iter().any(|archive| archive.0 == archive_path) {
482            return Ok(());
483        }
484
485        let archive_map = unsafe { Mmap::map(File::open(&archive_path)?)? };
486        let archive = ArchiveFile::parse(&*archive_map)
487            .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
488        let skip = match &ar_kind {
489            AddArchiveKind::Rlib(_, skip) => Some(*skip),
490            AddArchiveKind::Other => None,
491        };
492        let metadata_link = match &mut ar_kind {
493            AddArchiveKind::Rlib(cache, _) => cache.get_or_insert_with(&archive_path, || {
494                rmeta_link::read(
495                    &archive,
496                    &archive_map,
497                    &archive_path,
498                    self.sess.target.is_like_aix,
499                )
500            }),
501            AddArchiveKind::Other => None,
502        };
503        let archive_index = self.src_archives.len();
504
505        if let Some(expected_kind) =
506            target_archive_format_to_object_kind(&self.sess.target.archive_format)
507        {
508            let actual_kind = archive.kind();
509            if !archive_kinds_compatible(actual_kind, expected_kind) {
510                self.sess.dcx().emit_warn(crate::diagnostics::IncompatibleArchiveFormat {
511                    path: archive_path.clone(),
512                    actual: archive_kind_display_name(actual_kind),
513                    expected: archive_kind_display_name(expected_kind),
514                });
515            }
516        }
517
518        for entry in archive.members() {
519            let entry = entry.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
520            let file_name = String::from_utf8(entry.name().to_vec())
521                .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
522            let kind = if metadata_link
523                .as_ref()
524                .is_some_and(|m| m.rust_object_files.iter().any(|f| f == &file_name))
525            {
526                ArchiveEntryKind::RustObj
527            } else {
528                ArchiveEntryKind::Other
529            };
530            let drop = match skip {
531                Some(skip) => skip(&file_name, kind),
532                None => false,
533            };
534            if !drop {
535                let source = if entry.is_thin() {
536                    let member_path = archive_path.parent().unwrap().join(Path::new(&file_name));
537                    ArchiveEntrySource::File(member_path)
538                } else {
539                    ArchiveEntrySource::Archive { archive_index, file_range: entry.file_range() }
540                };
541                self.entries.push((file_name.into_bytes(), ArchiveEntry { source, kind }));
542            }
543        }
544
545        self.src_archives.push((archive_path, archive_map));
546        Ok(())
547    }
548
549    /// Adds an arbitrary file to this archive
550    fn add_file(&mut self, file: &Path, kind: ArchiveEntryKind) {
551        self.entries.push((
552            file.file_name().unwrap().to_str().unwrap().to_string().into_bytes(),
553            ArchiveEntry { source: ArchiveEntrySource::File(file.to_owned()), kind },
554        ));
555    }
556
557    /// Combine the provided files, rlibs, and native libraries into a single
558    /// `Archive`.
559    fn build(self: Box<Self>, output: &Path, symbols: Option<ArchiveSymbols>) -> bool {
560        let sess = self.sess;
561        match self.build_inner(output, symbols) {
562            Ok(any_members) => any_members,
563            Err(error) => {
564                sess.dcx().emit_fatal(ArchiveBuildFailure { path: output.to_owned(), error })
565            }
566        }
567    }
568}
569
570impl<'a> ArArchiveBuilder<'a> {
571    fn build_inner(self, output: &Path, symbols: Option<ArchiveSymbols>) -> io::Result<bool> {
572        let archive_kind = match &*self.sess.target.archive_format {
573            "gnu" => ArchiveKind::Gnu,
574            "bsd" => ArchiveKind::Bsd,
575            "darwin" => ArchiveKind::Darwin,
576            "coff" => ArchiveKind::Coff,
577            "aix_big" => ArchiveKind::AixBig,
578            kind => {
579                self.sess.dcx().emit_fatal(UnknownArchiveKind { kind });
580            }
581        };
582
583        // Collect all internally-defined symbol names across every Rust object file.
584        // This set is needed because rename must also apply to *undefined* references
585        // (cross-object calls within the staticlib), but we cannot use `!exported.contains(name)`
586        // alone — that would also match external C symbols like `malloc` which must not be renamed.
587        let rename = if let Some(sym) = &symbols
588            && let Some(rename_suffix) = sym.rename_suffix.as_deref()
589        {
590            let mut names = FxHashSet::default();
591            for (_, entry) in &self.entries {
592                if entry.kind != ArchiveEntryKind::RustObj {
593                    continue;
594                }
595                match &entry.source {
596                    ArchiveEntrySource::Archive { archive_index, file_range } => {
597                        let src_archive = &self.src_archives[*archive_index];
598                        let start = file_range.0 as usize;
599                        let end = start + file_range.1 as usize;
600                        if let Some(data) = src_archive.1.get(start..end) {
601                            collect_internal_names(data, &sym.exported, &mut names);
602                        }
603                    }
604                    ArchiveEntrySource::File(file) => {
605                        if let Ok(data) = fs::read(file) {
606                            collect_internal_names(&data, &sym.exported, &mut names);
607                        }
608                    }
609                }
610            }
611            Some((names, rename_suffix))
612        } else {
613            None
614        };
615
616        let mut entries = Vec::new();
617
618        for (entry_name, entry) in self.entries {
619            let data: Box<dyn AsRef<[u8]>> = match entry.source {
620                ArchiveEntrySource::Archive { archive_index, file_range } => {
621                    let src_archive = &self.src_archives[archive_index];
622                    let archive_data = &src_archive.1;
623                    let start = file_range.0 as usize;
624                    let end = start + file_range.1 as usize;
625                    let Some(data) = archive_data.get(start..end) else {
626                        return Err(io_error_context(
627                            "invalid archive member",
628                            io::Error::new(
629                                io::ErrorKind::InvalidData,
630                                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("archive member of {0} at offset {4} with size {1} exceeds archive size {2} in `{3}`",
                src_archive.0.display(), file_range.1, archive_data.len(),
                src_archive.0.display(), start))
    })format!(
631                                    "archive member of {} at offset {start} with size {} \
632                                         exceeds archive size {} in `{}`",
633                                    src_archive.0.display(),
634                                    file_range.1,
635                                    archive_data.len(),
636                                    src_archive.0.display(),
637                                ),
638                            ),
639                        ));
640                    };
641
642                    if entry.kind == ArchiveEntryKind::RustObj
643                        && let Some(sym) = &symbols
644                    {
645                        Box::new(apply_edits(data, &sym.exported, sym.hide, rename.as_ref()))
646                    } else {
647                        Box::new(data)
648                    }
649                }
650                ArchiveEntrySource::File(file) => unsafe {
651                    let mmap = Mmap::map(File::open(&file).map_err(|err| {
652                        io_error_context(
653                            &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("failed to open object file {0}",
                file.display()))
    })format!("failed to open object file {}", file.display()),
654                            err,
655                        )
656                    })?)
657                    .map_err(|err| {
658                        io_error_context(
659                            &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("failed to map object file {0}",
                file.display()))
    })format!("failed to map object file {}", file.display()),
660                            err,
661                        )
662                    })?;
663                    if entry.kind == ArchiveEntryKind::RustObj
664                        && let Some(sym) = &symbols
665                    {
666                        let edited = apply_edits(&mmap, &sym.exported, sym.hide, rename.as_ref());
667                        match edited {
668                            Cow::Borrowed(_) => Box::new(mmap) as Box<dyn AsRef<[u8]>>,
669                            Cow::Owned(v) => Box::new(v),
670                        }
671                    } else {
672                        Box::new(mmap) as Box<dyn AsRef<[u8]>>
673                    }
674                },
675            };
676
677            entries.push(NewArchiveMember {
678                buf: data,
679                object_reader: self.object_reader,
680                member_name: String::from_utf8(entry_name).unwrap(),
681                mtime: 0,
682                uid: 0,
683                gid: 0,
684                perms: 0o644,
685            })
686        }
687
688        // Write to a temporary file first before atomically renaming to the final name.
689        // This prevents programs (including rustc) from attempting to read a partial archive.
690        // It also enables writing an archive with the same filename as a dependency on Windows as
691        // required by a test.
692        // The tempfile crate currently uses 0o600 as mode for the temporary files and directories
693        // it creates. We need it to be the default mode for back compat reasons however. (See
694        // #107495) To handle this we are telling tempfile to create a temporary directory instead
695        // and then inside this directory create a file using File::create.
696        let archive_tmpdir = TempDirBuilder::new()
697            .suffix(".temp-archive")
698            .tempdir_in(output.parent().unwrap_or_else(|| Path::new("")))
699            .map_err(|err| {
700                io_error_context("couldn't create a directory for the temp file", err)
701            })?;
702        let archive_tmpfile_path = archive_tmpdir.path().join("tmp.a");
703        let archive_tmpfile = File::create_new(&archive_tmpfile_path)
704            .map_err(|err| io_error_context("couldn't create the temp file", err))?;
705
706        let mut archive_tmpfile = BufWriter::new(archive_tmpfile);
707        write_archive_to_stream(
708            &mut archive_tmpfile,
709            &entries,
710            archive_kind,
711            false,
712            /* is_ec = */ Some(self.sess.target.arch == Arch::Arm64EC),
713        )?;
714        archive_tmpfile.flush()?;
715        drop(archive_tmpfile);
716
717        let any_entries = !entries.is_empty();
718        drop(entries);
719        // Drop src_archives to unmap all input archives, which is necessary if we want to write the
720        // output archive to the same location as an input archive on Windows.
721        drop(self.src_archives);
722
723        fs::rename(archive_tmpfile_path, output)
724            .map_err(|err| io_error_context("failed to rename archive file", err))?;
725        archive_tmpdir
726            .close()
727            .map_err(|err| io_error_context("failed to remove temporary directory", err))?;
728
729        Ok(any_entries)
730    }
731}
732
733fn io_error_context(context: &str, err: io::Error) -> io::Error {
734    io::Error::new(io::ErrorKind::Other, ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}: {1}", context, err))
    })format!("{context}: {err}"))
735}