diff options
Diffstat (limited to 'crates/proc_macro_srv/src/dylib.rs')
-rw-r--r-- | crates/proc_macro_srv/src/dylib.rs | 224 |
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 | |||
3 | use crate::{proc_macro::bridge, rustc_server::TokenStream}; | ||
4 | use std::fs::File; | ||
5 | use std::path::{Path, PathBuf}; | ||
6 | |||
7 | use goblin::{mach::Mach, Object}; | ||
8 | use libloading::Library; | ||
9 | use memmap::Mmap; | ||
10 | use proc_macro_api::ProcMacroKind; | ||
11 | use std::io; | ||
12 | |||
13 | const NEW_REGISTRAR_SYMBOL: &str = "_rustc_proc_macro_decls_"; | ||
14 | |||
15 | fn 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 | |||
19 | fn is_derive_registrar_symbol(symbol: &str) -> bool { | ||
20 | symbol.contains(NEW_REGISTRAR_SYMBOL) | ||
21 | } | ||
22 | |||
23 | fn 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)] | ||
73 | fn load_library(file: &Path) -> Result<Library, libloading::Error> { | ||
74 | Library::new(file) | ||
75 | } | ||
76 | |||
77 | #[cfg(unix)] | ||
78 | fn 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 | |||
88 | struct ProcMacroLibraryLibloading { | ||
89 | // Hold the dylib to prevent it from unloading | ||
90 | _lib: Library, | ||
91 | exported_macros: Vec<bridge::client::ProcMacro>, | ||
92 | } | ||
93 | |||
94 | impl 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 | |||
111 | pub struct Expander { | ||
112 | inner: ProcMacroLibraryLibloading, | ||
113 | } | ||
114 | |||
115 | impl 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)] | ||
198 | fn 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)] | ||
222 | fn ensure_file_with_lock_free_access(path: &Path) -> io::Result<PathBuf> { | ||
223 | Ok(path.to_path_buf()) | ||
224 | } | ||