Skip to content

Commit f7f121d

Browse files
committed
Add dylib loading code
1 parent dde9488 commit f7f121d

File tree

5 files changed

+277
-1
lines changed

5 files changed

+277
-1
lines changed

Cargo.lock

Lines changed: 48 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ra_proc_macro_srv/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ doctest = false
1212
ra_tt = { path = "../ra_tt" }
1313
ra_mbe = { path = "../ra_mbe" }
1414
ra_proc_macro = { path = "../ra_proc_macro" }
15+
goblin = "0.2.1"
16+
libloading = "0.6.0"
1517

1618
[dev-dependencies]
1719
cargo_metadata = "0.9.1"
1820
difference = "2.0.0"
1921
# used as proc macro test target
20-
serde_derive = "=1.0.104"
22+
serde_derive = "=1.0.104"

crates/ra_proc_macro_srv/src/dylib.rs

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
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::io::Read;
6+
use std::path::Path;
7+
8+
use goblin::{mach::Mach, Object};
9+
use libloading::Library;
10+
use ra_proc_macro::ProcMacroKind;
11+
12+
static NEW_REGISTRAR_SYMBOL: &str = "__rustc_proc_macro_decls_";
13+
static _OLD_REGISTRAR_SYMBOL: &str = "__rustc_derive_registrar_";
14+
15+
fn 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+
23+
fn 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+
56+
fn is_derive_registrar_symbol(symbol: &str) -> bool {
57+
symbol.contains(NEW_REGISTRAR_SYMBOL)
58+
}
59+
60+
fn 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)]
77+
fn load_library(file: &Path) -> Result<Library, libloading::Error> {
78+
Library::new(file)
79+
}
80+
81+
#[cfg(unix)]
82+
fn 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+
92+
struct 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+
99+
impl 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+
117+
type ProcMacroLibraryImpl = ProcMacroLibraryLibloading;
118+
119+
pub struct Expander {
120+
libs: Vec<ProcMacroLibraryImpl>,
121+
}
122+
123+
impl 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+
}

crates/ra_proc_macro_srv/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ mod proc_macro;
1717
#[doc(hidden)]
1818
mod rustc_server;
1919

20+
mod dylib;
21+
2022
use proc_macro::bridge::client::TokenStream;
2123
use ra_proc_macro::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask};
2224

crates/ra_proc_macro_srv/src/rustc_server.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ impl TokenStream {
3434
TokenStream { subtree: Default::default() }
3535
}
3636

37+
pub fn with_subtree(subtree: tt::Subtree) -> Self {
38+
TokenStream { subtree }
39+
}
40+
3741
pub fn is_empty(&self) -> bool {
3842
self.subtree.token_trees.is_empty()
3943
}

0 commit comments

Comments
 (0)