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