1 //! Tidy check to enforce rules about platform-specific code in std.
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".
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.
15 //! Following are the basic rules, though there are currently
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:
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.
34 use std
::iter
::Iterator
;
37 // Paths that may contain platform-specific code.
38 const EXCEPTION_PATHS
: &[&str] = &[
41 "src/libpanic_unwind",
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
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
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.
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") {
94 let is_exception_path
= EXCEPTION_PATHS
.iter().any(|s
| filestr
.contains(&**s
));
95 if is_exception_path
{
99 check_cfgs(contents
, &file
, bad
, &mut saw_target_arch
, &mut saw_cfg_bang
);
102 assert
!(saw_target_arch
);
103 assert
!(saw_cfg_bang
);
110 saw_target_arch
: &mut bool
,
111 saw_cfg_bang
: &mut bool
,
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
);
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());
124 let line_numbers
= line_numbers
.as_ref().expect("");
125 let line
= match line_numbers
.binary_search(&idx
) {
126 Ok(_
) => unreachable
!(),
129 tidy_error
!(bad
, "{}:{}: platform-specific cfg: {}", file
.display(), line
, cfg
);
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
137 if !*saw_cfg_bang
&& cfg
.contains("cfg!") {
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");
147 if !contains_platform_specific_cfg
{
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,
162 if preceeded_by_doc_comment
{
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")
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
204 .map(char::is_alphanumeric
)
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
211 let maybe_space
= &contents
[*i
+ "cfg".len()..ip
];
212 maybe_space
.chars().all(|c
| char::is_whitespace(c
) || c
== '
!'
)
216 succeeds_non_ident
&& preceeds_whitespace_and_paren
221 let contents_from
= &contents
[i
..];
222 for (j
, byte
) in contents_from
.bytes().enumerate() {
230 return Some((i
, &contents_from
[..=j
]));
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