]> git.lizzy.rs Git - rust.git/blob - crates/proc_macro_srv/src/dylib.rs
Introduce proc_macro_srv::abis, impl 1.47 and 1.55
[rust.git] / crates / proc_macro_srv / src / dylib.rs
1 //! Handles dynamic library loading for proc macro
2
3 use std::{
4     fmt,
5     fs::File,
6     io,
7     path::{Path, PathBuf},
8 };
9
10 use libloading::Library;
11 use memmap2::Mmap;
12 use object::Object;
13 use proc_macro_api::{read_dylib_info, ProcMacroKind};
14
15 use super::abis::Abi;
16
17 const NEW_REGISTRAR_SYMBOL: &str = "_rustc_proc_macro_decls_";
18
19 fn invalid_data_err(e: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> io::Error {
20     io::Error::new(io::ErrorKind::InvalidData, e)
21 }
22
23 fn is_derive_registrar_symbol(symbol: &str) -> bool {
24     symbol.contains(NEW_REGISTRAR_SYMBOL)
25 }
26
27 fn find_registrar_symbol(file: &Path) -> io::Result<Option<String>> {
28     let file = File::open(file)?;
29     let buffer = unsafe { Mmap::map(&file)? };
30
31     Ok(object::File::parse(&*buffer)
32         .map_err(invalid_data_err)?
33         .exports()
34         .map_err(invalid_data_err)?
35         .into_iter()
36         .map(|export| export.name())
37         .filter_map(|sym| String::from_utf8(sym.into()).ok())
38         .find(|sym| is_derive_registrar_symbol(sym))
39         .map(|sym| {
40             // From MacOS docs:
41             // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dlsym.3.html
42             // Unlike other dyld API's, the symbol name passed to dlsym() must NOT be
43             // prepended with an underscore.
44             if cfg!(target_os = "macos") && sym.starts_with('_') {
45                 sym[1..].to_owned()
46             } else {
47                 sym
48             }
49         }))
50 }
51
52 /// Loads dynamic library in platform dependent manner.
53 ///
54 /// For unix, you have to use RTLD_DEEPBIND flag to escape problems described
55 /// [here](https://github.com/fedochet/rust-proc-macro-panic-inside-panic-expample)
56 /// and [here](https://github.com/rust-lang/rust/issues/60593).
57 ///
58 /// Usage of RTLD_DEEPBIND
59 /// [here](https://github.com/fedochet/rust-proc-macro-panic-inside-panic-expample/issues/1)
60 ///
61 /// It seems that on Windows that behaviour is default, so we do nothing in that case.
62 #[cfg(windows)]
63 fn load_library(file: &Path) -> Result<Library, libloading::Error> {
64     unsafe { Library::new(file) }
65 }
66
67 #[cfg(unix)]
68 fn load_library(file: &Path) -> Result<Library, libloading::Error> {
69     use libloading::os::unix::Library as UnixLibrary;
70     use std::os::raw::c_int;
71
72     const RTLD_NOW: c_int = 0x00002;
73     const RTLD_DEEPBIND: c_int = 0x00008;
74
75     unsafe { UnixLibrary::open(Some(file), RTLD_NOW | RTLD_DEEPBIND).map(|lib| lib.into()) }
76 }
77
78 #[derive(Debug)]
79 pub enum LoadProcMacroDylibError {
80     Io(io::Error),
81     LibLoading(libloading::Error),
82     UnsupportedABI,
83 }
84
85 impl fmt::Display for LoadProcMacroDylibError {
86     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87         match self {
88             Self::Io(e) => e.fmt(f),
89             Self::UnsupportedABI => write!(f, "unsupported ABI version"),
90             Self::LibLoading(e) => e.fmt(f),
91         }
92     }
93 }
94
95 impl From<io::Error> for LoadProcMacroDylibError {
96     fn from(e: io::Error) -> Self {
97         LoadProcMacroDylibError::Io(e)
98     }
99 }
100
101 impl From<libloading::Error> for LoadProcMacroDylibError {
102     fn from(e: libloading::Error) -> Self {
103         LoadProcMacroDylibError::LibLoading(e)
104     }
105 }
106
107 struct ProcMacroLibraryLibloading {
108     // Hold on to the library so it doesn't unload
109     _lib: Library,
110     abi: Abi,
111 }
112
113 impl ProcMacroLibraryLibloading {
114     fn open(file: &Path) -> Result<Self, LoadProcMacroDylibError> {
115         let symbol_name = find_registrar_symbol(file)?.ok_or_else(|| {
116             invalid_data_err(format!("Cannot find registrar symbol in file {}", file.display()))
117         })?;
118
119         let version_info = read_dylib_info(file)?;
120
121         let lib = load_library(file).map_err(invalid_data_err)?;
122         let abi = Abi::from_lib(&lib, symbol_name, version_info)?;
123         Ok(ProcMacroLibraryLibloading { _lib: lib, abi })
124     }
125 }
126
127 pub struct Expander {
128     inner: ProcMacroLibraryLibloading,
129 }
130
131 impl Expander {
132     pub fn new(lib: &Path) -> Result<Expander, LoadProcMacroDylibError> {
133         // Some libraries for dynamic loading require canonicalized path even when it is
134         // already absolute
135         let lib = lib.canonicalize()?;
136
137         let lib = ensure_file_with_lock_free_access(&lib)?;
138
139         let library = ProcMacroLibraryLibloading::open(&lib)?;
140
141         Ok(Expander { inner: library })
142     }
143
144     pub fn expand(
145         &self,
146         macro_name: &str,
147         macro_body: &tt::Subtree,
148         attributes: Option<&tt::Subtree>,
149     ) -> Result<tt::Subtree, String> {
150         let result = self.inner.abi.expand(macro_name, macro_body, attributes);
151         result.map_err(|e| e.as_str().unwrap_or_else(|| "<unknown error>".to_string()))
152     }
153
154     pub fn list_macros(&self) -> Vec<(String, ProcMacroKind)> {
155         self.inner.abi.list_macros()
156     }
157 }
158
159 /// Copy the dylib to temp directory to prevent locking in Windows
160 #[cfg(windows)]
161 fn ensure_file_with_lock_free_access(path: &Path) -> io::Result<PathBuf> {
162     use std::collections::hash_map::RandomState;
163     use std::ffi::OsString;
164     use std::hash::{BuildHasher, Hasher};
165
166     let mut to = std::env::temp_dir();
167
168     let file_name = path.file_name().ok_or_else(|| {
169         io::Error::new(
170             io::ErrorKind::InvalidInput,
171             format!("File path is invalid: {}", path.display()),
172         )
173     })?;
174
175     // Generate a unique number by abusing `HashMap`'s hasher.
176     // Maybe this will also "inspire" a libs team member to finally put `rand` in libstd.
177     let t = RandomState::new().build_hasher().finish();
178
179     let mut unique_name = OsString::from(t.to_string());
180     unique_name.push(file_name);
181
182     to.push(unique_name);
183     std::fs::copy(path, &to).unwrap();
184     Ok(to)
185 }
186
187 #[cfg(unix)]
188 fn ensure_file_with_lock_free_access(path: &Path) -> io::Result<PathBuf> {
189     Ok(path.to_path_buf())
190 }