diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/proc_macro_api/Cargo.toml | 3 | ||||
-rw-r--r-- | crates/proc_macro_api/src/lib.rs | 22 | ||||
-rw-r--r-- | crates/proc_macro_api/src/version.rs | 132 |
3 files changed, 154 insertions, 3 deletions
diff --git a/crates/proc_macro_api/Cargo.toml b/crates/proc_macro_api/Cargo.toml index f09726223..16fd56c7e 100644 --- a/crates/proc_macro_api/Cargo.toml +++ b/crates/proc_macro_api/Cargo.toml | |||
@@ -19,3 +19,6 @@ jod-thread = "0.1.1" | |||
19 | tt = { path = "../tt", version = "0.0.0" } | 19 | tt = { path = "../tt", version = "0.0.0" } |
20 | base_db = { path = "../base_db", version = "0.0.0" } | 20 | base_db = { path = "../base_db", version = "0.0.0" } |
21 | stdx = { path = "../stdx", version = "0.0.0" } | 21 | stdx = { path = "../stdx", version = "0.0.0" } |
22 | snap = "1" | ||
23 | object = { version = "0.23.0", default-features = false, features = ["std", "read_core", "elf", "macho", "pe", "unaligned"] } | ||
24 | memmap = "0.7.0" | ||
diff --git a/crates/proc_macro_api/src/lib.rs b/crates/proc_macro_api/src/lib.rs index 2ea456fb0..941d0fe9e 100644 --- a/crates/proc_macro_api/src/lib.rs +++ b/crates/proc_macro_api/src/lib.rs | |||
@@ -5,10 +5,12 @@ | |||
5 | //! is used to provide basic infrastructure for communication between two | 5 | //! is used to provide basic infrastructure for communication between two |
6 | //! processes: Client (RA itself), Server (the external program) | 6 | //! processes: Client (RA itself), Server (the external program) |
7 | 7 | ||
8 | mod rpc; | ||
9 | mod process; | ||
10 | pub mod msg; | 8 | pub mod msg; |
9 | mod process; | ||
10 | mod rpc; | ||
11 | mod version; | ||
11 | 12 | ||
13 | use base_db::{Env, ProcMacro}; | ||
12 | use std::{ | 14 | use std::{ |
13 | ffi::OsStr, | 15 | ffi::OsStr, |
14 | io, | 16 | io, |
@@ -16,7 +18,6 @@ use std::{ | |||
16 | sync::Arc, | 18 | sync::Arc, |
17 | }; | 19 | }; |
18 | 20 | ||
19 | use base_db::{Env, ProcMacro}; | ||
20 | use tt::{SmolStr, Subtree}; | 21 | use tt::{SmolStr, Subtree}; |
21 | 22 | ||
22 | use crate::process::{ProcMacroProcessSrv, ProcMacroProcessThread}; | 23 | use crate::process::{ProcMacroProcessSrv, ProcMacroProcessThread}; |
@@ -75,6 +76,21 @@ impl ProcMacroClient { | |||
75 | } | 76 | } |
76 | 77 | ||
77 | pub fn by_dylib_path(&self, dylib_path: &Path) -> Vec<ProcMacro> { | 78 | pub fn by_dylib_path(&self, dylib_path: &Path) -> Vec<ProcMacro> { |
79 | match version::read_info(dylib_path) { | ||
80 | Ok(info) => { | ||
81 | if info.version.0 < 1 || info.version.1 < 47 { | ||
82 | eprintln!("proc-macro {} built by {:#?} is not supported by Rust Analyzer, please update your rust version.", dylib_path.to_string_lossy(), info); | ||
83 | } | ||
84 | } | ||
85 | Err(err) => { | ||
86 | eprintln!( | ||
87 | "proc-macro {} failed to find the given version. Reason: {}", | ||
88 | dylib_path.to_string_lossy(), | ||
89 | err | ||
90 | ); | ||
91 | } | ||
92 | } | ||
93 | |||
78 | let macros = match self.process.find_proc_macros(dylib_path) { | 94 | let macros = match self.process.find_proc_macros(dylib_path) { |
79 | Err(err) => { | 95 | Err(err) => { |
80 | eprintln!("Failed to find proc macros. Error: {:#?}", err); | 96 | eprintln!("Failed to find proc macros. Error: {:#?}", err); |
diff --git a/crates/proc_macro_api/src/version.rs b/crates/proc_macro_api/src/version.rs new file mode 100644 index 000000000..11a7fb59a --- /dev/null +++ b/crates/proc_macro_api/src/version.rs | |||
@@ -0,0 +1,132 @@ | |||
1 | //! Reading proc-macro rustc version information from binary data | ||
2 | |||
3 | use std::{ | ||
4 | fs::File, | ||
5 | io::{self, Read}, | ||
6 | path::Path, | ||
7 | }; | ||
8 | |||
9 | use memmap::Mmap; | ||
10 | use object::read::{File as BinaryFile, Object, ObjectSection}; | ||
11 | use snap::read::FrameDecoder as SnapDecoder; | ||
12 | |||
13 | #[derive(Debug)] | ||
14 | pub(crate) struct RustCInfo { | ||
15 | pub(crate) version: (usize, usize, usize), | ||
16 | pub(crate) channel: String, | ||
17 | pub(crate) commit: String, | ||
18 | pub(crate) date: String, | ||
19 | } | ||
20 | |||
21 | pub(crate) fn read_info(dylib_path: &Path) -> io::Result<RustCInfo> { | ||
22 | macro_rules! err { | ||
23 | ($e:literal) => { | ||
24 | io::Error::new(io::ErrorKind::InvalidData, $e) | ||
25 | }; | ||
26 | } | ||
27 | |||
28 | let ver_str = read_version(dylib_path)?; | ||
29 | let mut items = ver_str.split_whitespace(); | ||
30 | let tag = items.next().ok_or(err!("version format error"))?; | ||
31 | if tag != "rustc" { | ||
32 | return Err(err!("version format error (No rustc tag)")); | ||
33 | } | ||
34 | |||
35 | let version_part = items.next().ok_or(err!("no version string"))?; | ||
36 | let mut version_parts = version_part.split("-"); | ||
37 | let version = version_parts.next().ok_or(err!("no version"))?; | ||
38 | let channel = version_parts.next().unwrap_or_default().to_string(); | ||
39 | |||
40 | let commit = items.next().ok_or(err!("no commit info"))?; | ||
41 | // remove ( | ||
42 | if commit.len() == 0 { | ||
43 | return Err(err!("commit format error")); | ||
44 | } | ||
45 | let commit = commit[1..].to_string(); | ||
46 | let date = items.next().ok_or(err!("no date info"))?; | ||
47 | // remove ) | ||
48 | if date.len() == 0 { | ||
49 | return Err(err!("date format error")); | ||
50 | } | ||
51 | let date = date[0..date.len() - 2].to_string(); | ||
52 | |||
53 | let version_numbers = version | ||
54 | .split(".") | ||
55 | .map(|it| it.parse::<usize>()) | ||
56 | .collect::<Result<Vec<_>, _>>() | ||
57 | .map_err(|_| err!("version number error"))?; | ||
58 | |||
59 | if version_numbers.len() != 3 { | ||
60 | return Err(err!("version number format error")); | ||
61 | } | ||
62 | let version = (version_numbers[0], version_numbers[1], version_numbers[2]); | ||
63 | |||
64 | Ok(RustCInfo { version, channel, commit, date }) | ||
65 | } | ||
66 | |||
67 | /// This is used inside read_version() to locate the ".rustc" section | ||
68 | /// from a proc macro crate's binary file. | ||
69 | fn read_section<'a>(dylib_binary: &'a [u8], section_name: &str) -> io::Result<&'a [u8]> { | ||
70 | BinaryFile::parse(dylib_binary) | ||
71 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? | ||
72 | .section_by_name(section_name) | ||
73 | .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "section read error"))? | ||
74 | .data() | ||
75 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) | ||
76 | } | ||
77 | |||
78 | /// Check the version of rustc that was used to compile a proc macro crate's | ||
79 | /// | ||
80 | /// binary file. | ||
81 | /// A proc macro crate binary's ".rustc" section has following byte layout: | ||
82 | /// * [b'r',b'u',b's',b't',0,0,0,5] is the first 8 bytes | ||
83 | /// * ff060000 734e6150 is followed, it's the snappy format magic bytes, | ||
84 | /// means bytes from here(including this sequence) are compressed in | ||
85 | /// snappy compression format. Version info is inside here, so decompress | ||
86 | /// this. | ||
87 | /// The bytes you get after decompressing the snappy format portion has | ||
88 | /// following layout: | ||
89 | /// * [b'r',b'u',b's',b't',0,0,0,5] is the first 8 bytes(again) | ||
90 | /// * [crate root bytes] next 4 bytes is to store crate root position, | ||
91 | /// according to rustc's source code comment | ||
92 | /// * [length byte] next 1 byte tells us how many bytes we should read next | ||
93 | /// for the version string's utf8 bytes | ||
94 | /// * [version string bytes encoded in utf8] <- GET THIS BOI | ||
95 | /// * [some more bytes that we don really care but still there] :-) | ||
96 | /// Check this issue for more about the bytes layout: | ||
97 | /// https://github.com/rust-analyzer/rust-analyzer/issues/6174 | ||
98 | fn read_version(dylib_path: &Path) -> io::Result<String> { | ||
99 | let dylib_file = File::open(dylib_path)?; | ||
100 | let dylib_mmaped = unsafe { Mmap::map(&dylib_file) }?; | ||
101 | |||
102 | let dot_rustc = read_section(&dylib_mmaped, ".rustc")?; | ||
103 | |||
104 | let header = &dot_rustc[..8]; | ||
105 | const EXPECTED_HEADER: [u8; 8] = [b'r', b'u', b's', b't', 0, 0, 0, 5]; | ||
106 | // check if header is valid | ||
107 | if header != EXPECTED_HEADER { | ||
108 | return Err(io::Error::new( | ||
109 | io::ErrorKind::InvalidData, | ||
110 | format!("only metadata version 5 is supported, section header was: {:?}", header), | ||
111 | )); | ||
112 | } | ||
113 | |||
114 | let snappy_portion = &dot_rustc[8..]; | ||
115 | |||
116 | let mut snappy_decoder = SnapDecoder::new(snappy_portion); | ||
117 | |||
118 | // the bytes before version string bytes, so this basically is: | ||
119 | // 8 bytes for [b'r',b'u',b's',b't',0,0,0,5] | ||
120 | // 4 bytes for [crate root bytes] | ||
121 | // 1 byte for length of version string | ||
122 | // so 13 bytes in total, and we should check the 13th byte | ||
123 | // to know the length | ||
124 | let mut bytes_before_version = [0u8; 13]; | ||
125 | snappy_decoder.read_exact(&mut bytes_before_version)?; | ||
126 | let length = bytes_before_version[12]; // what? can't use -1 indexing? | ||
127 | |||
128 | let mut version_string_utf8 = vec![0u8; length as usize]; | ||
129 | snappy_decoder.read_exact(&mut version_string_utf8)?; | ||
130 | let version_string = String::from_utf8(version_string_utf8); | ||
131 | version_string.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) | ||
132 | } | ||