diff options
author | Edwin Cheng <[email protected]> | 2021-03-04 02:48:12 +0000 |
---|---|---|
committer | Edwin Cheng <[email protected]> | 2021-03-04 06:47:34 +0000 |
commit | cc8c40480a34aa1d233d9a34b505bee16897ef54 (patch) | |
tree | 861635c3c5f12feffbd087086ba8d15acbdec9b6 | |
parent | 0669abda4aa2a544ad8ab465fba0853ff8379c23 (diff) |
Print warning if proc-macro built by old rustc
-rw-r--r-- | crates/proc_macro_api/src/lib.rs | 90 | ||||
-rw-r--r-- | crates/proc_macro_api/src/version.rs | 131 |
2 files changed, 148 insertions, 73 deletions
diff --git a/crates/proc_macro_api/src/lib.rs b/crates/proc_macro_api/src/lib.rs index 8f0f141f7..941d0fe9e 100644 --- a/crates/proc_macro_api/src/lib.rs +++ b/crates/proc_macro_api/src/lib.rs | |||
@@ -8,12 +8,12 @@ | |||
8 | pub mod msg; | 8 | pub mod msg; |
9 | mod process; | 9 | mod process; |
10 | mod rpc; | 10 | mod rpc; |
11 | mod version; | ||
11 | 12 | ||
12 | use base_db::{Env, ProcMacro}; | 13 | use base_db::{Env, ProcMacro}; |
13 | use std::{ | 14 | use std::{ |
14 | ffi::OsStr, | 15 | ffi::OsStr, |
15 | fs::File, | 16 | io, |
16 | io::{self, Read}, | ||
17 | path::{Path, PathBuf}, | 17 | path::{Path, PathBuf}, |
18 | sync::Arc, | 18 | sync::Arc, |
19 | }; | 19 | }; |
@@ -24,10 +24,6 @@ use crate::process::{ProcMacroProcessSrv, ProcMacroProcessThread}; | |||
24 | 24 | ||
25 | pub use rpc::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask, ProcMacroKind}; | 25 | pub use rpc::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask, ProcMacroKind}; |
26 | 26 | ||
27 | use memmap::Mmap; | ||
28 | use object::read::{File as BinaryFile, Object, ObjectSection}; | ||
29 | use snap::read::FrameDecoder as SnapDecoder; | ||
30 | |||
31 | #[derive(Debug, Clone)] | 27 | #[derive(Debug, Clone)] |
32 | struct ProcMacroProcessExpander { | 28 | struct ProcMacroProcessExpander { |
33 | process: Arc<ProcMacroProcessSrv>, | 29 | process: Arc<ProcMacroProcessSrv>, |
@@ -80,6 +76,21 @@ impl ProcMacroClient { | |||
80 | } | 76 | } |
81 | 77 | ||
82 | 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 | |||
83 | let macros = match self.process.find_proc_macros(dylib_path) { | 94 | let macros = match self.process.find_proc_macros(dylib_path) { |
84 | Err(err) => { | 95 | Err(err) => { |
85 | eprintln!("Failed to find proc macros. Error: {:#?}", err); | 96 | eprintln!("Failed to find proc macros. Error: {:#?}", err); |
@@ -107,71 +118,4 @@ impl ProcMacroClient { | |||
107 | }) | 118 | }) |
108 | .collect() | 119 | .collect() |
109 | } | 120 | } |
110 | |||
111 | // This is used inside self.read_version() to locate the ".rustc" section | ||
112 | // from a proc macro crate's binary file. | ||
113 | fn read_section<'a>(&self, dylib_binary: &'a [u8], section_name: &str) -> io::Result<&'a [u8]> { | ||
114 | BinaryFile::parse(dylib_binary) | ||
115 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))? | ||
116 | .section_by_name(section_name) | ||
117 | .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "section read error"))? | ||
118 | .data() | ||
119 | .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) | ||
120 | } | ||
121 | |||
122 | // Check the version of rustc that was used to compile a proc macro crate's | ||
123 | // binary file. | ||
124 | // A proc macro crate binary's ".rustc" section has following byte layout: | ||
125 | // * [b'r',b'u',b's',b't',0,0,0,5] is the first 8 bytes | ||
126 | // * ff060000 734e6150 is followed, it's the snappy format magic bytes, | ||
127 | // means bytes from here(including this sequence) are compressed in | ||
128 | // snappy compression format. Version info is inside here, so decompress | ||
129 | // this. | ||
130 | // The bytes you get after decompressing the snappy format portion has | ||
131 | // following layout: | ||
132 | // * [b'r',b'u',b's',b't',0,0,0,5] is the first 8 bytes(again) | ||
133 | // * [crate root bytes] next 4 bytes is to store crate root position, | ||
134 | // according to rustc's source code comment | ||
135 | // * [length byte] next 1 byte tells us how many bytes we should read next | ||
136 | // for the version string's utf8 bytes | ||
137 | // * [version string bytes encoded in utf8] <- GET THIS BOI | ||
138 | // * [some more bytes that we don really care but still there] :-) | ||
139 | // Check this issue for more about the bytes layout: | ||
140 | // https://github.com/rust-analyzer/rust-analyzer/issues/6174 | ||
141 | #[allow(unused)] | ||
142 | fn read_version(&self, dylib_path: &Path) -> io::Result<String> { | ||
143 | let dylib_file = File::open(dylib_path)?; | ||
144 | let dylib_mmaped = unsafe { Mmap::map(&dylib_file) }?; | ||
145 | |||
146 | let dot_rustc = self.read_section(&dylib_mmaped, ".rustc")?; | ||
147 | |||
148 | let header = &dot_rustc[..8]; | ||
149 | const EXPECTED_HEADER: [u8; 8] = [b'r', b'u', b's', b't', 0, 0, 0, 5]; | ||
150 | // check if header is valid | ||
151 | if header != EXPECTED_HEADER { | ||
152 | return Err(io::Error::new( | ||
153 | io::ErrorKind::InvalidData, | ||
154 | format!("only metadata version 5 is supported, section header was: {:?}", header), | ||
155 | )); | ||
156 | } | ||
157 | |||
158 | let snappy_portion = &dot_rustc[8..]; | ||
159 | |||
160 | let mut snappy_decoder = SnapDecoder::new(snappy_portion); | ||
161 | |||
162 | // the bytes before version string bytes, so this basically is: | ||
163 | // 8 bytes for [b'r',b'u',b's',b't',0,0,0,5] | ||
164 | // 4 bytes for [crate root bytes] | ||
165 | // 1 byte for length of version string | ||
166 | // so 13 bytes in total, and we should check the 13th byte | ||
167 | // to know the length | ||
168 | let mut bytes_before_version = [0u8; 13]; | ||
169 | snappy_decoder.read_exact(&mut bytes_before_version)?; | ||
170 | let length = bytes_before_version[12]; // what? can't use -1 indexing? | ||
171 | |||
172 | let mut version_string_utf8 = vec![0u8; length as usize]; | ||
173 | snappy_decoder.read_exact(&mut version_string_utf8)?; | ||
174 | let version_string = String::from_utf8(version_string_utf8); | ||
175 | version_string.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) | ||
176 | } | ||
177 | } | 121 | } |
diff --git a/crates/proc_macro_api/src/version.rs b/crates/proc_macro_api/src/version.rs new file mode 100644 index 000000000..80cd183a0 --- /dev/null +++ b/crates/proc_macro_api/src/version.rs | |||
@@ -0,0 +1,131 @@ | |||
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 | // binary file. | ||
80 | // A proc macro crate binary's ".rustc" section has following byte layout: | ||
81 | // * [b'r',b'u',b's',b't',0,0,0,5] is the first 8 bytes | ||
82 | // * ff060000 734e6150 is followed, it's the snappy format magic bytes, | ||
83 | // means bytes from here(including this sequence) are compressed in | ||
84 | // snappy compression format. Version info is inside here, so decompress | ||
85 | // this. | ||
86 | // The bytes you get after decompressing the snappy format portion has | ||
87 | // following layout: | ||
88 | // * [b'r',b'u',b's',b't',0,0,0,5] is the first 8 bytes(again) | ||
89 | // * [crate root bytes] next 4 bytes is to store crate root position, | ||
90 | // according to rustc's source code comment | ||
91 | // * [length byte] next 1 byte tells us how many bytes we should read next | ||
92 | // for the version string's utf8 bytes | ||
93 | // * [version string bytes encoded in utf8] <- GET THIS BOI | ||
94 | // * [some more bytes that we don really care but still there] :-) | ||
95 | // Check this issue for more about the bytes layout: | ||
96 | // https://github.com/rust-analyzer/rust-analyzer/issues/6174 | ||
97 | fn read_version(dylib_path: &Path) -> io::Result<String> { | ||
98 | let dylib_file = File::open(dylib_path)?; | ||
99 | let dylib_mmaped = unsafe { Mmap::map(&dylib_file) }?; | ||
100 | |||
101 | let dot_rustc = read_section(&dylib_mmaped, ".rustc")?; | ||
102 | |||
103 | let header = &dot_rustc[..8]; | ||
104 | const EXPECTED_HEADER: [u8; 8] = [b'r', b'u', b's', b't', 0, 0, 0, 5]; | ||
105 | // check if header is valid | ||
106 | if header != EXPECTED_HEADER { | ||
107 | return Err(io::Error::new( | ||
108 | io::ErrorKind::InvalidData, | ||
109 | format!("only metadata version 5 is supported, section header was: {:?}", header), | ||
110 | )); | ||
111 | } | ||
112 | |||
113 | let snappy_portion = &dot_rustc[8..]; | ||
114 | |||
115 | let mut snappy_decoder = SnapDecoder::new(snappy_portion); | ||
116 | |||
117 | // the bytes before version string bytes, so this basically is: | ||
118 | // 8 bytes for [b'r',b'u',b's',b't',0,0,0,5] | ||
119 | // 4 bytes for [crate root bytes] | ||
120 | // 1 byte for length of version string | ||
121 | // so 13 bytes in total, and we should check the 13th byte | ||
122 | // to know the length | ||
123 | let mut bytes_before_version = [0u8; 13]; | ||
124 | snappy_decoder.read_exact(&mut bytes_before_version)?; | ||
125 | let length = bytes_before_version[12]; // what? can't use -1 indexing? | ||
126 | |||
127 | let mut version_string_utf8 = vec![0u8; length as usize]; | ||
128 | snappy_decoder.read_exact(&mut version_string_utf8)?; | ||
129 | let version_string = String::from_utf8(version_string_utf8); | ||
130 | version_string.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) | ||
131 | } | ||