1use std::path::Path;
33
34use crate::diagnostics::{CheckId, RunningCheck, TidyCtx};
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/stdio.rs",
62 "library/std/src/lib.rs", "library/std/src/path.rs",
64 "library/std/src/io/error.rs", ];
66
67pub fn check(library_path: &Path, tidy_ctx: TidyCtx) {
68 let mut check = tidy_ctx.start_check(CheckId::new("pal").path(library_path));
69
70 let root_path = library_path.parent().unwrap();
71 assert!(root_path.join("x.py").is_file());
73
74 let mut saw_target_arch = false;
76 let mut saw_cfg_bang = false;
77 walk(library_path, |path, _is_dir| filter_dirs(path), &mut |entry, contents| {
78 let file = entry.path();
79 let file = file.strip_prefix(root_path).unwrap();
81 let filestr = file.to_string_lossy().replace("\\", "/");
82 if !filestr.ends_with(".rs") {
83 return;
84 }
85
86 let is_exception_path = EXCEPTION_PATHS.iter().any(|s| filestr.contains(&**s));
87 if is_exception_path {
88 return;
89 }
90
91 if filestr.contains("tests") || filestr.contains("benches") {
93 return;
94 }
95
96 check_cfgs(contents, file, &mut check, &mut saw_target_arch, &mut saw_cfg_bang);
97 });
98
99 assert!(saw_target_arch);
100 assert!(saw_cfg_bang);
101}
102
103fn check_cfgs(
104 contents: &str,
105 file: &Path,
106 check: &mut RunningCheck,
107 saw_target_arch: &mut bool,
108 saw_cfg_bang: &mut bool,
109) {
110 let cfgs = parse_cfgs(contents);
112
113 let mut line_numbers: Option<Vec<usize>> = None;
114 let mut err = |idx: usize, cfg: &str| {
115 if line_numbers.is_none() {
116 line_numbers = Some(contents.match_indices('\n').map(|(i, _)| i).collect());
117 }
118 let line_numbers = line_numbers.as_ref().expect("");
119 let line = match line_numbers.binary_search(&idx) {
120 Ok(_) => unreachable!(),
121 Err(i) => i + 1,
122 };
123 check.error(format!("{}:{line}: platform-specific cfg: {cfg}", file.display()));
124 };
125
126 for (idx, cfg) in cfgs {
127 if !*saw_target_arch && cfg.contains("target_arch") {
129 *saw_target_arch = true
130 }
131 if !*saw_cfg_bang && cfg.contains("cfg!") {
132 *saw_cfg_bang = true
133 }
134
135 let contains_platform_specific_cfg = cfg.contains("target_os")
136 || cfg.contains("target_env")
137 || cfg.contains("target_abi")
138 || cfg.contains("target_vendor")
139 || cfg.contains("target_family")
140 || cfg.contains("unix")
141 || cfg.contains("windows");
142
143 if !contains_platform_specific_cfg {
144 continue;
145 }
146
147 let preceded_by_doc_comment = {
148 let pre_contents = &contents[..idx];
149 let pre_newline = pre_contents.rfind('\n');
150 let pre_doc_comment = pre_contents.rfind("///");
151 match (pre_newline, pre_doc_comment) {
152 (Some(n), Some(c)) => n < c,
153 (None, Some(_)) => true,
154 (_, None) => false,
155 }
156 };
157
158 if preceded_by_doc_comment {
159 continue;
160 }
161
162 if cfg.contains("test") {
164 continue;
165 }
166
167 err(idx, cfg);
168 }
169}
170
171fn parse_cfgs(contents: &str) -> Vec<(usize, &str)> {
172 let candidate_cfgs = contents.match_indices("cfg");
173 let candidate_cfg_idxs = candidate_cfgs.map(|(i, _)| i);
174 let cfgs = candidate_cfg_idxs.filter(|i| {
177 let pre_idx = i.saturating_sub(1);
178 let succeeds_non_ident = !contents
179 .as_bytes()
180 .get(pre_idx)
181 .cloned()
182 .map(char::from)
183 .map(char::is_alphanumeric)
184 .unwrap_or(false);
185 let contents_after = &contents[*i..];
186 let first_paren = contents_after.find('(');
187 let paren_idx = first_paren.map(|ip| i + ip);
188 let preceeds_whitespace_and_paren = paren_idx
189 .map(|ip| {
190 let maybe_space = &contents[*i + "cfg".len()..ip];
191 maybe_space.chars().all(|c| char::is_whitespace(c) || c == '!')
192 })
193 .unwrap_or(false);
194
195 succeeds_non_ident && preceeds_whitespace_and_paren
196 });
197
198 cfgs.flat_map(|i| {
199 let mut depth = 0;
200 let contents_from = &contents[i..];
201 for (j, byte) in contents_from.bytes().enumerate() {
202 match byte {
203 b'(' => {
204 depth += 1;
205 }
206 b')' => {
207 depth -= 1;
208 if depth == 0 {
209 return Some((i, &contents_from[..=j]));
210 }
211 }
212 _ => {}
213 }
214 }
215
216 None
219 })
220 .collect()
221}