]> git.lizzy.rs Git - rust.git/blob - src/tools/tidy/src/pal.rs
Auto merge of #102700 - oli-obk:0xDEAD_TAIT, r=compiler-errors
[rust.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/`
25 //!   - `os/`
26 //!
27 //! `std/sys_common` should _not_ contain platform-specific code.
28 //! Finally, because std contains tests with platform-specific
29 //! `ignore` attributes, once the parser encounters `mod tests`,
30 //! platform-specific cfgs are allowed. Not sure yet how to deal with
31 //! this in the long term.
32
33 use crate::walk::{filter_dirs, walk};
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     "library/panic_abort",
40     "library/panic_unwind",
41     "library/unwind",
42     "library/rtstartup", // Not sure what to do about this. magic stuff for mingw
43     "library/term",      // Not sure how to make this crate portable, but test crate needs it.
44     "library/test",      // Probably should defer to unstable `std::sys` APIs.
45     // The `VaList` implementation must have platform specific code.
46     // The Windows implementation of a `va_list` is always a character
47     // pointer regardless of the target architecture. As a result,
48     // we must use `#[cfg(windows)]` to conditionally compile the
49     // correct `VaList` structure for windows.
50     "library/core/src/ffi/mod.rs",
51     "library/std/src/sys/", // Platform-specific code for std lives here.
52     "library/std/src/os",   // Platform-specific public interfaces
53     // Temporary `std` exceptions
54     // FIXME: platform-specific code should be moved to `sys`
55     "library/std/src/io/copy.rs",
56     "library/std/src/io/stdio.rs",
57     "library/std/src/f32.rs",
58     "library/std/src/f64.rs",
59     "library/std/src/path.rs",
60     "library/std/src/sys_common", // Should only contain abstractions over platforms
61     "library/std/src/net/test.rs", // Utility helpers for tests
62     "library/std/src/panic.rs",   // fuchsia-specific panic backtrace handling
63     "library/std/src/personality.rs",
64     "library/std/src/personality/",
65 ];
66
67 pub fn check(path: &Path, bad: &mut bool) {
68     // Sanity check that the complex parsing here works.
69     let mut saw_target_arch = false;
70     let mut saw_cfg_bang = false;
71     walk(path, &mut filter_dirs, &mut |entry, contents| {
72         let file = entry.path();
73         let filestr = file.to_string_lossy().replace("\\", "/");
74         if !filestr.ends_with(".rs") {
75             return;
76         }
77
78         let is_exception_path = EXCEPTION_PATHS.iter().any(|s| filestr.contains(&**s));
79         if is_exception_path {
80             return;
81         }
82
83         // exclude tests and benchmarks as some platforms do not support all tests
84         if filestr.contains("tests") || filestr.contains("benches") {
85             return;
86         }
87
88         check_cfgs(contents, &file, bad, &mut saw_target_arch, &mut saw_cfg_bang);
89     });
90
91     assert!(saw_target_arch);
92     assert!(saw_cfg_bang);
93 }
94
95 fn check_cfgs(
96     contents: &str,
97     file: &Path,
98     bad: &mut bool,
99     saw_target_arch: &mut bool,
100     saw_cfg_bang: &mut bool,
101 ) {
102     // Pull out all `cfg(...)` and `cfg!(...)` strings.
103     let cfgs = parse_cfgs(contents);
104
105     let mut line_numbers: Option<Vec<usize>> = None;
106     let mut err = |idx: usize, cfg: &str| {
107         if line_numbers.is_none() {
108             line_numbers = Some(contents.match_indices('\n').map(|(i, _)| i).collect());
109         }
110         let line_numbers = line_numbers.as_ref().expect("");
111         let line = match line_numbers.binary_search(&idx) {
112             Ok(_) => unreachable!(),
113             Err(i) => i + 1,
114         };
115         tidy_error!(bad, "{}:{}: platform-specific cfg: {}", file.display(), line, cfg);
116     };
117
118     for (idx, cfg) in cfgs {
119         // Sanity check that the parsing here works.
120         if !*saw_target_arch && cfg.contains("target_arch") {
121             *saw_target_arch = true
122         }
123         if !*saw_cfg_bang && cfg.contains("cfg!") {
124             *saw_cfg_bang = true
125         }
126
127         let contains_platform_specific_cfg = cfg.contains("target_os")
128             || cfg.contains("target_env")
129             || cfg.contains("target_abi")
130             || cfg.contains("target_vendor")
131             || cfg.contains("unix")
132             || cfg.contains("windows");
133
134         if !contains_platform_specific_cfg {
135             continue;
136         }
137
138         let preceded_by_doc_comment = {
139             let pre_contents = &contents[..idx];
140             let pre_newline = pre_contents.rfind('\n');
141             let pre_doc_comment = pre_contents.rfind("///");
142             match (pre_newline, pre_doc_comment) {
143                 (Some(n), Some(c)) => n < c,
144                 (None, Some(_)) => true,
145                 (_, None) => false,
146             }
147         };
148
149         if preceded_by_doc_comment {
150             continue;
151         }
152
153         // exclude tests as some platforms do not support all tests
154         if cfg.contains("test") {
155             continue;
156         }
157
158         err(idx, cfg);
159     }
160 }
161
162 fn parse_cfgs(contents: &str) -> Vec<(usize, &str)> {
163     let candidate_cfgs = contents.match_indices("cfg");
164     let candidate_cfg_idxs = candidate_cfgs.map(|(i, _)| i);
165     // This is puling out the indexes of all "cfg" strings
166     // that appear to be tokens followed by a parenthesis.
167     let cfgs = candidate_cfg_idxs.filter(|i| {
168         let pre_idx = i.saturating_sub(1);
169         let succeeds_non_ident = !contents
170             .as_bytes()
171             .get(pre_idx)
172             .cloned()
173             .map(char::from)
174             .map(char::is_alphanumeric)
175             .unwrap_or(false);
176         let contents_after = &contents[*i..];
177         let first_paren = contents_after.find('(');
178         let paren_idx = first_paren.map(|ip| i + ip);
179         let preceeds_whitespace_and_paren = paren_idx
180             .map(|ip| {
181                 let maybe_space = &contents[*i + "cfg".len()..ip];
182                 maybe_space.chars().all(|c| char::is_whitespace(c) || c == '!')
183             })
184             .unwrap_or(false);
185
186         succeeds_non_ident && preceeds_whitespace_and_paren
187     });
188
189     cfgs.flat_map(|i| {
190         let mut depth = 0;
191         let contents_from = &contents[i..];
192         for (j, byte) in contents_from.bytes().enumerate() {
193             match byte {
194                 b'(' => {
195                     depth += 1;
196                 }
197                 b')' => {
198                     depth -= 1;
199                     if depth == 0 {
200                         return Some((i, &contents_from[..=j]));
201                     }
202                 }
203                 _ => {}
204             }
205         }
206
207         // if the parentheses are unbalanced just ignore this cfg -- it'll be caught when attempting
208         // to run the compiler, and there's no real reason to lint it separately here
209         None
210     })
211     .collect()
212 }