]> git.proxmox.com Git - rustc.git/blob - src/tools/tidy/src/pal.rs
New upstream version 1.14.0+dfsg1
[rustc.git] / src / tools / tidy / src / pal.rs
1 // Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 //! Tidy check to enforce rules about platform-specific code in std
12 //!
13 //! This is intended to maintain existing standards of code
14 //! organization in hopes that the standard library will continue to
15 //! be refactored to isolate platform-specific bits, making porting
16 //! easier; where "standard library" roughly means "all the
17 //! dependencies of the std and test crates".
18 //!
19 //! This generally means placing restrictions on where `cfg(unix)`,
20 //! `cfg(windows)`, `cfg(target_os)` and `cfg(target_env)` may appear,
21 //! the basic objective being to isolate platform-specific code to the
22 //! platform-specific `std::sys` modules, and to the allocation,
23 //! unwinding, and libc crates.
24 //!
25 //! Following are the basic rules, though there are currently
26 //! exceptions:
27 //!
28 //! - core may not have platform-specific code
29 //! - liballoc_system may have platform-specific code
30 //! - liballoc_jemalloc may have platform-specific code
31 //! - libpanic_abort may have platform-specific code
32 //! - libpanic_unwind may have platform-specific code
33 //! - libunwind may have platform-specific code
34 //! - other crates in the std facade may not
35 //! - std may have platform-specific code in the following places
36 //! - sys/unix/
37 //! - sys/windows/
38 //! - os/
39 //!
40 //! `std/sys_common` should _not_ contain platform-specific code.
41 //! Finally, because std contains tests with platform-specific
42 //! `ignore` attributes, once the parser encounters `mod tests`,
43 //! platform-specific cfgs are allowed. Not sure yet how to deal with
44 //! this in the long term.
45
46 use std::fs::File;
47 use std::io::Read;
48 use std::path::Path;
49 use std::iter::Iterator;
50
51 // Paths that may contain platform-specific code
52 const EXCEPTION_PATHS: &'static [&'static str] = &[
53 // std crates
54 "src/liballoc_jemalloc",
55 "src/liballoc_system",
56 "src/liblibc",
57 "src/libpanic_abort",
58 "src/libpanic_unwind",
59 "src/libunwind",
60 "src/libstd/sys/", // Platform-specific code for std lives here.
61 // This has the trailing slash so that sys_common is not excepted.
62 "src/libstd/os", // Platform-specific public interfaces
63 "src/rtstartup", // Not sure what to do about this. magic stuff for mingw
64
65 // temporary exceptions
66 "src/libstd/rtdeps.rs", // Until rustbuild replaces make
67 "src/libstd/path.rs",
68 "src/libstd/f32.rs",
69 "src/libstd/f64.rs",
70 "src/libstd/sys_common/mod.rs",
71 "src/libstd/sys_common/net.rs",
72 "src/libterm", // Not sure how to make this crate portable, but test needs it
73 "src/libtest", // Probably should defer to unstable std::sys APIs
74
75 // std testing crates, ok for now at least
76 "src/libcoretest",
77
78 // non-std crates
79 "src/test",
80 "src/tools",
81 "src/librustc",
82 "src/librustdoc",
83 "src/libsyntax",
84 "src/bootstrap",
85 ];
86
87 pub fn check(path: &Path, bad: &mut bool) {
88 let ref mut contents = String::new();
89 // Sanity check that the complex parsing here works
90 let ref mut saw_target_arch = false;
91 let ref mut saw_cfg_bang = false;
92 super::walk(path, &mut super::filter_dirs, &mut |file| {
93 let filestr = file.to_string_lossy().replace("\\", "/");
94 if !filestr.ends_with(".rs") { return }
95
96 let is_exception_path = EXCEPTION_PATHS.iter().any(|s| filestr.contains(&**s));
97 if is_exception_path { return }
98
99 check_cfgs(contents, &file, bad, saw_target_arch, saw_cfg_bang);
100 });
101
102 assert!(*saw_target_arch);
103 assert!(*saw_cfg_bang);
104 }
105
106 fn check_cfgs(contents: &mut String, file: &Path,
107 bad: &mut bool, saw_target_arch: &mut bool, saw_cfg_bang: &mut bool) {
108 contents.truncate(0);
109 t!(t!(File::open(file), file).read_to_string(contents));
110
111 // For now it's ok to have platform-specific code after 'mod tests'.
112 let mod_tests_idx = find_test_mod(contents);
113 let contents = &contents[..mod_tests_idx];
114 // Pull out all "cfg(...)" and "cfg!(...)" strings
115 let cfgs = parse_cfgs(contents);
116
117 let mut line_numbers: Option<Vec<usize>> = None;
118 let mut err = |idx: usize, cfg: &str| {
119 if line_numbers.is_none() {
120 line_numbers = Some(contents.match_indices('\n').map(|(i, _)| i).collect());
121 }
122 let line_numbers = line_numbers.as_ref().expect("");
123 let line = match line_numbers.binary_search(&idx) {
124 Ok(_) => unreachable!(),
125 Err(i) => i + 1
126 };
127 println!("{}:{}: platform-specific cfg: {}", file.display(), line, cfg);
128 *bad = true;
129 };
130
131 for (idx, cfg) in cfgs.into_iter() {
132 // Sanity check that the parsing here works
133 if !*saw_target_arch && cfg.contains("target_arch") { *saw_target_arch = true }
134 if !*saw_cfg_bang && cfg.contains("cfg!") { *saw_cfg_bang = true }
135
136 let contains_platform_specific_cfg =
137 cfg.contains("target_os")
138 || cfg.contains("target_env")
139 || cfg.contains("target_vendor")
140 || cfg.contains("unix")
141 || cfg.contains("windows");
142
143 if !contains_platform_specific_cfg { continue }
144
145 let preceeded_by_doc_comment = {
146 let pre_contents = &contents[..idx];
147 let pre_newline = pre_contents.rfind('\n');
148 let pre_doc_comment = pre_contents.rfind("///");
149 match (pre_newline, pre_doc_comment) {
150 (Some(n), Some(c)) => n < c,
151 (None, Some(_)) => true,
152 (_, None) => false,
153 }
154 };
155
156 if preceeded_by_doc_comment { continue }
157
158 err(idx, cfg);
159 }
160 }
161
162 fn find_test_mod(contents: &str) -> usize {
163 if let Some(mod_tests_idx) = contents.find("mod tests") {
164 // Also capture a previos line indicating "mod tests" in cfg-ed out
165 let prev_newline_idx = contents[..mod_tests_idx].rfind('\n').unwrap_or(mod_tests_idx);
166 let prev_newline_idx = contents[..prev_newline_idx].rfind('\n');
167 if let Some(nl) = prev_newline_idx {
168 let prev_line = &contents[nl + 1 .. mod_tests_idx];
169 let emcc_cfg = "cfg(all(test, not(target_os";
170 if prev_line.contains(emcc_cfg) {
171 nl
172 } else {
173 mod_tests_idx
174 }
175 } else {
176 mod_tests_idx
177 }
178 } else {
179 contents.len()
180 }
181 }
182
183 fn parse_cfgs<'a>(contents: &'a str) -> Vec<(usize, &'a str)> {
184 let candidate_cfgs = contents.match_indices("cfg");
185 let candidate_cfg_idxs = candidate_cfgs.map(|(i, _)| i);
186 // This is puling out the indexes of all "cfg" strings
187 // that appear to be tokens succeeded by a paren.
188 let cfgs = candidate_cfg_idxs.filter(|i| {
189 let pre_idx = i.saturating_sub(*i);
190 let succeeds_non_ident = !contents.as_bytes().get(pre_idx)
191 .cloned()
192 .map(char::from)
193 .map(char::is_alphanumeric)
194 .unwrap_or(false);
195 let contents_after = &contents[*i..];
196 let first_paren = contents_after.find('(');
197 let paren_idx = first_paren.map(|ip| i + ip);
198 let preceeds_whitespace_and_paren = paren_idx.map(|ip| {
199 let maybe_space = &contents[*i + "cfg".len() .. ip];
200 maybe_space.chars().all(|c| char::is_whitespace(c) || c == '!')
201 }).unwrap_or(false);
202
203 succeeds_non_ident && preceeds_whitespace_and_paren
204 });
205
206 cfgs.map(|i| {
207 let mut depth = 0;
208 let contents_from = &contents[i..];
209 for (j, byte) in contents_from.bytes().enumerate() {
210 match byte {
211 b'(' => {
212 depth += 1;
213 }
214 b')' => {
215 depth -= 1;
216 if depth == 0 {
217 return (i, &contents_from[.. j + 1]);
218 }
219 }
220 _ => { }
221 }
222 }
223
224 unreachable!()
225 }).collect()
226 }