summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/proc-macro-srv/src/lib.rs
blob: 3679bfc43c980a85a40909698dbe9fc7f27f9715 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//! RA Proc Macro Server
//!
//! This library is able to call compiled Rust custom derive dynamic libraries on arbitrary code.
//! The general idea here is based on <https://github.com/fedochet/rust-proc-macro-expander>.
//!
//! But we adapt it to better fit RA needs:
//!
//! * We use `tt` for proc-macro `TokenStream` server, it is easier to manipulate and interact with
//!   RA than `proc-macro2` token stream.
//! * By **copying** the whole rustc `lib_proc_macro` code, we are able to build this with `stable`
//!   rustc rather than `unstable`. (Although in general ABI compatibility is still an issue)…

#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
#![cfg_attr(
    feature = "sysroot-abi",
    feature(proc_macro_internals, proc_macro_diagnostic, proc_macro_span)
)]
#![allow(unreachable_pub)]

mod dylib;
mod abis;

use std::{
    collections::{hash_map::Entry, HashMap},
    env,
    ffi::OsString,
    fs,
    path::{Path, PathBuf},
    thread,
    time::SystemTime,
};

use proc_macro_api::{
    msg::{ExpandMacro, FlatTree, PanicMessage},
    ProcMacroKind,
};

#[derive(Default)]
pub(crate) struct ProcMacroSrv {
    expanders: HashMap<(PathBuf, SystemTime), dylib::Expander>,
}

const EXPANDER_STACK_SIZE: usize = 8 * 1024 * 1024;

impl ProcMacroSrv {
    pub fn expand(&mut self, task: ExpandMacro) -> Result<FlatTree, PanicMessage> {
        let expander = self.expander(task.lib.as_ref()).map_err(|err| {
            debug_assert!(false, "should list macros before asking to expand");
            PanicMessage(format!("failed to load macro: {}", err))
        })?;

        let prev_env = EnvSnapshot::new();
        for (k, v) in &task.env {
            env::set_var(k, v);
        }
        let prev_working_dir = match task.current_dir {
            Some(dir) => {
                let prev_working_dir = std::env::current_dir().ok();
                if let Err(err) = std::env::set_current_dir(&dir) {
                    eprintln!("Failed to set the current working dir to {}. Error: {:?}", dir, err)
                }
                prev_working_dir
            }
            None => None,
        };

        let macro_body = task.macro_body.to_subtree();
        let attributes = task.attributes.map(|it| it.to_subtree());
        let result = thread::scope(|s| {
            let thread = thread::Builder::new()
                .stack_size(EXPANDER_STACK_SIZE)
                .name(task.macro_name.clone())
                .spawn_scoped(s, || {
                    expander
                        .expand(&task.macro_name, &macro_body, attributes.as_ref())
                        .map(|it| FlatTree::new(&it))
                });
            let res = match thread {
                Ok(handle) => handle.join(),
                Err(e) => std::panic::resume_unwind(Box::new(e)),
            };

            match res {
                Ok(res) => res,
                Err(e) => std::panic::resume_unwind(e),
            }
        });

        prev_env.rollback();

        if let Some(dir) = prev_working_dir {
            if let Err(err) = std::env::set_current_dir(&dir) {
                eprintln!(
                    "Failed to set the current working dir to {}. Error: {:?}",
                    dir.display(),
                    err
                )
            }
        }

        result.map_err(PanicMessage)
    }

    pub(crate) fn list_macros(
        &mut self,
        dylib_path: &Path,
    ) -> Result<Vec<(String, ProcMacroKind)>, String> {
        let expander = self.expander(dylib_path)?;
        Ok(expander.list_macros())
    }

    fn expander(&mut self, path: &Path) -> Result<&dylib::Expander, String> {
        let time = fs::metadata(path).and_then(|it| it.modified()).map_err(|err| {
            format!("Failed to get file metadata for {}: {:?}", path.display(), err)
        })?;

        Ok(match self.expanders.entry((path.to_path_buf(), time)) {
            Entry::Vacant(v) => v.insert(dylib::Expander::new(path).map_err(|err| {
                format!("Cannot create expander for {}: {:?}", path.display(), err)
            })?),
            Entry::Occupied(e) => e.into_mut(),
        })
    }
}

struct EnvSnapshot {
    vars: HashMap<OsString, OsString>,
}

impl EnvSnapshot {
    fn new() -> EnvSnapshot {
        EnvSnapshot { vars: env::vars_os().collect() }
    }

    fn rollback(self) {
        let mut old_vars = self.vars;
        for (name, value) in env::vars_os() {
            let old_value = old_vars.remove(&name);
            if old_value != Some(value) {
                match old_value {
                    None => env::remove_var(name),
                    Some(old_value) => env::set_var(name, old_value),
                }
            }
        }
        for (name, old_value) in old_vars {
            env::set_var(name, old_value)
        }
    }
}

pub mod cli;

#[cfg(test)]
mod tests;