1#![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 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 ¯o_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 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 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}