diff options
Diffstat (limited to 'crates/ra_proc_macro_srv/src/dylib.rs')
-rw-r--r-- | crates/ra_proc_macro_srv/src/dylib.rs | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/crates/ra_proc_macro_srv/src/dylib.rs b/crates/ra_proc_macro_srv/src/dylib.rs new file mode 100644 index 000000000..ec63d587b --- /dev/null +++ b/crates/ra_proc_macro_srv/src/dylib.rs | |||
@@ -0,0 +1,211 @@ | |||
1 | //! Handles dynamic library loading for proc macro | ||
2 | |||
3 | use crate::{proc_macro::bridge, rustc_server::TokenStream}; | ||
4 | use std::path::Path; | ||
5 | |||
6 | use goblin::{mach::Mach, Object}; | ||
7 | use libloading::Library; | ||
8 | use ra_proc_macro::ProcMacroKind; | ||
9 | |||
10 | use std::io::Error as IoError; | ||
11 | use std::io::ErrorKind as IoErrorKind; | ||
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>>) -> IoError { | ||
16 | IoError::new(IoErrorKind::InvalidData, e) | ||
17 | } | ||
18 | |||
19 | fn get_symbols_from_lib(file: &Path) -> Result<Vec<String>, IoError> { | ||
20 | let buffer = std::fs::read(file)?; | ||
21 | let object = Object::parse(&buffer).map_err(invalid_data_err)?; | ||
22 | |||
23 | match object { | ||
24 | Object::Elf(elf) => { | ||
25 | let symbols = elf.dynstrtab.to_vec().map_err(invalid_data_err)?; | ||
26 | let names = symbols.iter().map(|s| s.to_string()).collect(); | ||
27 | Ok(names) | ||
28 | } | ||
29 | Object::PE(pe) => { | ||
30 | let symbol_names = | ||
31 | pe.exports.iter().flat_map(|s| s.name).map(|n| n.to_string()).collect(); | ||
32 | Ok(symbol_names) | ||
33 | } | ||
34 | Object::Mach(mach) => match mach { | ||
35 | Mach::Binary(binary) => { | ||
36 | let exports = binary.exports().map_err(invalid_data_err)?; | ||
37 | let names = exports | ||
38 | .into_iter() | ||
39 | .map(|s| { | ||
40 | // In macos doc: | ||
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 s.name.starts_with("_") { | ||
45 | s.name[1..].to_string() | ||
46 | } else { | ||
47 | s.name | ||
48 | } | ||
49 | }) | ||
50 | .collect(); | ||
51 | Ok(names) | ||
52 | } | ||
53 | Mach::Fat(_) => Ok(vec![]), | ||
54 | }, | ||
55 | Object::Archive(_) | Object::Unknown(_) => Ok(vec![]), | ||
56 | } | ||
57 | } | ||
58 | |||
59 | fn is_derive_registrar_symbol(symbol: &str) -> bool { | ||
60 | symbol.contains(NEW_REGISTRAR_SYMBOL) | ||
61 | } | ||
62 | |||
63 | fn find_registrar_symbol(file: &Path) -> Result<Option<String>, IoError> { | ||
64 | let symbols = get_symbols_from_lib(file)?; | ||
65 | Ok(symbols.into_iter().find(|s| is_derive_registrar_symbol(s))) | ||
66 | } | ||
67 | |||
68 | /// Loads dynamic library in platform dependent manner. | ||
69 | /// | ||
70 | /// For unix, you have to use RTLD_DEEPBIND flag to escape problems described | ||
71 | /// [here](https://github.com/fedochet/rust-proc-macro-panic-inside-panic-expample) | ||
72 | /// and [here](https://github.com/rust-lang/rust/issues/60593). | ||
73 | /// | ||
74 | /// Usage of RTLD_DEEPBIND | ||
75 | /// [here](https://github.com/fedochet/rust-proc-macro-panic-inside-panic-expample/issues/1) | ||
76 | /// | ||
77 | /// It seems that on Windows that behaviour is default, so we do nothing in that case. | ||
78 | #[cfg(windows)] | ||
79 | fn load_library(file: &Path) -> Result<Library, libloading::Error> { | ||
80 | Library::new(file) | ||
81 | } | ||
82 | |||
83 | #[cfg(unix)] | ||
84 | fn load_library(file: &Path) -> Result<Library, libloading::Error> { | ||
85 | use libloading::os::unix::Library as UnixLibrary; | ||
86 | use std::os::raw::c_int; | ||
87 | |||
88 | const RTLD_NOW: c_int = 0x00002; | ||
89 | const RTLD_DEEPBIND: c_int = 0x00008; | ||
90 | |||
91 | UnixLibrary::open(Some(file), RTLD_NOW | RTLD_DEEPBIND).map(|lib| lib.into()) | ||
92 | } | ||
93 | |||
94 | struct ProcMacroLibraryLibloading { | ||
95 | // Hold the dylib to prevent it for unloadeding | ||
96 | _lib: Library, | ||
97 | exported_macros: Vec<bridge::client::ProcMacro>, | ||
98 | } | ||
99 | |||
100 | impl ProcMacroLibraryLibloading { | ||
101 | fn open(file: &Path) -> Result<Self, IoError> { | ||
102 | let symbol_name = find_registrar_symbol(file)? | ||
103 | .ok_or(invalid_data_err(format!("Cannot find registrar symbol in file {:?}", file)))?; | ||
104 | |||
105 | let lib = load_library(file).map_err(invalid_data_err)?; | ||
106 | let exported_macros = { | ||
107 | let macros: libloading::Symbol<&&[bridge::client::ProcMacro]> = | ||
108 | unsafe { lib.get(symbol_name.as_bytes()) }.map_err(invalid_data_err)?; | ||
109 | macros.to_vec() | ||
110 | }; | ||
111 | |||
112 | Ok(ProcMacroLibraryLibloading { _lib: lib, exported_macros }) | ||
113 | } | ||
114 | } | ||
115 | |||
116 | type ProcMacroLibraryImpl = ProcMacroLibraryLibloading; | ||
117 | |||
118 | pub struct Expander { | ||
119 | libs: Vec<ProcMacroLibraryImpl>, | ||
120 | } | ||
121 | |||
122 | impl Expander { | ||
123 | pub fn new<P: AsRef<Path>>(lib: &P) -> Result<Expander, String> { | ||
124 | let mut libs = vec![]; | ||
125 | /* Some libraries for dynamic loading require canonicalized path (even when it is | ||
126 | already absolute | ||
127 | */ | ||
128 | let lib = | ||
129 | lib.as_ref().canonicalize().expect(&format!("Cannot canonicalize {:?}", lib.as_ref())); | ||
130 | |||
131 | let library = ProcMacroLibraryImpl::open(&lib).map_err(|e| e.to_string())?; | ||
132 | libs.push(library); | ||
133 | |||
134 | Ok(Expander { libs }) | ||
135 | } | ||
136 | |||
137 | pub fn expand( | ||
138 | &self, | ||
139 | macro_name: &str, | ||
140 | macro_body: &ra_tt::Subtree, | ||
141 | attributes: Option<&ra_tt::Subtree>, | ||
142 | ) -> Result<ra_tt::Subtree, bridge::PanicMessage> { | ||
143 | let parsed_body = TokenStream::with_subtree(macro_body.clone()); | ||
144 | |||
145 | let parsed_attributes = attributes | ||
146 | .map_or(crate::rustc_server::TokenStream::new(), |attr| { | ||
147 | TokenStream::with_subtree(attr.clone()) | ||
148 | }); | ||
149 | |||
150 | for lib in &self.libs { | ||
151 | for proc_macro in &lib.exported_macros { | ||
152 | match proc_macro { | ||
153 | bridge::client::ProcMacro::CustomDerive { trait_name, client, .. } | ||
154 | if *trait_name == macro_name => | ||
155 | { | ||
156 | let res = client.run( | ||
157 | &crate::proc_macro::bridge::server::SameThread, | ||
158 | crate::rustc_server::Rustc::default(), | ||
159 | parsed_body, | ||
160 | ); | ||
161 | return res.map(|it| it.subtree); | ||
162 | } | ||
163 | bridge::client::ProcMacro::Bang { name, client } if *name == macro_name => { | ||
164 | let res = client.run( | ||
165 | &crate::proc_macro::bridge::server::SameThread, | ||
166 | crate::rustc_server::Rustc::default(), | ||
167 | parsed_body, | ||
168 | ); | ||
169 | return res.map(|it| it.subtree); | ||
170 | } | ||
171 | bridge::client::ProcMacro::Attr { name, client } if *name == macro_name => { | ||
172 | let res = client.run( | ||
173 | &crate::proc_macro::bridge::server::SameThread, | ||
174 | crate::rustc_server::Rustc::default(), | ||
175 | parsed_attributes, | ||
176 | parsed_body, | ||
177 | ); | ||
178 | |||
179 | return res.map(|it| it.subtree); | ||
180 | } | ||
181 | _ => continue, | ||
182 | } | ||
183 | } | ||
184 | } | ||
185 | |||
186 | Err(bridge::PanicMessage::String("Nothing to expand".to_string())) | ||
187 | } | ||
188 | |||
189 | pub fn list_macros(&self) -> Result<Vec<(String, ProcMacroKind)>, bridge::PanicMessage> { | ||
190 | let mut result = vec![]; | ||
191 | |||
192 | for lib in &self.libs { | ||
193 | for proc_macro in &lib.exported_macros { | ||
194 | let res = match proc_macro { | ||
195 | bridge::client::ProcMacro::CustomDerive { trait_name, .. } => { | ||
196 | (trait_name.to_string(), ProcMacroKind::CustomDerive) | ||
197 | } | ||
198 | bridge::client::ProcMacro::Bang { name, .. } => { | ||
199 | (name.to_string(), ProcMacroKind::FuncLike) | ||
200 | } | ||
201 | bridge::client::ProcMacro::Attr { name, .. } => { | ||
202 | (name.to_string(), ProcMacroKind::Attr) | ||
203 | } | ||
204 | }; | ||
205 | result.push(res); | ||
206 | } | ||
207 | } | ||
208 | |||
209 | Ok(result) | ||
210 | } | ||
211 | } | ||