Skip to main content

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(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    unused_features
23)]
24#![deny(deprecated_safe, clippy::undocumented_unsafe_blocks)]
25
26#[cfg(not(feature = "in-rust-tree"))]
27extern crate proc_macro as rustc_proc_macro;
28#[cfg(feature = "in-rust-tree")]
29extern crate rustc_driver as _;
30#[cfg(feature = "in-rust-tree")]
31extern crate rustc_proc_macro;
32
33#[cfg(not(feature = "in-rust-tree"))]
34extern crate ra_ap_rustc_lexer as rustc_lexer;
35#[cfg(feature = "in-rust-tree")]
36extern crate rustc_lexer;
37
38mod bridge;
39mod dylib;
40mod server_impl;
41mod token_stream;
42
43use std::{
44    collections::{HashMap, hash_map::Entry},
45    env,
46    ffi::OsString,
47    fs,
48    ops::Range,
49    path::{Path, PathBuf},
50    sync::{Arc, Mutex, PoisonError},
51    thread,
52};
53
54use paths::{Utf8Path, Utf8PathBuf};
55use span::Span;
56use temp_dir::TempDir;
57
58pub use crate::server_impl::token_id::SpanId;
59
60pub use rustc_proc_macro::Delimiter;
61pub use span;
62
63pub use crate::bridge::*;
64pub use crate::server_impl::literal_from_str;
65pub use crate::token_stream::{TokenStream, TokenStreamIter, literal_to_string};
66
67#[derive(Copy, Clone, Eq, PartialEq, Debug)]
68pub enum ProcMacroKind {
69    CustomDerive,
70    Attr,
71    Bang,
72}
73
74pub const RUSTC_VERSION_STRING: &str = env!("RUSTC_VERSION");
75
76pub struct ProcMacroSrv<'env> {
77    expanders: Mutex<HashMap<Utf8PathBuf, Arc<dylib::Expander>>>,
78    env: &'env EnvSnapshot,
79    temp_dir: TempDir,
80}
81
82impl<'env> ProcMacroSrv<'env> {
83    pub fn new(env: &'env EnvSnapshot) -> Self {
84        Self {
85            expanders: Default::default(),
86            env,
87            temp_dir: TempDir::with_prefix("proc-macro-srv").unwrap(),
88        }
89    }
90
91    pub fn join_spans(&self, first: Span, second: Span) -> Option<Span> {
92        first.join(second, |_, _| {
93            // FIXME: Once we can talk back to the client, implement a "long join" request for anchors
94            // that differ in [AstId]s as joining those spans requires resolving the AstIds.
95            None
96        })
97    }
98}
99
100#[derive(Debug)]
101pub enum ProcMacroClientError {
102    Cancelled { reason: String },
103    Io(std::io::Error),
104    Protocol(String),
105    Eof,
106}
107
108#[derive(Debug)]
109pub enum ProcMacroPanicMarker {
110    Cancelled { reason: String },
111    Internal { reason: String },
112}
113
114pub type ProcMacroClientHandle<'a> = &'a mut (dyn ProcMacroClientInterface + Sync + Send);
115
116pub trait ProcMacroClientInterface {
117    fn file(&mut self, file_id: span::FileId) -> String;
118    fn source_text(&mut self, span: Span) -> Option<String>;
119    fn local_file(&mut self, file_id: span::FileId) -> Option<String>;
120    /// Line and column are 1-based.
121    fn line_column(&mut self, span: Span) -> Option<(u32, u32)>;
122
123    fn byte_range(&mut self, span: Span) -> Range<usize>;
124    fn span_source(&mut self, span: Span) -> Span;
125    fn span_parent(&mut self, span: Span) -> Option<Span>;
126}
127
128const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024;
129
130pub enum ExpandError {
131    Panic(PanicMessage),
132    Cancelled { reason: Option<String> },
133    Internal { reason: Option<String> },
134}
135
136impl ExpandError {
137    pub fn into_string(self) -> Option<String> {
138        match self {
139            ExpandError::Panic(panic_message) => panic_message.into_string(),
140            ExpandError::Cancelled { reason } => reason,
141            ExpandError::Internal { reason } => reason,
142        }
143    }
144}
145
146impl ProcMacroSrv<'_> {
147    pub fn expand<S: ProcMacroSrvSpan>(
148        &self,
149        lib: impl AsRef<Utf8Path>,
150        env: &[(String, String)],
151        current_dir: Option<impl AsRef<Path>>,
152        macro_name: &str,
153        macro_body: token_stream::TokenStream<S>,
154        attribute: Option<token_stream::TokenStream<S>>,
155        def_site: S,
156        call_site: S,
157        mixed_site: S,
158        callback: Option<ProcMacroClientHandle<'_>>,
159    ) -> Result<token_stream::TokenStream<S>, ExpandError> {
160        let snapped_env = self.env;
161        let expander = self.expander(lib.as_ref()).map_err(|err| ExpandError::Internal {
162            reason: Some(format!("failed to load macro: {err}")),
163        })?;
164
165        let prev_env = EnvChange::apply(snapped_env, env, current_dir.as_ref().map(<_>::as_ref));
166
167        // Note, we spawn a new thread here so that thread locals allocation don't accumulate (this
168        // includes the proc-macro symbol interner)
169        let result = thread::scope(|s| {
170            let thread = thread::Builder::new()
171                .stack_size(EXPANDER_STACK_SIZE)
172                .name(macro_name.to_owned())
173                .spawn_scoped(s, move || {
174                    expander.expand(
175                        macro_name, macro_body, attribute, def_site, call_site, mixed_site,
176                        callback,
177                    )
178                });
179            match thread.unwrap().join() {
180                Ok(res) => res.map_err(ExpandError::Panic),
181
182                Err(payload) => {
183                    if let Some(marker) = payload.downcast_ref::<ProcMacroPanicMarker>() {
184                        return match marker {
185                            ProcMacroPanicMarker::Cancelled { reason } => {
186                                Err(ExpandError::Cancelled { reason: Some(reason.clone()) })
187                            }
188                            ProcMacroPanicMarker::Internal { reason } => {
189                                Err(ExpandError::Internal { reason: Some(reason.clone()) })
190                            }
191                        };
192                    }
193
194                    std::panic::resume_unwind(payload)
195                }
196            }
197        });
198        prev_env.rollback();
199
200        result
201    }
202
203    pub fn list_macros(
204        &self,
205        dylib_path: &Utf8Path,
206    ) -> Result<Vec<(String, ProcMacroKind)>, String> {
207        let expander = self.expander(dylib_path)?;
208        Ok(expander.list_macros().map(|(k, v)| (k.to_owned(), v)).collect())
209    }
210
211    fn expander(&self, path: &Utf8Path) -> Result<Arc<dylib::Expander>, String> {
212        let expander = || {
213            let expander = dylib::Expander::new(&self.temp_dir, path)
214                .map_err(|err| format!("Cannot create expander for {path}: {err}",));
215            expander.map(Arc::new)
216        };
217
218        Ok(
219            match self
220                .expanders
221                .lock()
222                .unwrap_or_else(PoisonError::into_inner)
223                .entry(path.to_path_buf())
224            {
225                Entry::Vacant(v) => v.insert(expander()?).clone(),
226                Entry::Occupied(mut e) => {
227                    let time = fs::metadata(path).and_then(|it| it.modified()).ok();
228                    if Some(e.get().modified_time()) != time {
229                        e.insert(expander()?);
230                    }
231                    e.get().clone()
232                }
233            },
234        )
235    }
236}
237
238pub trait ProcMacroSrvSpan: Copy + Send + Sync {
239    type Server<'a>: rustc_proc_macro::bridge::server::Server<
240            TokenStream = crate::token_stream::TokenStream<Self>,
241        >;
242    fn make_server<'a>(
243        call_site: Self,
244        def_site: Self,
245        mixed_site: Self,
246        callback: Option<ProcMacroClientHandle<'a>>,
247    ) -> Self::Server<'a>;
248}
249
250impl ProcMacroSrvSpan for SpanId {
251    type Server<'a> = server_impl::token_id::SpanIdServer<'a>;
252
253    fn make_server<'a>(
254        call_site: Self,
255        def_site: Self,
256        mixed_site: Self,
257        callback: Option<ProcMacroClientHandle<'a>>,
258    ) -> Self::Server<'a> {
259        Self::Server {
260            call_site,
261            def_site,
262            mixed_site,
263            callback,
264            tracked_env_vars: Default::default(),
265            tracked_paths: Default::default(),
266        }
267    }
268}
269
270impl ProcMacroSrvSpan for Span {
271    type Server<'a> = server_impl::rust_analyzer_span::RaSpanServer<'a>;
272    fn make_server<'a>(
273        call_site: Self,
274        def_site: Self,
275        mixed_site: Self,
276        callback: Option<ProcMacroClientHandle<'a>>,
277    ) -> Self::Server<'a> {
278        Self::Server {
279            call_site,
280            def_site,
281            mixed_site,
282            callback,
283            tracked_env_vars: Default::default(),
284            tracked_paths: Default::default(),
285        }
286    }
287}
288
289#[derive(Debug, Clone)]
290pub struct PanicMessage {
291    message: Option<String>,
292}
293
294impl PanicMessage {
295    pub fn into_string(self) -> Option<String> {
296        self.message
297    }
298}
299
300pub struct EnvSnapshot {
301    vars: HashMap<OsString, OsString>,
302}
303
304impl Default for EnvSnapshot {
305    fn default() -> EnvSnapshot {
306        EnvSnapshot { vars: env::vars_os().collect() }
307    }
308}
309
310static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
311
312struct EnvChange<'snap> {
313    changed_vars: Vec<&'snap str>,
314    prev_working_dir: Option<PathBuf>,
315    snap: &'snap EnvSnapshot,
316    _guard: std::sync::MutexGuard<'snap, ()>,
317}
318
319impl<'snap> EnvChange<'snap> {
320    fn apply(
321        snap: &'snap EnvSnapshot,
322        new_vars: &'snap [(String, String)],
323        current_dir: Option<&Path>,
324    ) -> EnvChange<'snap> {
325        let guard = ENV_LOCK.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
326        let prev_working_dir = match current_dir {
327            Some(dir) => {
328                let prev_working_dir = std::env::current_dir().ok();
329                if let Err(err) = std::env::set_current_dir(dir) {
330                    eprintln!(
331                        "Failed to change the current working dir to {}. Error: {err:?}",
332                        dir.display()
333                    )
334                }
335                prev_working_dir
336            }
337            None => None,
338        };
339        EnvChange {
340            snap,
341            changed_vars: new_vars
342                .iter()
343                .map(|(k, v)| {
344                    // SAFETY: We have acquired the environment lock
345                    unsafe { env::set_var(k, v) };
346                    &**k
347                })
348                .collect(),
349            prev_working_dir,
350            _guard: guard,
351        }
352    }
353
354    fn rollback(self) {}
355}
356
357impl Drop for EnvChange<'_> {
358    fn drop(&mut self) {
359        for name in self.changed_vars.drain(..) {
360            // SAFETY: We have acquired the environment lock
361            unsafe {
362                match self.snap.vars.get::<std::ffi::OsStr>(name.as_ref()) {
363                    Some(prev_val) => env::set_var(name, prev_val),
364                    None => env::remove_var(name),
365                }
366            }
367        }
368
369        if let Some(dir) = &self.prev_working_dir
370            && let Err(err) = std::env::set_current_dir(dir)
371        {
372            eprintln!(
373                "Failed to change the current working dir back to {}. Error: {:?}",
374                dir.display(),
375                err
376            )
377        }
378    }
379}
380
381#[cfg(test)]
382mod tests;
383
384#[cfg(test)]
385pub fn proc_macro_test_dylib_path() -> paths::Utf8PathBuf {
386    proc_macro_test::PROC_MACRO_TEST_LOCATION.into()
387}