]>
Commit | Line | Data |
---|---|---|
9fa01778 | 1 | //! Tidy check to enforce rules about platform-specific code in std. |
c30ab7b3 SL |
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 | //! | |
9fa01778 XL |
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: | |
17df50a5 | 24 | //! - `sys/` |
9fa01778 | 25 | //! - `os/` |
c30ab7b3 SL |
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 | ||
2b03887a | 33 | use crate::walk::{filter_dirs, walk}; |
c30ab7b3 | 34 | use std::iter::Iterator; |
dfeec247 | 35 | use std::path::Path; |
c30ab7b3 | 36 | |
9fa01778 | 37 | // Paths that may contain platform-specific code. |
b7449926 | 38 | const EXCEPTION_PATHS: &[&str] = &[ |
3dfed10e XL |
39 | "library/panic_abort", |
40 | "library/panic_unwind", | |
41 | "library/unwind", | |
17df50a5 XL |
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. | |
a1dfa0c6 XL |
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. | |
5e7ed085 | 50 | "library/core/src/ffi/mod.rs", |
17df50a5 XL |
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", | |
17df50a5 XL |
60 | "library/std/src/sys_common", // Should only contain abstractions over platforms |
61 | "library/std/src/net/test.rs", // Utility helpers for tests | |
5099ac24 | 62 | "library/std/src/panic.rs", // fuchsia-specific panic backtrace handling |
f2b60f7d FG |
63 | "library/std/src/personality.rs", |
64 | "library/std/src/personality/", | |
c30ab7b3 SL |
65 | ]; |
66 | ||
67 | pub fn check(path: &Path, bad: &mut bool) { | |
9fa01778 | 68 | // Sanity check that the complex parsing here works. |
b7449926 XL |
69 | let mut saw_target_arch = false; |
70 | let mut saw_cfg_bang = false; | |
2b03887a | 71 | walk(path, &mut filter_dirs, &mut |entry, contents| { |
dc9dc135 | 72 | let file = entry.path(); |
c30ab7b3 | 73 | let filestr = file.to_string_lossy().replace("\\", "/"); |
dfeec247 XL |
74 | if !filestr.ends_with(".rs") { |
75 | return; | |
76 | } | |
c30ab7b3 SL |
77 | |
78 | let is_exception_path = EXCEPTION_PATHS.iter().any(|s| filestr.contains(&**s)); | |
dfeec247 XL |
79 | if is_exception_path { |
80 | return; | |
81 | } | |
c30ab7b3 | 82 | |
17df50a5 XL |
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 | ||
dc9dc135 | 88 | check_cfgs(contents, &file, bad, &mut saw_target_arch, &mut saw_cfg_bang); |
c30ab7b3 SL |
89 | }); |
90 | ||
b7449926 XL |
91 | assert!(saw_target_arch); |
92 | assert!(saw_cfg_bang); | |
c30ab7b3 SL |
93 | } |
94 | ||
dfeec247 XL |
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 | ) { | |
9fa01778 | 102 | // Pull out all `cfg(...)` and `cfg!(...)` strings. |
c30ab7b3 SL |
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!(), | |
dfeec247 | 113 | Err(i) => i + 1, |
c30ab7b3 | 114 | }; |
cc61c64b | 115 | tidy_error!(bad, "{}:{}: platform-specific cfg: {}", file.display(), line, cfg); |
c30ab7b3 SL |
116 | }; |
117 | ||
b7449926 | 118 | for (idx, cfg) in cfgs { |
9fa01778 | 119 | // Sanity check that the parsing here works. |
dfeec247 XL |
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 | } | |
c30ab7b3 | 126 | |
dfeec247 | 127 | let contains_platform_specific_cfg = cfg.contains("target_os") |
c30ab7b3 | 128 | || cfg.contains("target_env") |
136023e0 | 129 | || cfg.contains("target_abi") |
c30ab7b3 SL |
130 | || cfg.contains("target_vendor") |
131 | || cfg.contains("unix") | |
132 | || cfg.contains("windows"); | |
133 | ||
dfeec247 XL |
134 | if !contains_platform_specific_cfg { |
135 | continue; | |
136 | } | |
c30ab7b3 | 137 | |
5e7ed085 | 138 | let preceded_by_doc_comment = { |
c30ab7b3 SL |
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 | ||
5e7ed085 | 149 | if preceded_by_doc_comment { |
dfeec247 XL |
150 | continue; |
151 | } | |
c30ab7b3 | 152 | |
17df50a5 XL |
153 | // exclude tests as some platforms do not support all tests |
154 | if cfg.contains("test") { | |
155 | continue; | |
c30ab7b3 | 156 | } |
17df50a5 XL |
157 | |
158 | err(idx, cfg); | |
c30ab7b3 SL |
159 | } |
160 | } | |
161 | ||
17df50a5 | 162 | fn parse_cfgs(contents: &str) -> Vec<(usize, &str)> { |
c30ab7b3 SL |
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 | |
9fa01778 | 166 | // that appear to be tokens followed by a parenthesis. |
c30ab7b3 | 167 | let cfgs = candidate_cfg_idxs.filter(|i| { |
17df50a5 | 168 | let pre_idx = i.saturating_sub(1); |
dfeec247 XL |
169 | let succeeds_non_ident = !contents |
170 | .as_bytes() | |
171 | .get(pre_idx) | |
c30ab7b3 SL |
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); | |
dfeec247 XL |
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); | |
c30ab7b3 SL |
185 | |
186 | succeeds_non_ident && preceeds_whitespace_and_paren | |
187 | }); | |
188 | ||
dc9dc135 | 189 | cfgs.flat_map(|i| { |
c30ab7b3 SL |
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 { | |
dc9dc135 | 200 | return Some((i, &contents_from[..=j])); |
c30ab7b3 SL |
201 | } |
202 | } | |
dfeec247 | 203 | _ => {} |
c30ab7b3 SL |
204 | } |
205 | } | |
206 | ||
dc9dc135 XL |
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 | |
dfeec247 XL |
210 | }) |
211 | .collect() | |
c30ab7b3 | 212 | } |