]> git.proxmox.com Git - rustc.git/blob - src/tools/tidy/src/pal.rs
New upstream version 1.43.0+dfsg1
[rustc.git] / src / tools / tidy / src / pal.rs
1 //! Tidy check to enforce rules about platform-specific code in std.
2 //!
3 //! This is intended to maintain existing standards of code
4 //! organization in hopes that the standard library will continue to
5 //! be refactored to isolate platform-specific bits, making porting
6 //! easier; where "standard library" roughly means "all the
7 //! dependencies of the std and test crates".
8 //!
9 //! This generally means placing restrictions on where `cfg(unix)`,
10 //! `cfg(windows)`, `cfg(target_os)` and `cfg(target_env)` may appear,
11 //! the basic objective being to isolate platform-specific code to the
12 //! platform-specific `std::sys` modules, and to the allocation,
13 //! unwinding, and libc crates.
14 //!
15 //! Following are the basic rules, though there are currently
16 //! exceptions:
17 //!
18 //! - core may not have platform-specific code.
19 //! - libpanic_abort may have platform-specific code.
20 //! - libpanic_unwind may have platform-specific code.
21 //! - libunwind may have platform-specific code.
22 //! - other crates in the std facade may not.
23 //! - std may have platform-specific code in the following places:
24 //! - `sys/unix/`
25 //! - `sys/windows/`
26 //! - `os/`
27 //!
28 //! `std/sys_common` should _not_ contain platform-specific code.
29 //! Finally, because std contains tests with platform-specific
30 //! `ignore` attributes, once the parser encounters `mod tests`,
31 //! platform-specific cfgs are allowed. Not sure yet how to deal with
32 //! this in the long term.
33
34 use std::iter::Iterator;
35 use std::path::Path;
36
37 // Paths that may contain platform-specific code.
38 const EXCEPTION_PATHS: &[&str] = &[
39 // std crates
40 "src/libpanic_abort",
41 "src/libpanic_unwind",
42 "src/libunwind",
43 // black_box implementation is LLVM-version specific and it uses
44 // target_os to tell targets with different LLVM-versions apart
45 // (e.g. `wasm32-unknown-emscripten` vs `wasm32-unknown-unknown`):
46 "src/libcore/hint.rs",
47 "src/libstd/sys/", // Platform-specific code for std lives here.
48 // This has the trailing slash so that sys_common is not excepted.
49 "src/libstd/os", // Platform-specific public interfaces
50 "src/rtstartup", // Not sure what to do about this. magic stuff for mingw
51 // temporary exceptions
52 "src/libstd/lib.rs",
53 "src/libstd/path.rs",
54 "src/libstd/f32.rs",
55 "src/libstd/f64.rs",
56 // Integration test for platform-specific run-time feature detection:
57 "src/libstd/tests/run-time-detect.rs",
58 "src/libstd/net/test.rs",
59 "src/libstd/sys_common/mod.rs",
60 "src/libstd/sys_common/net.rs",
61 "src/libstd/sys_common/backtrace.rs",
62 "src/libterm", // Not sure how to make this crate portable, but test crate needs it.
63 "src/libtest", // Probably should defer to unstable `std::sys` APIs.
64 "src/libstd/sync/mpsc", // some tests are only run on non-emscripten
65 // std testing crates, okay for now at least
66 "src/libcore/tests",
67 "src/liballoc/tests/lib.rs",
68 // The `VaList` implementation must have platform specific code.
69 // The Windows implementation of a `va_list` is always a character
70 // pointer regardless of the target architecture. As a result,
71 // we must use `#[cfg(windows)]` to conditionally compile the
72 // correct `VaList` structure for windows.
73 "src/libcore/ffi.rs",
74 // non-std crates
75 "src/test",
76 "src/tools",
77 "src/librustc",
78 "src/librustdoc",
79 "src/librustc_ast",
80 "src/bootstrap",
81 ];
82
83 pub fn check(path: &Path, bad: &mut bool) {
84 // Sanity check that the complex parsing here works.
85 let mut saw_target_arch = false;
86 let mut saw_cfg_bang = false;
87 super::walk(path, &mut super::filter_dirs, &mut |entry, contents| {
88 let file = entry.path();
89 let filestr = file.to_string_lossy().replace("\\", "/");
90 if !filestr.ends_with(".rs") {
91 return;
92 }
93
94 let is_exception_path = EXCEPTION_PATHS.iter().any(|s| filestr.contains(&**s));
95 if is_exception_path {
96 return;
97 }
98
99 check_cfgs(contents, &file, bad, &mut saw_target_arch, &mut saw_cfg_bang);
100 });
101
102 assert!(saw_target_arch);
103 assert!(saw_cfg_bang);
104 }
105
106 fn check_cfgs(
107 contents: &str,
108 file: &Path,
109 bad: &mut bool,
110 saw_target_arch: &mut bool,
111 saw_cfg_bang: &mut bool,
112 ) {
113 // For now it's ok to have platform-specific code after 'mod tests'.
114 let mod_tests_idx = find_test_mod(contents);
115 let contents = &contents[..mod_tests_idx];
116 // Pull out all `cfg(...)` and `cfg!(...)` strings.
117 let cfgs = parse_cfgs(contents);
118
119 let mut line_numbers: Option<Vec<usize>> = None;
120 let mut err = |idx: usize, cfg: &str| {
121 if line_numbers.is_none() {
122 line_numbers = Some(contents.match_indices('\n').map(|(i, _)| i).collect());
123 }
124 let line_numbers = line_numbers.as_ref().expect("");
125 let line = match line_numbers.binary_search(&idx) {
126 Ok(_) => unreachable!(),
127 Err(i) => i + 1,
128 };
129 tidy_error!(bad, "{}:{}: platform-specific cfg: {}", file.display(), line, cfg);
130 };
131
132 for (idx, cfg) in cfgs {
133 // Sanity check that the parsing here works.
134 if !*saw_target_arch && cfg.contains("target_arch") {
135 *saw_target_arch = true
136 }
137 if !*saw_cfg_bang && cfg.contains("cfg!") {
138 *saw_cfg_bang = true
139 }
140
141 let contains_platform_specific_cfg = cfg.contains("target_os")
142 || cfg.contains("target_env")
143 || cfg.contains("target_vendor")
144 || cfg.contains("unix")
145 || cfg.contains("windows");
146
147 if !contains_platform_specific_cfg {
148 continue;
149 }
150
151 let preceeded_by_doc_comment = {
152 let pre_contents = &contents[..idx];
153 let pre_newline = pre_contents.rfind('\n');
154 let pre_doc_comment = pre_contents.rfind("///");
155 match (pre_newline, pre_doc_comment) {
156 (Some(n), Some(c)) => n < c,
157 (None, Some(_)) => true,
158 (_, None) => false,
159 }
160 };
161
162 if preceeded_by_doc_comment {
163 continue;
164 }
165
166 err(idx, cfg);
167 }
168 }
169
170 fn find_test_mod(contents: &str) -> usize {
171 if let Some(mod_tests_idx) = contents.find("mod tests") {
172 // Also capture a previous line indicating that "mod tests" is cfg'd out.
173 let prev_newline_idx = contents[..mod_tests_idx].rfind('\n').unwrap_or(mod_tests_idx);
174 let prev_newline_idx = contents[..prev_newline_idx].rfind('\n');
175 if let Some(nl) = prev_newline_idx {
176 let prev_line = &contents[nl + 1..mod_tests_idx];
177 if prev_line.contains("cfg(all(test, not(target_os")
178 || prev_line.contains("cfg(all(test, not(any(target_os")
179 {
180 nl
181 } else {
182 mod_tests_idx
183 }
184 } else {
185 mod_tests_idx
186 }
187 } else {
188 contents.len()
189 }
190 }
191
192 fn parse_cfgs<'a>(contents: &'a str) -> Vec<(usize, &'a str)> {
193 let candidate_cfgs = contents.match_indices("cfg");
194 let candidate_cfg_idxs = candidate_cfgs.map(|(i, _)| i);
195 // This is puling out the indexes of all "cfg" strings
196 // that appear to be tokens followed by a parenthesis.
197 let cfgs = candidate_cfg_idxs.filter(|i| {
198 let pre_idx = i.saturating_sub(*i);
199 let succeeds_non_ident = !contents
200 .as_bytes()
201 .get(pre_idx)
202 .cloned()
203 .map(char::from)
204 .map(char::is_alphanumeric)
205 .unwrap_or(false);
206 let contents_after = &contents[*i..];
207 let first_paren = contents_after.find('(');
208 let paren_idx = first_paren.map(|ip| i + ip);
209 let preceeds_whitespace_and_paren = paren_idx
210 .map(|ip| {
211 let maybe_space = &contents[*i + "cfg".len()..ip];
212 maybe_space.chars().all(|c| char::is_whitespace(c) || c == '!')
213 })
214 .unwrap_or(false);
215
216 succeeds_non_ident && preceeds_whitespace_and_paren
217 });
218
219 cfgs.flat_map(|i| {
220 let mut depth = 0;
221 let contents_from = &contents[i..];
222 for (j, byte) in contents_from.bytes().enumerate() {
223 match byte {
224 b'(' => {
225 depth += 1;
226 }
227 b')' => {
228 depth -= 1;
229 if depth == 0 {
230 return Some((i, &contents_from[..=j]));
231 }
232 }
233 _ => {}
234 }
235 }
236
237 // if the parentheses are unbalanced just ignore this cfg -- it'll be caught when attempting
238 // to run the compiler, and there's no real reason to lint it separately here
239 None
240 })
241 .collect()
242 }