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        return Err(unsafe { *Box::from_raw(err as *mut io::Error) });
136    }
137
138    unsafe extern "C" fn callback(state: *mut c_void, symbol_name: *const c_char) -> *mut c_void {
139        let f = unsafe { &mut *(state as *mut &mut dyn FnMut(&[u8]) -> io::Result<()>) };
140        match f(unsafe { CStr::from_ptr(symbol_name) }.to_bytes()) {
141            Ok(()) => std::ptr::null_mut(),
142            Err(err) => Box::into_raw(Box::new(err)) as *mut c_void,
143        }
144    }
145
146    unsafe extern "C" fn error_callback(error: *const c_char) -> *mut c_void {
147        let error = unsafe { CStr::from_ptr(error) };
148        Box::into_raw(Box::new(io::Error::new(
149            io::ErrorKind::Other,
150            format!("LLVM error: {}", error.to_string_lossy()),
151        ))) as *mut c_void
152    }
153}
154
155fn llvm_is_64_bit_object_file(buf: &[u8]) -> bool {
156    unsafe { llvm::LLVMRustIs64BitSymbolicFile(buf.as_ptr(), buf.len()) }
157}
158
159fn llvm_is_ec_object_file(buf: &[u8]) -> bool {
160    unsafe { llvm::LLVMRustIsECObject(buf.as_ptr(), buf.len()) }
161}
162
163impl<'a> LlvmArchiveBuilder<'a> {
164    fn build_with_llvm(&mut self, output: &Path) -> io::Result<bool> {
165        let kind = &*self.sess.target.archive_format;
166        let kind = kind
167            .parse::<ArchiveKind>()
168            .map_err(|_| kind)
169            .unwrap_or_else(|kind| self.sess.dcx().emit_fatal(UnknownArchiveKind { kind }));
170
171        let mut additions = mem::take(&mut self.additions);
172        // Values in the `members` list below will contain pointers to the strings allocated here.
173        // So they need to get dropped after all elements of `members` get freed.
174        let mut strings = Vec::new();
175        let mut members = Vec::new();
176
177        let dst = CString::new(output.to_str().unwrap())?;
178
179        unsafe {
180            for addition in &mut additions {
181                match addition {
182                    Addition::File { path, name_in_archive } => {
183                        let path = CString::new(path.to_str().unwrap())?;
184                        let name = CString::new(name_in_archive.as_bytes())?;
185                        members.push(llvm::LLVMRustArchiveMemberNew(
186                            path.as_ptr(),
187                            name.as_ptr(),
188                            None,
189                        ));
190                        strings.push(path);
191                        strings.push(name);
192                    }
193                    Addition::Archive { archive, skip, .. } => {
194                        for child in archive.iter() {
195                            let child = child.map_err(string_to_io_error)?;
196                            if !is_relevant_child(&child) {
197                                continue;
198                            }
199                            let child_name = child.name().unwrap();
200                            if skip(child_name) {
201                                continue;
202                            }
203
204                            // It appears that LLVM's archive writer is a little
205                            // buggy if the name we pass down isn't just the
206                            // filename component, so chop that off here and
207                            // pass it in.
208                            //
209                            // See LLVM bug 25877 for more info.
210                            let child_name =
211                                Path::new(child_name).file_name().unwrap().to_str().unwrap();
212                            let name = CString::new(child_name)?;
213                            let m = llvm::LLVMRustArchiveMemberNew(
214                                ptr::null(),
215                                name.as_ptr(),
216                                Some(child.raw),
217                            );
218                            members.push(m);
219                            strings.push(name);
220                        }
221                    }
222                }
223            }
224
225            let r = llvm::LLVMRustWriteArchive(
226                dst.as_ptr(),
227                members.len() as libc::size_t,
228                members.as_ptr() as *const &_,
229                true,
230                kind,
231                self.sess.target.arch == "arm64ec",
232            );
233            let ret = if r.into_result().is_err() {
234                let msg = last_error().unwrap_or_else(|| "failed to write archive".into());
235                Err(io::Error::new(io::ErrorKind::Other, msg))
236            } else {
237                Ok(!members.is_empty())
238            };
239            for member in members {
240                llvm::LLVMRustArchiveMemberFree(member);
241            }
242            ret
243        }
244    }
245}
246
247fn string_to_io_error(s: String) -> io::Error {
248    io::Error::new(io::ErrorKind::Other, format!("bad archive: {s}"))
249}