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