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(feature = "sysroot-abi")]
14#![cfg_attr(feature = "in-rust-tree", feature(rustc_private))]
15#![feature(proc_macro_internals, proc_macro_diagnostic, proc_macro_span)]
16#![allow(
17    unreachable_pub,
18    internal_features,
19    clippy::disallowed_types,
20    clippy::print_stderr,
21    unused_crate_dependencies
22)]
23#![deny(deprecated_safe, clippy::undocumented_unsafe_blocks)]
24
25extern crate proc_macro;
26#[cfg(feature = "in-rust-tree")]
27extern crate rustc_driver as _;
28
29#[cfg(not(feature = "in-rust-tree"))]
30extern crate ra_ap_rustc_lexer as rustc_lexer;
31#[cfg(feature = "in-rust-tree")]
32extern crate rustc_lexer;
33
34mod bridge;
35mod dylib;
36mod server_impl;
37mod token_stream;
38
39use std::{
40    collections::{HashMap, hash_map::Entry},
41    env,
42    ffi::OsString,
43    fs,
44    path::{Path, PathBuf},
45    sync::{Arc, Mutex, PoisonError},
46    thread,
47};
48
49use paths::{Utf8Path, Utf8PathBuf};
50use span::Span;
51use temp_dir::TempDir;
52
53pub use crate::server_impl::token_id::SpanId;
54
55pub use proc_macro::Delimiter;
56
57pub use crate::bridge::*;
58pub use crate::server_impl::literal_from_str;
59pub use crate::token_stream::{TokenStream, TokenStreamIter, literal_to_string};
60
61#[derive(Copy, Clone, Eq, PartialEq, Debug)]
62pub enum ProcMacroKind {
63    CustomDerive,
64    Attr,
65    Bang,
66}
67
68pub const RUSTC_VERSION_STRING: &str = env!("RUSTC_VERSION");
69
70pub struct ProcMacroSrv<'env> {
71    expanders: Mutex<HashMap<Utf8PathBuf, Arc<dylib::Expander>>>,
72    env: &'env EnvSnapshot,
73    temp_dir: TempDir,
74}
75
76impl<'env> ProcMacroSrv<'env> {
77    pub fn new(env: &'env EnvSnapshot) -> Self {
78        Self {
79            expanders: Default::default(),
80            env,
81            temp_dir: TempDir::with_prefix("proc-macro-srv").unwrap(),
82        }
83    }
84
85    pub fn join_spans(&self, first: Span, second: Span) -> Option<Span> {
86        first.join(second, |_, _| {
87            // FIXME: Once we can talk back to the client, implement a "long join" request for anchors
88            // that differ in [AstId]s as joining those spans requires resolving the AstIds.
89            None
90        })
91    }
92}
93
94const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024;
95
96impl ProcMacroSrv<'_> {
97    pub fn expand<S: ProcMacroSrvSpan>(
98        &self,
99        lib: impl AsRef<Utf8Path>,
100        env: &[(String, String)],
101        current_dir: Option<impl AsRef<Path>>,
102        macro_name: &str,
103        macro_body: token_stream::TokenStream<S>,
104        attribute: Option<token_stream::TokenStream<S>>,
105        def_site: S,
106        call_site: S,
107        mixed_site: S,
108    ) -> Result<token_stream::TokenStream<S>, PanicMessage> {
109        let snapped_env = self.env;
110        let expander = self.expander(lib.as_ref()).map_err(|err| PanicMessage {
111            message: Some(format!("failed to load macro: {err}")),
112        })?;
113
114        let prev_env = EnvChange::apply(snapped_env, env, current_dir.as_ref().map(<_>::as_ref));
115
116        // Note, we spawn a new thread here so that thread locals allocation don't accumulate (this
117        // includes the proc-macro symbol interner)
118        let result = thread::scope(|s| {
119            let thread = thread::Builder::new()
120                .stack_size(EXPANDER_STACK_SIZE)
121                .name(macro_name.to_owned())
122                .spawn_scoped(s, move || {
123                    expander
124                        .expand(macro_name, macro_body, attribute, def_site, call_site, mixed_site)
125                });
126            match thread.unwrap().join() {
127                Ok(res) => res,
128                Err(e) => std::panic::resume_unwind(e),
129            }
130        });
131        prev_env.rollback();
132
133        result
134    }
135
136    pub fn list_macros(
137        &self,
138        dylib_path: &Utf8Path,
139    ) -> Result<Vec<(String, ProcMacroKind)>, String> {
140        let expander = self.expander(dylib_path)?;
141        Ok(expander.list_macros().map(|(k, v)| (k.to_owned(), v)).collect())
142    }
143
144    fn expander(&self, path: &Utf8Path) -> Result<Arc<dylib::Expander>, String> {
145        let expander = || {
146            let expander = dylib::Expander::new(&self.temp_dir, path)
147                .map_err(|err| format!("Cannot create expander for {path}: {err}",));
148            expander.map(Arc::new)
149        };
150
151        Ok(
152            match self
153                .expanders
154                .lock()
155                .unwrap_or_else(PoisonError::into_inner)
156                .entry(path.to_path_buf())
157            {
158                Entry::Vacant(v) => v.insert(expander()?).clone(),
159                Entry::Occupied(mut e) => {
160                    let time = fs::metadata(path).and_then(|it| it.modified()).ok();
161                    if Some(e.get().modified_time()) != time {
162                        e.insert(expander()?);
163                    }
164                    e.get().clone()
165                }
166            },
167        )
168    }
169}
170
171pub trait ProcMacroSrvSpan: Copy + Send + Sync {
172    type Server: proc_macro::bridge::server::Server<TokenStream = crate::token_stream::TokenStream<Self>>;
173    fn make_server(call_site: Self, def_site: Self, mixed_site: Self) -> Self::Server;
174}
175
176impl ProcMacroSrvSpan for SpanId {
177    type Server = server_impl::token_id::SpanIdServer;
178
179    fn make_server(call_site: Self, def_site: Self, mixed_site: Self) -> Self::Server {
180        Self::Server {
181            call_site,
182            def_site,
183            mixed_site,
184            tracked_env_vars: Default::default(),
185            tracked_paths: Default::default(),
186        }
187    }
188}
189impl ProcMacroSrvSpan for Span {
190    type Server = server_impl::rust_analyzer_span::RaSpanServer;
191    fn make_server(call_site: Self, def_site: Self, mixed_site: Self) -> Self::Server {
192        Self::Server {
193            call_site,
194            def_site,
195            mixed_site,
196            tracked_env_vars: Default::default(),
197            tracked_paths: Default::default(),
198        }
199    }
200}
201
202#[derive(Debug, Clone)]
203pub struct PanicMessage {
204    message: Option<String>,
205}
206
207impl PanicMessage {
208    pub fn into_string(self) -> Option<String> {
209        self.message
210    }
211}
212
213pub struct EnvSnapshot {
214    vars: HashMap<OsString, OsString>,
215}
216
217impl Default for EnvSnapshot {
218    fn default() -> EnvSnapshot {
219        EnvSnapshot { vars: env::vars_os().collect() }
220    }
221}
222
223static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
224
225struct EnvChange<'snap> {
226    changed_vars: Vec<&'snap str>,
227    prev_working_dir: Option<PathBuf>,
228    snap: &'snap EnvSnapshot,
229    _guard: std::sync::MutexGuard<'snap, ()>,
230}
231
232impl<'snap> EnvChange<'snap> {
233    fn apply(
234        snap: &'snap EnvSnapshot,
235        new_vars: &'snap [(String, String)],
236        current_dir: Option<&Path>,
237    ) -> EnvChange<'snap> {
238        let guard = ENV_LOCK.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
239        let prev_working_dir = match current_dir {
240            Some(dir) => {
241                let prev_working_dir = std::env::current_dir().ok();
242                if let Err(err) = std::env::set_current_dir(dir) {
243                    eprintln!(
244                        "Failed to set the current working dir to {}. Error: {err:?}",
245                        dir.display()
246                    )
247                }
248                prev_working_dir
249            }
250            None => None,
251        };
252        EnvChange {
253            snap,
254            changed_vars: new_vars
255                .iter()
256                .map(|(k, v)| {
257                    // SAFETY: We have acquired the environment lock
258                    unsafe { env::set_var(k, v) };
259                    &**k
260                })
261                .collect(),
262            prev_working_dir,
263            _guard: guard,
264        }
265    }
266
267    fn rollback(self) {}
268}
269
270impl Drop for EnvChange<'_> {
271    fn drop(&mut self) {
272        for name in self.changed_vars.drain(..) {
273            // SAFETY: We have acquired the environment lock
274            unsafe {
275                match self.snap.vars.get::<std::ffi::OsStr>(name.as_ref()) {
276                    Some(prev_val) => env::set_var(name, prev_val),
277                    None => env::remove_var(name),
278                }
279            }
280        }
281
282        if let Some(dir) = &self.prev_working_dir
283            && let Err(err) = std::env::set_current_dir(dir)
284        {
285            eprintln!(
286                "Failed to set the current working dir to {}. Error: {:?}",
287                dir.display(),
288                err
289            )
290        }
291    }
292}
293
294#[cfg(test)]
295mod tests;
296
297#[cfg(test)]
298pub fn proc_macro_test_dylib_path() -> paths::Utf8PathBuf {
299    proc_macro_test::PROC_MACRO_TEST_LOCATION.into()
300}