1use std::path::Path;
34
35use crate::walk::{filter_dirs, walk};
36
37const EXCEPTION_PATHS: &[&str] = &[
39 "library/compiler-builtins",
40 "library/std_detect",
41 "library/windows_targets",
42 "library/panic_abort",
43 "library/panic_unwind",
44 "library/unwind",
45 "library/rtstartup", "library/test", "library/core/src/ffi/va_list.rs",
53 "library/core/src/ffi/mod.rs",
55 "library/core/src/ffi/primitives.rs",
56 "library/core/src/os", "library/std/src/sys", "library/std/src/os", "library/std/src/io/copy.rs",
62 "library/std/src/io/stdio.rs",
63 "library/std/src/lib.rs", "library/std/src/path.rs",
65 "library/std/src/sys_common", "library/std/src/net/test.rs", "library/std/src/io/error.rs", ];
69
70pub fn check(path: &Path, bad: &mut bool) {
71 let mut saw_target_arch = false;
73 let mut saw_cfg_bang = false;
74 walk(path, |path, _is_dir| filter_dirs(path), &mut |entry, contents| {
75 let file = entry.path();
76 let filestr = file.to_string_lossy().replace("\\", "/");
77 if !filestr.ends_with(".rs") {
78 return;
79 }
80
81 let is_exception_path = EXCEPTION_PATHS.iter().any(|s| filestr.contains(&**s));
82 if is_exception_path {
83 return;
84 }
85
86 if filestr.contains("tests") || filestr.contains("benches") {
88 return;
89 }
90
91 check_cfgs(contents, file, bad, &mut saw_target_arch, &mut saw_cfg_bang);
92 });
93
94 assert!(saw_target_arch);
95 assert!(saw_cfg_bang);
96}
97
98fn check_cfgs(
99 contents: &str,
100 file: &Path,
101 bad: &mut bool,
102 saw_target_arch: &mut bool,
103 saw_cfg_bang: &mut bool,
104) {
105 let cfgs = parse_cfgs(contents);
107
108 let mut line_numbers: Option<Vec<usize>> = None;
109 let mut err = |idx: usize, cfg: &str| {
110 if line_numbers.is_none() {
111 line_numbers = Some(contents.match_indices('\n').map(|(i, _)| i).collect());
112 }
113 let line_numbers = line_numbers.as_ref().expect("");
114 let line = match line_numbers.binary_search(&idx) {
115 Ok(_) => unreachable!(),
116 Err(i) => i + 1,
117 };
118 tidy_error!(bad, "{}:{}: platform-specific cfg: {}", file.display(), line, cfg);
119 };
120
121 for (idx, cfg) in cfgs {
122 if !*saw_target_arch && cfg.contains("target_arch") {
124 *saw_target_arch = true
125 }
126 if !*saw_cfg_bang && cfg.contains("cfg!") {
127 *saw_cfg_bang = true
128 }
129
130 let contains_platform_specific_cfg = cfg.contains("target_os")
131 || cfg.contains("target_env")
132 || cfg.contains("target_abi")
133 || cfg.contains("target_vendor")
134 || cfg.contains("target_family")
135 || cfg.contains("unix")
136 || cfg.contains("windows");
137
138 if !contains_platform_specific_cfg {
139 continue;
140 }
141
142 let preceded_by_doc_comment = {
143 let pre_contents = &contents[..idx];
144 let pre_newline = pre_contents.rfind('\n');
145 let pre_doc_comment = pre_contents.rfind("///");
146 match (pre_newline, pre_doc_comment) {
147 (Some(n), Some(c)) => n < c,
148 (None, Some(_)) => true,
149 (_, None) => false,
150 }
151 };
152
153 if preceded_by_doc_comment {
154 continue;
155 }
156
157 if cfg.contains("test") {
159 continue;
160 }
161
162 err(idx, cfg);
163 }
164}
165
166fn parse_cfgs(contents: &str) -> Vec<(usize, &str)> {
167 let candidate_cfgs = contents.match_indices("cfg");
168 let candidate_cfg_idxs = candidate_cfgs.map(|(i, _)| i);
169 let cfgs = candidate_cfg_idxs.filter(|i| {
172 let pre_idx = i.saturating_sub(1);
173 let succeeds_non_ident = !contents
174 .as_bytes()
175 .get(pre_idx)
176 .cloned()
177 .map(char::from)
178 .map(char::is_alphanumeric)
179 .unwrap_or(false);
180 let contents_after = &contents[*i..];
181 let first_paren = contents_after.find('(');
182 let paren_idx = first_paren.map(|ip| i + ip);
183 let preceeds_whitespace_and_paren = paren_idx
184 .map(|ip| {
185 let maybe_space = &contents[*i + "cfg".len()..ip];
186 maybe_space.chars().all(|c| char::is_whitespace(c) || c == '!')
187 })
188 .unwrap_or(false);
189
190 succeeds_non_ident && preceeds_whitespace_and_paren
191 });
192
193 cfgs.flat_map(|i| {
194 let mut depth = 0;
195 let contents_from = &contents[i..];
196 for (j, byte) in contents_from.bytes().enumerate() {
197 match byte {
198 b'(' => {
199 depth += 1;
200 }
201 b')' => {
202 depth -= 1;
203 if depth == 0 {
204 return Some((i, &contents_from[..=j]));
205 }
206 }
207 _ => {}
208 }
209 }
210
211 None
214 })
215 .collect()
216}