ra_ap_proc_macro_srv/
lib.rs

1//! RA Proc Macro Server
2//!
3//! This library is able to call compiled Rust custom derive dynamic libraries on arbitrary code.
4//! The general idea here is based on <https://github.com/fedochet/rust-proc-macro-expander>.
5//!
6//! But we adapt it to better fit RA needs:
7//!
8//! * We use `tt` for proc-macro `TokenStream` server, it is easier to manipulate and interact with
9//!   RA than `proc-macro2` token stream.
10//! * By **copying** the whole rustc `lib_proc_macro` code, we are able to build this with `stable`
11//!   rustc rather than `unstable`. (Although in general ABI compatibility is still an issue)…
12
13#![cfg(any(feature = "sysroot-abi", rust_analyzer))]
14#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
15#![feature(proc_macro_internals, proc_macro_diagnostic, proc_macro_span)]
16#![allow(unreachable_pub, internal_features, clippy::disallowed_types, clippy::print_stderr)]
17#![deny(deprecated_safe)]
18
19extern crate proc_macro;
20#[cfg(feature = "in-rust-tree")]
21extern crate rustc_driver as _;
22
23#[cfg(not(feature = "in-rust-tree"))]
24extern crate ra_ap_rustc_lexer as rustc_lexer;
25#[cfg(feature = "in-rust-tree")]
26extern crate rustc_lexer;
27
28mod dylib;
29mod proc_macros;
30mod server_impl;
31
32use std::{
33    collections::{hash_map::Entry, HashMap},
34    env,
35    ffi::OsString,
36    fs,
37    path::{Path, PathBuf},
38    sync::{Arc, Mutex, PoisonError},
39    thread,
40};
41
42use paths::{Utf8Path, Utf8PathBuf};
43use span::{Span, TokenId};
44
45use crate::server_impl::TokenStream;
46
47#[derive(Copy, Clone, Eq, PartialEq, Debug)]
48pub enum ProcMacroKind {
49    CustomDerive,
50    Attr,
51    Bang,
52}
53
54pub const RUSTC_VERSION_STRING: &str = env!("RUSTC_VERSION");
55
56pub struct ProcMacroSrv<'env> {
57    expanders: Mutex<HashMap<Utf8PathBuf, Arc<dylib::Expander>>>,
58    env: &'env EnvSnapshot,
59}
60
61impl<'env> ProcMacroSrv<'env> {
62    pub fn new(env: &'env EnvSnapshot) -> Self {
63        Self { expanders: Default::default(), env }
64    }
65}
66
67const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024;
68
69impl ProcMacroSrv<'_> {
70    pub fn expand<S: ProcMacroSrvSpan>(
71        &self,
72        lib: impl AsRef<Utf8Path>,
73        env: Vec<(String, String)>,
74        current_dir: Option<impl AsRef<Path>>,
75        macro_name: String,
76        macro_body: tt::TopSubtree<S>,
77        attribute: Option<tt::TopSubtree<S>>,
78        def_site: S,
79        call_site: S,
80        mixed_site: S,
81    ) -> Result<Vec<tt::TokenTree<S>>, String> {
82        let snapped_env = self.env;
83        let expander =
84            self.expander(lib.as_ref()).map_err(|err| format!("failed to load macro: {err}"))?;
85
86        let prev_env = EnvChange::apply(snapped_env, env, current_dir.as_ref().map(<_>::as_ref));
87
88        // Note, we spawn a new thread here so that thread locals allocation don't accumulate (this
89        // includes the proc-macro symbol interner)
90        let result = thread::scope(|s| {
91            let thread = thread::Builder::new()
92                .stack_size(EXPANDER_STACK_SIZE)
93                .name(macro_name.clone())
94                .spawn_scoped(s, move || {
95                    expander
96                        .expand(
97                            &macro_name,
98                            server_impl::TopSubtree(macro_body.0.into_vec()),
99                            attribute.map(|it| server_impl::TopSubtree(it.0.into_vec())),
100                            def_site,
101                            call_site,
102                            mixed_site,
103                        )
104                        .map(|tt| tt.0)
105                });
106            let res = match thread {
107                Ok(handle) => handle.join(),
108                Err(e) => return Err(e.to_string()),
109            };
110
111            match res {
112                Ok(res) => res,
113                Err(e) => std::panic::resume_unwind(e),
114            }
115        });
116        prev_env.rollback();
117
118        result
119    }
120
121    pub fn list_macros(
122        &self,
123        dylib_path: &Utf8Path,
124    ) -> Result<Vec<(String, ProcMacroKind)>, String> {
125        let expander = self.expander(dylib_path)?;
126        Ok(expander.list_macros())
127    }
128
129    fn expander(&self, path: &Utf8Path) -> Result<Arc<dylib::Expander>, String> {
130        let expander = || {
131            let expander = dylib::Expander::new(path)
132                .map_err(|err| format!("Cannot create expander for {path}: {err}",));
133            expander.map(Arc::new)
134        };
135
136        Ok(
137            match self
138                .expanders
139                .lock()
140                .unwrap_or_else(PoisonError::into_inner)
141                .entry(path.to_path_buf())
142            {
143                Entry::Vacant(v) => v.insert(expander()?).clone(),
144                Entry::Occupied(mut e) => {
145                    let time = fs::metadata(path).and_then(|it| it.modified()).ok();
146                    if Some(e.get().modified_time()) != time {
147                        e.insert(expander()?);
148                    }
149                    e.get().clone()
150                }
151            },
152        )
153    }
154}
155
156pub trait ProcMacroSrvSpan: Copy + Send {
157    type Server: proc_macro::bridge::server::Server<TokenStream = TokenStream<Self>>;
158    fn make_server(call_site: Self, def_site: Self, mixed_site: Self) -> Self::Server;
159}
160
161impl ProcMacroSrvSpan for TokenId {
162    type Server = server_impl::token_id::TokenIdServer;
163
164    fn make_server(call_site: Self, def_site: Self, mixed_site: Self) -> Self::Server {
165        Self::Server { call_site, def_site, mixed_site }
166    }
167}
168impl ProcMacroSrvSpan for Span {
169    type Server = server_impl::rust_analyzer_span::RaSpanServer;
170    fn make_server(call_site: Self, def_site: Self, mixed_site: Self) -> Self::Server {
171        Self::Server {
172            call_site,
173            def_site,
174            mixed_site,
175            tracked_env_vars: Default::default(),
176            tracked_paths: Default::default(),
177        }
178    }
179}
180pub struct PanicMessage {
181    message: Option<String>,
182}
183
184impl PanicMessage {
185    pub fn into_string(self) -> Option<String> {
186        self.message
187    }
188}
189
190pub struct EnvSnapshot {
191    vars: HashMap<OsString, OsString>,
192}
193
194impl Default for EnvSnapshot {
195    fn default() -> EnvSnapshot {
196        EnvSnapshot { vars: env::vars_os().collect() }
197    }
198}
199
200static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
201
202struct EnvChange<'snap> {
203    changed_vars: Vec<String>,
204    prev_working_dir: Option<PathBuf>,
205    snap: &'snap EnvSnapshot,
206    _guard: std::sync::MutexGuard<'snap, ()>,
207}
208
209impl<'snap> EnvChange<'snap> {
210    fn apply(
211        snap: &'snap EnvSnapshot,
212        new_vars: Vec<(String, String)>,
213        current_dir: Option<&Path>,
214    ) -> EnvChange<'snap> {
215        let guard = ENV_LOCK.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
216        let prev_working_dir = match current_dir {
217            Some(dir) => {
218                let prev_working_dir = std::env::current_dir().ok();
219                if let Err(err) = std::env::set_current_dir(dir) {
220                    eprintln!(
221                        "Failed to set the current working dir to {}. Error: {err:?}",
222                        dir.display()
223                    )
224                }
225                prev_working_dir
226            }
227            None => None,
228        };
229        EnvChange {
230            snap,
231            changed_vars: new_vars
232                .into_iter()
233                .map(|(k, v)| {
234                    // SAFETY: We have acquired the environment lock
235                    unsafe { env::set_var(&k, v) };
236                    k
237                })
238                .collect(),
239            prev_working_dir,
240            _guard: guard,
241        }
242    }
243
244    fn rollback(self) {}
245}
246
247impl Drop for EnvChange<'_> {
248    fn drop(&mut self) {
249        for name in self.changed_vars.drain(..) {
250            // SAFETY: We have acquired the environment lock
251            unsafe {
252                match self.snap.vars.get::<std::ffi::OsStr>(name.as_ref()) {
253                    Some(prev_val) => env::set_var(name, prev_val),
254                    None => env::remove_var(name),
255                }
256            }
257        }
258
259        if let Some(dir) = &self.prev_working_dir {
260            if let Err(err) = std::env::set_current_dir(dir) {
261                eprintln!(
262                    "Failed to set the current working dir to {}. Error: {:?}",
263                    dir.display(),
264                    err
265                )
266            }
267        }
268    }
269}
270
271#[cfg(test)]
272mod tests;
273
274#[cfg(test)]
275pub fn proc_macro_test_dylib_path() -> paths::Utf8PathBuf {
276    proc_macro_test::PROC_MACRO_TEST_LOCATION.into()
277}