Skip to content

Commit 0ecdba2

Browse files
Merge #3920
3920: Implement expand_task and list_macros in proc_macro_srv r=matklad a=edwin0cheng This PR finish up the remain `proc_macro_srv` implementation : 1. Added dylib loading code for proc-macro crate dylib. Note that we have to add some special flags for unix loading because of a bug in old version of glibc, see fedochet/rust-proc-macro-panic-inside-panic-expample#1 and rust-lang/rust#60593 for details. 2. Added tests for proc-macro expansion: We use a trick here by adding `serde_derive` to dev-dependencies and calling `cargo-metadata` for searching its dylib path, and expand it in our tests. [EDIT] Note that this PR **DO NOT** implement the final glue code with rust-analzyer and proc-macro-srv yet. Co-authored-by: Edwin Cheng <edwin0cheng@gmail.com>
2 parents 54bdb9c + 31d163a commit 0ecdba2

File tree

8 files changed

+600
-5
lines changed

8 files changed

+600
-5
lines changed

Cargo.lock

+49
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

+4-1
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ 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"
17+
test_utils = { path = "../test_utils" }
1518

1619
[dev-dependencies]
1720
cargo_metadata = "0.9.1"
1821
difference = "2.0.0"
1922
# used as proc macro test target
20-
serde_derive = "=1.0.104"
23+
serde_derive = "=1.0.104"

crates/ra_proc_macro_srv/src/dylib.rs

+211
Original file line numberDiff line numberDiff line change
@@ -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+
}

crates/ra_proc_macro_srv/src/lib.rs

+32-4
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,41 @@ 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

23-
pub fn expand_task(_task: &ExpansionTask) -> Result<ExpansionResult, String> {
24-
unimplemented!()
25+
pub fn expand_task(task: &ExpansionTask) -> Result<ExpansionResult, String> {
26+
let expander = dylib::Expander::new(&task.lib)
27+
.expect(&format!("Cannot expand with provided libraries: ${:?}", &task.lib));
28+
29+
match expander.expand(&task.macro_name, &task.macro_body, task.attributes.as_ref()) {
30+
Ok(expansion) => Ok(ExpansionResult { expansion }),
31+
Err(msg) => {
32+
let reason = format!(
33+
"Cannot perform expansion for {}: error {:?}!",
34+
&task.macro_name,
35+
msg.as_str()
36+
);
37+
Err(reason)
38+
}
39+
}
2540
}
2641

27-
pub fn list_macros(_task: &ListMacrosTask) -> Result<ListMacrosResult, String> {
28-
unimplemented!()
42+
pub fn list_macros(task: &ListMacrosTask) -> Result<ListMacrosResult, String> {
43+
let expander = dylib::Expander::new(&task.lib)
44+
.expect(&format!("Cannot expand with provided libraries: ${:?}", &task.lib));
45+
46+
match expander.list_macros() {
47+
Ok(macros) => Ok(ListMacrosResult { macros }),
48+
Err(msg) => {
49+
let reason =
50+
format!("Cannot perform expansion for {:?}: error {:?}!", &task.lib, msg.as_str());
51+
Err(reason)
52+
}
53+
}
2954
}
55+
56+
#[cfg(test)]
57+
mod tests;

crates/ra_proc_macro_srv/src/rustc_server.rs

+4
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)