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