]> git.lizzy.rs Git - rust.git/blob - crates/ra_proc_macro_srv/src/dylib.rs
ec63d587bb294e23ed1f795e86a9bcd1f2ca19ec
[rust.git] / crates / ra_proc_macro_srv / src / dylib.rs
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 }