aboutsummaryrefslogtreecommitdiff
path: root/crates/proc_macro_srv/src/dylib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/proc_macro_srv/src/dylib.rs')
-rw-r--r--crates/proc_macro_srv/src/dylib.rs224
1 files changed, 224 insertions, 0 deletions
diff --git a/crates/proc_macro_srv/src/dylib.rs b/crates/proc_macro_srv/src/dylib.rs
new file mode 100644
index 000000000..f8f705da8
--- /dev/null
+++ b/crates/proc_macro_srv/src/dylib.rs
@@ -0,0 +1,224 @@
1//! Handles dynamic library loading for proc macro
2
3use crate::{proc_macro::bridge, rustc_server::TokenStream};
4use std::fs::File;
5use std::path::{Path, PathBuf};
6
7use goblin::{mach::Mach, Object};
8use libloading::Library;
9use memmap::Mmap;
10use proc_macro_api::ProcMacroKind;
11use std::io;
12
13const NEW_REGISTRAR_SYMBOL: &str = "_rustc_proc_macro_decls_";
14
15fn invalid_data_err(e: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> io::Error {
16 io::Error::new(io::ErrorKind::InvalidData, e)
17}
18
19fn is_derive_registrar_symbol(symbol: &str) -> bool {
20 symbol.contains(NEW_REGISTRAR_SYMBOL)
21}
22
23fn find_registrar_symbol(file: &Path) -> io::Result<Option<String>> {
24 let file = File::open(file)?;
25 let buffer = unsafe { Mmap::map(&file)? };
26 let object = Object::parse(&buffer).map_err(invalid_data_err)?;
27
28 let name = match object {
29 Object::Elf(elf) => {
30 let symbols = elf.dynstrtab.to_vec().map_err(invalid_data_err)?;
31 symbols.into_iter().find(|s| is_derive_registrar_symbol(s)).map(&str::to_owned)
32 }
33 Object::PE(pe) => pe
34 .exports
35 .iter()
36 .flat_map(|s| s.name)
37 .find(|s| is_derive_registrar_symbol(s))
38 .map(&str::to_owned),
39 Object::Mach(Mach::Binary(binary)) => {
40 let exports = binary.exports().map_err(invalid_data_err)?;
41 exports
42 .iter()
43 .map(|s| {
44 // In macos doc:
45 // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dlsym.3.html
46 // Unlike other dyld API's, the symbol name passed to dlsym() must NOT be
47 // prepended with an underscore.
48 if s.name.starts_with('_') {
49 &s.name[1..]
50 } else {
51 &s.name
52 }
53 })
54 .find(|s| is_derive_registrar_symbol(s))
55 .map(&str::to_owned)
56 }
57 _ => return Ok(None),
58 };
59 return Ok(name);
60}
61
62/// Loads dynamic library in platform dependent manner.
63///
64/// For unix, you have to use RTLD_DEEPBIND flag to escape problems described
65/// [here](https://github.com/fedochet/rust-proc-macro-panic-inside-panic-expample)
66/// and [here](https://github.com/rust-lang/rust/issues/60593).
67///
68/// Usage of RTLD_DEEPBIND
69/// [here](https://github.com/fedochet/rust-proc-macro-panic-inside-panic-expample/issues/1)
70///
71/// It seems that on Windows that behaviour is default, so we do nothing in that case.
72#[cfg(windows)]
73fn load_library(file: &Path) -> Result<Library, libloading::Error> {
74 Library::new(file)
75}
76
77#[cfg(unix)]
78fn load_library(file: &Path) -> Result<Library, libloading::Error> {
79 use libloading::os::unix::Library as UnixLibrary;
80 use std::os::raw::c_int;
81
82 const RTLD_NOW: c_int = 0x00002;
83 const RTLD_DEEPBIND: c_int = 0x00008;
84
85 UnixLibrary::open(Some(file), RTLD_NOW | RTLD_DEEPBIND).map(|lib| lib.into())
86}
87
88struct ProcMacroLibraryLibloading {
89 // Hold the dylib to prevent it from unloading
90 _lib: Library,
91 exported_macros: Vec<bridge::client::ProcMacro>,
92}
93
94impl ProcMacroLibraryLibloading {
95 fn open(file: &Path) -> io::Result<Self> {
96 let symbol_name = find_registrar_symbol(file)?.ok_or_else(|| {
97 invalid_data_err(format!("Cannot find registrar symbol in file {}", file.display()))
98 })?;
99
100 let lib = load_library(file).map_err(invalid_data_err)?;
101 let exported_macros = {
102 let macros: libloading::Symbol<&&[bridge::client::ProcMacro]> =
103 unsafe { lib.get(symbol_name.as_bytes()) }.map_err(invalid_data_err)?;
104 macros.to_vec()
105 };
106
107 Ok(ProcMacroLibraryLibloading { _lib: lib, exported_macros })
108 }
109}
110
111pub struct Expander {
112 inner: ProcMacroLibraryLibloading,
113}
114
115impl Expander {
116 pub fn new(lib: &Path) -> io::Result<Expander> {
117 // Some libraries for dynamic loading require canonicalized path even when it is
118 // already absolute
119 let lib = lib.canonicalize()?;
120
121 let lib = ensure_file_with_lock_free_access(&lib)?;
122
123 let library = ProcMacroLibraryLibloading::open(&lib)?;
124
125 Ok(Expander { inner: library })
126 }
127
128 pub fn expand(
129 &self,
130 macro_name: &str,
131 macro_body: &tt::Subtree,
132 attributes: Option<&tt::Subtree>,
133 ) -> Result<tt::Subtree, bridge::PanicMessage> {
134 let parsed_body = TokenStream::with_subtree(macro_body.clone());
135
136 let parsed_attributes = attributes
137 .map_or(crate::rustc_server::TokenStream::new(), |attr| {
138 TokenStream::with_subtree(attr.clone())
139 });
140
141 for proc_macro in &self.inner.exported_macros {
142 match proc_macro {
143 bridge::client::ProcMacro::CustomDerive { trait_name, client, .. }
144 if *trait_name == macro_name =>
145 {
146 let res = client.run(
147 &crate::proc_macro::bridge::server::SameThread,
148 crate::rustc_server::Rustc::default(),
149 parsed_body,
150 );
151 return res.map(|it| it.subtree);
152 }
153 bridge::client::ProcMacro::Bang { name, client } if *name == macro_name => {
154 let res = client.run(
155 &crate::proc_macro::bridge::server::SameThread,
156 crate::rustc_server::Rustc::default(),
157 parsed_body,
158 );
159 return res.map(|it| it.subtree);
160 }
161 bridge::client::ProcMacro::Attr { name, client } if *name == macro_name => {
162 let res = client.run(
163 &crate::proc_macro::bridge::server::SameThread,
164 crate::rustc_server::Rustc::default(),
165 parsed_attributes,
166 parsed_body,
167 );
168 return res.map(|it| it.subtree);
169 }
170 _ => continue,
171 }
172 }
173
174 Err(bridge::PanicMessage::String("Nothing to expand".to_string()))
175 }
176
177 pub fn list_macros(&self) -> Vec<(String, ProcMacroKind)> {
178 self.inner
179 .exported_macros
180 .iter()
181 .map(|proc_macro| match proc_macro {
182 bridge::client::ProcMacro::CustomDerive { trait_name, .. } => {
183 (trait_name.to_string(), ProcMacroKind::CustomDerive)
184 }
185 bridge::client::ProcMacro::Bang { name, .. } => {
186 (name.to_string(), ProcMacroKind::FuncLike)
187 }
188 bridge::client::ProcMacro::Attr { name, .. } => {
189 (name.to_string(), ProcMacroKind::Attr)
190 }
191 })
192 .collect()
193 }
194}
195
196/// Copy the dylib to temp directory to prevent locking in Windows
197#[cfg(windows)]
198fn ensure_file_with_lock_free_access(path: &Path) -> io::Result<PathBuf> {
199 use std::{ffi::OsString, time::SystemTime};
200
201 let mut to = std::env::temp_dir();
202
203 let file_name = path.file_name().ok_or_else(|| {
204 io::Error::new(
205 io::ErrorKind::InvalidInput,
206 format!("File path is invalid: {}", path.display()),
207 )
208 })?;
209
210 // generate a time deps unique number
211 let t = SystemTime::now().duration_since(std::time::UNIX_EPOCH).expect("Time went backwards");
212
213 let mut unique_name = OsString::from(t.as_millis().to_string());
214 unique_name.push(file_name);
215
216 to.push(unique_name);
217 std::fs::copy(path, &to).unwrap();
218 Ok(to)
219}
220
221#[cfg(unix)]
222fn ensure_file_with_lock_free_access(path: &Path) -> io::Result<PathBuf> {
223 Ok(path.to_path_buf())
224}