rustc_codegen_llvm/back/
archive.rs

1//! A helper class for dealing with static archives
2
3use std::ffi::{CStr, CString, c_char, c_void};
4use std::path::{Path, PathBuf};
5use std::{io, mem, ptr, str};
6
7use rustc_codegen_ssa::back::archive::{
8    ArArchiveBuilder, ArchiveBuildFailure, ArchiveBuilder, ArchiveBuilderBuilder,
9    DEFAULT_OBJECT_READER, ObjectReader, UnknownArchiveKind, try_extract_macho_fat_archive,
10};
11use rustc_session::Session;
12
13use crate::llvm::archive_ro::{ArchiveRO, Child};
14use crate::llvm::{self, ArchiveKind, last_error};
15
16/// Helper for adding many files to an archive.
17#[must_use = "must call build() to finish building the archive"]
18pub(crate) struct LlvmArchiveBuilder<'a> {
19    sess: &'a Session,
20    additions: Vec<Addition>,
21}
22
23enum Addition {
24    File { path: PathBuf, name_in_archive: String },
25    Archive { path: PathBuf, archive: ArchiveRO, skip: Box<dyn FnMut(&str) -> bool> },
26}
27
28impl Addition {
29    fn path(&self) -> &Path {
30        match self {
31            Addition::File { path, .. } | Addition::Archive { path, .. } => path,
32        }
33    }
34}
35
36fn is_relevant_child(c: &Child<'_>) -> bool {
37    match c.name() {
38        Some(name) => !name.contains("SYMDEF"),
39        None => false,
40    }
41}
42
43impl<'a> ArchiveBuilder for LlvmArchiveBuilder<'a> {
44    fn add_archive(
45        &mut self,
46        archive: &Path,
47        skip: Box<dyn FnMut(&str) -> bool + 'static>,
48    ) -> io::Result<()> {
49        let mut archive = archive.to_path_buf();
50        if self.sess.target.llvm_target.contains("-apple-macosx") {
51            if let Some(new_archive) = try_extract_macho_fat_archive(self.sess, &archive)? {
52                archive = new_archive
53            }
54        }
55        let archive_ro = match ArchiveRO::open(&archive) {
56            Ok(ar) => ar,
57            Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
58        };
59        if self.additions.iter().any(|ar| ar.path() == archive) {
60            return Ok(());
61        }
62        self.additions.push(Addition::Archive {
63            path: archive,
64            archive: archive_ro,
65            skip: Box::new(skip),
66        });
67        Ok(())
68    }
69
70    /// Adds an arbitrary file to this archive
71    fn add_file(&mut self, file: &Path) {
72        let name = file.file_name().unwrap().to_str().unwrap();
73        self.additions
74            .push(Addition::File { path: file.to_path_buf(), name_in_archive: name.to_owned() });
75    }
76
77    /// Combine the provided files, rlibs, and native libraries into a single
78    /// `Archive`.
79    fn build(mut self: Box<Self>, output: &Path) -> bool {
80        match self.build_with_llvm(output) {
81            Ok(any_members) => any_members,
82            Err(error) => {
83                self.sess.dcx().emit_fatal(ArchiveBuildFailure { path: output.to_owned(), error })
84            }
85        }
86    }
87}
88
89pub(crate) struct LlvmArchiveBuilderBuilder;
90
91impl ArchiveBuilderBuilder for LlvmArchiveBuilderBuilder {
92    fn new_archive_builder<'a>(&self, sess: &'a Session) -> Box<dyn ArchiveBuilder + 'a> {
93        // Keeping LlvmArchiveBuilder around in case of a regression caused by using
94        // ArArchiveBuilder.
95        // FIXME(#128955) remove a couple of months after #128936 gets merged in case
96        // no regression is found.
97        if false {
98            Box::new(LlvmArchiveBuilder { sess, additions: Vec::new() })
99        } else {
100            Box::new(ArArchiveBuilder::new(sess, &LLVM_OBJECT_READER))
101        }
102    }
103}
104
105// The object crate doesn't know how to get symbols for LLVM bitcode and COFF bigobj files.
106// As such we need to use LLVM for them.
107
108static LLVM_OBJECT_READER: ObjectReader = ObjectReader {
109    get_symbols: get_llvm_object_symbols,
110    is_64_bit_object_file: llvm_is_64_bit_object_file,
111    is_ec_object_file: llvm_is_ec_object_file,
112    get_xcoff_member_alignment: DEFAULT_OBJECT_READER.get_xcoff_member_alignment,
113};
114
115#[deny(unsafe_op_in_unsafe_fn)]
116fn get_llvm_object_symbols(
117    buf: &[u8],
118    f: &mut dyn FnMut(&[u8]) -> io::Result<()>,
119) -> io::Result<bool> {
120    let mut state = Box::new(f);
121
122    let err = unsafe {
123        llvm::LLVMRustGetSymbols(
124            buf.as_ptr(),
125            buf.len(),
126            (&raw mut *state) as *mut c_void,
127            callback,
128            error_callback,
129        )
130    };
131
132    if err.is_null() {
133        return Ok(true);
134    } else {
135        let error = unsafe { *Box::from_raw(err as *mut io::Error) };
136        // These are the magic constants for LLVM bitcode files:
137        // https://github.com/llvm/llvm-project/blob/7eadc1960d199676f04add402bb0aa6f65b7b234/llvm/lib/BinaryFormat/Magic.cpp#L90-L97
138        if buf.starts_with(&[0xDE, 0xCE, 0x17, 0x0B]) || buf.starts_with(&[b'B', b'C', 0xC0, 0xDE])
139        {
140            // For LLVM bitcode, failure to read the symbols is not fatal. The bitcode may have been
141            // produced by a newer LLVM version that the one linked to rustc. This is fine provided
142            // that the linker does use said newer LLVM version. We skip writing the symbols for the
143            // bitcode to the symbol table of the archive. Traditional linkers don't like this, but
144            // newer linkers like lld, mold and wild ignore the symbol table anyway, so if they link
145            // against a new enough LLVM it will work out in the end.
146            // LLVM's archive writer also has this same behavior of only warning about invalid
147            // bitcode since https://github.com/llvm/llvm-project/pull/96848
148
149            // We don't have access to the DiagCtxt here to produce a nice warning in the correct format.
150            eprintln!("warning: Failed to read symbol table from LLVM bitcode: {}", error);
151            return Ok(true);
152        } else {
153            return Err(error);
154        }
155    }
156
157    unsafe extern "C" fn callback(state: *mut c_void, symbol_name: *const c_char) -> *mut c_void {
158        let f = unsafe { &mut *(state as *mut &mut dyn FnMut(&[u8]) -> io::Result<()>) };
159        match f(unsafe { CStr::from_ptr(symbol_name) }.to_bytes()) {
160            Ok(()) => std::ptr::null_mut(),
161            Err(err) => Box::into_raw(Box::new(err) as Box<io::Error>) as *mut c_void,
162        }
163    }
164
165    unsafe extern "C" fn error_callback(error: *const c_char) -> *mut c_void {
166        let error = unsafe { CStr::from_ptr(error) };
167        Box::into_raw(Box::new(io::Error::new(
168            io::ErrorKind::Other,
169            format!("LLVM error: {}", error.to_string_lossy()),
170        )) as Box<io::Error>) as *mut c_void
171    }
172}
173
174fn llvm_is_64_bit_object_file(buf: &[u8]) -> bool {
175    unsafe { llvm::LLVMRustIs64BitSymbolicFile(buf.as_ptr(), buf.len()) }
176}
177
178fn llvm_is_ec_object_file(buf: &[u8]) -> bool {
179    unsafe { llvm::LLVMRustIsECObject(buf.as_ptr(), buf.len()) }
180}
181
182impl<'a> LlvmArchiveBuilder<'a> {
183    fn build_with_llvm(&mut self, output: &Path) -> io::Result<bool> {
184        let kind = &*self.sess.target.archive_format;
185        let kind = kind
186            .parse::<ArchiveKind>()
187            .map_err(|_| kind)
188            .unwrap_or_else(|kind| self.sess.dcx().emit_fatal(UnknownArchiveKind { kind }));
189
190        let mut additions = mem::take(&mut self.additions);
191        // Values in the `members` list below will contain pointers to the strings allocated here.
192        // So they need to get dropped after all elements of `members` get freed.
193        let mut strings = Vec::new();
194        let mut members = Vec::new();
195
196        let dst = CString::new(output.to_str().unwrap())?;
197
198        unsafe {
199            for addition in &mut additions {
200                match addition {
201                    Addition::File { path, name_in_archive } => {
202                        let path = CString::new(path.to_str().unwrap())?;
203                        let name = CString::new(name_in_archive.as_bytes())?;
204                        members.push(llvm::LLVMRustArchiveMemberNew(
205                            path.as_ptr(),
206                            name.as_ptr(),
207                            None,
208                        ));
209                        strings.push(path);
210                        strings.push(name);
211                    }
212                    Addition::Archive { archive, skip, .. } => {
213                        for child in archive.iter() {
214                            let child = child.map_err(string_to_io_error)?;
215                            if !is_relevant_child(&child) {
216                                continue;
217                            }
218                            let child_name = child.name().unwrap();
219                            if skip(child_name) {
220                                continue;
221                            }
222
223                            // It appears that LLVM's archive writer is a little
224                            // buggy if the name we pass down isn't just the
225                            // filename component, so chop that off here and
226                            // pass it in.
227                            //
228                            // See LLVM bug 25877 for more info.
229                            let child_name =
230                                Path::new(child_name).file_name().unwrap().to_str().unwrap();
231                            let name = CString::new(child_name)?;
232                            let m = llvm::LLVMRustArchiveMemberNew(
233                                ptr::null(),
234                                name.as_ptr(),
235                                Some(child.raw),
236                            );
237                            members.push(m);
238                            strings.push(name);
239                        }
240                    }
241                }
242            }
243
244            let r = llvm::LLVMRustWriteArchive(
245                dst.as_ptr(),
246                members.len() as libc::size_t,
247                members.as_ptr() as *const &_,
248                true,
249                kind,
250                self.sess.target.arch == "arm64ec",
251            );
252            let ret = if r.into_result().is_err() {
253                let msg = last_error().unwrap_or_else(|| "failed to write archive".into());
254                Err(io::Error::new(io::ErrorKind::Other, msg))
255            } else {
256                Ok(!members.is_empty())
257            };
258            for member in members {
259                llvm::LLVMRustArchiveMemberFree(member);
260            }
261            ret
262        }
263    }
264}
265
266fn string_to_io_error(s: String) -> io::Error {
267    io::Error::new(io::ErrorKind::Other, format!("bad archive: {s}"))
268}