]> git.lizzy.rs Git - rust.git/blob - src/tools/tidy/src/pal.rs
SGX target: fix std unit tests
[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/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::fs::File;
35 use std::io::Read;
36 use std::path::Path;
37 use std::iter::Iterator;
38
39 // Paths that may contain platform-specific code.
40 const EXCEPTION_PATHS: &[&str] = &[
41     // std crates
42     "src/libpanic_abort",
43     "src/libpanic_unwind",
44     "src/libunwind",
45     "src/libstd/sys/", // Platform-specific code for std lives here.
46                        // This has the trailing slash so that sys_common is not excepted.
47     "src/libstd/os", // Platform-specific public interfaces
48     "src/rtstartup", // Not sure what to do about this. magic stuff for mingw
49
50     // temporary exceptions
51     "src/libstd/lib.rs",
52     "src/libstd/path.rs",
53     "src/libstd/f32.rs",
54     "src/libstd/f64.rs",
55     // Integration test for platform-specific run-time feature detection:
56     "src/libstd/tests/run-time-detect.rs" ,
57     "src/libstd/net/test.rs",
58     "src/libstd/sys_common/mod.rs",
59     "src/libstd/sys_common/net.rs",
60     "src/libterm", // Not sure how to make this crate portable, but test crate needs it.
61     "src/libtest", // Probably should defer to unstable `std::sys` APIs.
62     "src/libstd/sync/mpsc", // some tests are only run on non-emscripten
63
64     // std testing crates, okay for now at least
65     "src/libcore/tests",
66     "src/liballoc/tests/lib.rs",
67
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
75     // non-std crates
76     "src/test",
77     "src/tools",
78     "src/librustc",
79     "src/librustdoc",
80     "src/libsyntax",
81     "src/bootstrap",
82 ];
83
84 pub fn check(path: &Path, bad: &mut bool) {
85     let mut contents = String::new();
86     // Sanity check that the complex parsing here works.
87     let mut saw_target_arch = false;
88     let mut saw_cfg_bang = false;
89     super::walk(path, &mut super::filter_dirs, &mut |file| {
90         let filestr = file.to_string_lossy().replace("\\", "/");
91         if !filestr.ends_with(".rs") { return }
92
93         let is_exception_path = EXCEPTION_PATHS.iter().any(|s| filestr.contains(&**s));
94         if is_exception_path { return }
95
96         check_cfgs(&mut contents, &file, bad, &mut saw_target_arch, &mut saw_cfg_bang);
97     });
98
99     assert!(saw_target_arch);
100     assert!(saw_cfg_bang);
101 }
102
103 fn check_cfgs(contents: &mut String, file: &Path,
104               bad: &mut bool, saw_target_arch: &mut bool, saw_cfg_bang: &mut bool) {
105     contents.truncate(0);
106     t!(t!(File::open(file), file).read_to_string(contents));
107
108     // For now it's ok to have platform-specific code after 'mod tests'.
109     let mod_tests_idx = find_test_mod(contents);
110     let contents = &contents[..mod_tests_idx];
111     // Pull out all `cfg(...)` and `cfg!(...)` strings.
112     let cfgs = parse_cfgs(contents);
113
114     let mut line_numbers: Option<Vec<usize>> = None;
115     let mut err = |idx: usize, cfg: &str| {
116         if line_numbers.is_none() {
117             line_numbers = Some(contents.match_indices('\n').map(|(i, _)| i).collect());
118         }
119         let line_numbers = line_numbers.as_ref().expect("");
120         let line = match line_numbers.binary_search(&idx) {
121             Ok(_) => unreachable!(),
122             Err(i) => i + 1
123         };
124         tidy_error!(bad, "{}:{}: platform-specific cfg: {}", file.display(), line, cfg);
125     };
126
127     for (idx, cfg) in cfgs {
128         // Sanity check that the parsing here works.
129         if !*saw_target_arch && cfg.contains("target_arch") { *saw_target_arch = true }
130         if !*saw_cfg_bang && cfg.contains("cfg!") { *saw_cfg_bang = true }
131
132         let contains_platform_specific_cfg =
133             cfg.contains("target_os")
134             || cfg.contains("target_env")
135             || cfg.contains("target_vendor")
136             || cfg.contains("unix")
137             || cfg.contains("windows");
138
139         if !contains_platform_specific_cfg { continue }
140
141         let preceeded_by_doc_comment = {
142             let pre_contents = &contents[..idx];
143             let pre_newline = pre_contents.rfind('\n');
144             let pre_doc_comment = pre_contents.rfind("///");
145             match (pre_newline, pre_doc_comment) {
146                 (Some(n), Some(c)) => n < c,
147                 (None, Some(_)) => true,
148                 (_, None) => false,
149             }
150         };
151
152         if preceeded_by_doc_comment { continue }
153
154         err(idx, cfg);
155     }
156 }
157
158 fn find_test_mod(contents: &str) -> usize {
159     if let Some(mod_tests_idx) = contents.find("mod tests") {
160         // Also capture a previous line indicating that "mod tests" is cfg'd out.
161         let prev_newline_idx = contents[..mod_tests_idx].rfind('\n').unwrap_or(mod_tests_idx);
162         let prev_newline_idx = contents[..prev_newline_idx].rfind('\n');
163         if let Some(nl) = prev_newline_idx {
164             let prev_line = &contents[nl + 1 .. mod_tests_idx];
165             if prev_line.contains("cfg(all(test, not(target_os")
166                 || prev_line.contains("cfg(all(test, not(any(target_os") {
167                 nl
168             } else {
169                 mod_tests_idx
170             }
171         } else {
172             mod_tests_idx
173         }
174     } else {
175         contents.len()
176     }
177 }
178
179 fn parse_cfgs<'a>(contents: &'a str) -> Vec<(usize, &'a str)> {
180     let candidate_cfgs = contents.match_indices("cfg");
181     let candidate_cfg_idxs = candidate_cfgs.map(|(i, _)| i);
182     // This is puling out the indexes of all "cfg" strings
183     // that appear to be tokens followed by a parenthesis.
184     let cfgs = candidate_cfg_idxs.filter(|i| {
185         let pre_idx = i.saturating_sub(*i);
186         let succeeds_non_ident = !contents.as_bytes().get(pre_idx)
187             .cloned()
188             .map(char::from)
189             .map(char::is_alphanumeric)
190             .unwrap_or(false);
191         let contents_after = &contents[*i..];
192         let first_paren = contents_after.find('(');
193         let paren_idx = first_paren.map(|ip| i + ip);
194         let preceeds_whitespace_and_paren = paren_idx.map(|ip| {
195             let maybe_space = &contents[*i + "cfg".len() .. ip];
196             maybe_space.chars().all(|c| char::is_whitespace(c) || c == '!')
197         }).unwrap_or(false);
198
199         succeeds_non_ident && preceeds_whitespace_and_paren
200     });
201
202     cfgs.map(|i| {
203         let mut depth = 0;
204         let contents_from = &contents[i..];
205         for (j, byte) in contents_from.bytes().enumerate() {
206             match byte {
207                 b'(' => {
208                     depth += 1;
209                 }
210                 b')' => {
211                     depth -= 1;
212                     if depth == 0 {
213                         return (i, &contents_from[..=j]);
214                     }
215                 }
216                 _ => { }
217             }
218         }
219
220         unreachable!()
221     }).collect()
222 }