compiletest/header/
needs.rs

1use crate::common::{Config, KNOWN_TARGET_HAS_ATOMIC_WIDTHS, Sanitizer};
2use crate::header::{IgnoreDecision, llvm_has_libzstd};
3
4pub(super) fn handle_needs(
5    cache: &CachedNeedsConditions,
6    config: &Config,
7    ln: &str,
8) -> IgnoreDecision {
9    // Note thet we intentionally still put the needs- prefix here to make the file show up when
10    // grepping for a directive name, even though we could technically strip that.
11    let needs = &[
12        Need {
13            name: "needs-asm-support",
14            condition: config.has_asm_support(),
15            ignore_reason: "ignored on targets without inline assembly support",
16        },
17        Need {
18            name: "needs-sanitizer-support",
19            condition: cache.sanitizer_support,
20            ignore_reason: "ignored on targets without sanitizers support",
21        },
22        Need {
23            name: "needs-sanitizer-address",
24            condition: cache.sanitizer_address,
25            ignore_reason: "ignored on targets without address sanitizer",
26        },
27        Need {
28            name: "needs-sanitizer-cfi",
29            condition: cache.sanitizer_cfi,
30            ignore_reason: "ignored on targets without CFI sanitizer",
31        },
32        Need {
33            name: "needs-sanitizer-dataflow",
34            condition: cache.sanitizer_dataflow,
35            ignore_reason: "ignored on targets without dataflow sanitizer",
36        },
37        Need {
38            name: "needs-sanitizer-kcfi",
39            condition: cache.sanitizer_kcfi,
40            ignore_reason: "ignored on targets without kernel CFI sanitizer",
41        },
42        Need {
43            name: "needs-sanitizer-kasan",
44            condition: cache.sanitizer_kasan,
45            ignore_reason: "ignored on targets without kernel address sanitizer",
46        },
47        Need {
48            name: "needs-sanitizer-leak",
49            condition: cache.sanitizer_leak,
50            ignore_reason: "ignored on targets without leak sanitizer",
51        },
52        Need {
53            name: "needs-sanitizer-memory",
54            condition: cache.sanitizer_memory,
55            ignore_reason: "ignored on targets without memory sanitizer",
56        },
57        Need {
58            name: "needs-sanitizer-thread",
59            condition: cache.sanitizer_thread,
60            ignore_reason: "ignored on targets without thread sanitizer",
61        },
62        Need {
63            name: "needs-sanitizer-hwaddress",
64            condition: cache.sanitizer_hwaddress,
65            ignore_reason: "ignored on targets without hardware-assisted address sanitizer",
66        },
67        Need {
68            name: "needs-sanitizer-memtag",
69            condition: cache.sanitizer_memtag,
70            ignore_reason: "ignored on targets without memory tagging sanitizer",
71        },
72        Need {
73            name: "needs-sanitizer-shadow-call-stack",
74            condition: cache.sanitizer_shadow_call_stack,
75            ignore_reason: "ignored on targets without shadow call stacks",
76        },
77        Need {
78            name: "needs-sanitizer-safestack",
79            condition: cache.sanitizer_safestack,
80            ignore_reason: "ignored on targets without SafeStack support",
81        },
82        Need {
83            name: "needs-enzyme",
84            condition: config.has_enzyme,
85            ignore_reason: "ignored when LLVM Enzyme is disabled",
86        },
87        Need {
88            name: "needs-run-enabled",
89            condition: config.run_enabled(),
90            ignore_reason: "ignored when running the resulting test binaries is disabled",
91        },
92        Need {
93            name: "needs-threads",
94            condition: config.has_threads(),
95            ignore_reason: "ignored on targets without threading support",
96        },
97        Need {
98            name: "needs-subprocess",
99            condition: config.has_subprocess_support(),
100            ignore_reason: "ignored on targets without subprocess support",
101        },
102        Need {
103            name: "needs-unwind",
104            condition: config.can_unwind(),
105            ignore_reason: "ignored on targets without unwinding support",
106        },
107        Need {
108            name: "needs-profiler-runtime",
109            condition: config.profiler_runtime,
110            ignore_reason: "ignored when the profiler runtime is not available",
111        },
112        Need {
113            name: "needs-force-clang-based-tests",
114            condition: config.run_clang_based_tests_with.is_some(),
115            ignore_reason: "ignored when RUSTBUILD_FORCE_CLANG_BASED_TESTS is not set",
116        },
117        Need {
118            name: "needs-xray",
119            condition: cache.xray,
120            ignore_reason: "ignored on targets without xray tracing",
121        },
122        Need {
123            name: "needs-rust-lld",
124            condition: cache.rust_lld,
125            ignore_reason: "ignored on targets without Rust's LLD",
126        },
127        Need {
128            name: "needs-dlltool",
129            condition: cache.dlltool,
130            ignore_reason: "ignored when dlltool for the current architecture is not present",
131        },
132        Need {
133            name: "needs-git-hash",
134            condition: config.git_hash,
135            ignore_reason: "ignored when git hashes have been omitted for building",
136        },
137        Need {
138            name: "needs-dynamic-linking",
139            condition: config.target_cfg().dynamic_linking,
140            ignore_reason: "ignored on targets without dynamic linking",
141        },
142        Need {
143            name: "needs-relocation-model-pic",
144            condition: config.target_cfg().relocation_model == "pic",
145            ignore_reason: "ignored on targets without PIC relocation model",
146        },
147        Need {
148            name: "needs-deterministic-layouts",
149            condition: !config.rust_randomized_layout,
150            ignore_reason: "ignored when randomizing layouts",
151        },
152        Need {
153            name: "needs-wasmtime",
154            condition: config.runner.as_ref().is_some_and(|r| r.contains("wasmtime")),
155            ignore_reason: "ignored when wasmtime runner is not available",
156        },
157        Need {
158            name: "needs-symlink",
159            condition: cache.symlinks,
160            ignore_reason: "ignored if symlinks are unavailable",
161        },
162        Need {
163            name: "needs-llvm-zstd",
164            condition: cache.llvm_zstd,
165            ignore_reason: "ignored if LLVM wasn't build with zstd for ELF section compression",
166        },
167        Need {
168            name: "needs-rustc-debug-assertions",
169            condition: config.with_rustc_debug_assertions,
170            ignore_reason: "ignored if rustc wasn't built with debug assertions",
171        },
172        Need {
173            name: "needs-std-debug-assertions",
174            condition: config.with_std_debug_assertions,
175            ignore_reason: "ignored if std wasn't built with debug assertions",
176        },
177    ];
178
179    let (name, rest) = match ln.split_once([':', ' ']) {
180        Some((name, rest)) => (name, Some(rest)),
181        None => (ln, None),
182    };
183
184    // FIXME(jieyouxu): tighten up this parsing to reject using both `:` and ` ` as means to
185    // delineate value.
186    if name == "needs-target-has-atomic" {
187        let Some(rest) = rest else {
188            return IgnoreDecision::Error {
189                message: "expected `needs-target-has-atomic` to have a comma-separated list of atomic widths".to_string(),
190            };
191        };
192
193        // Expect directive value to be a list of comma-separated atomic widths.
194        let specified_widths = rest
195            .split(',')
196            .map(|width| width.trim())
197            .map(ToString::to_string)
198            .collect::<Vec<String>>();
199
200        for width in &specified_widths {
201            if !KNOWN_TARGET_HAS_ATOMIC_WIDTHS.contains(&width.as_str()) {
202                return IgnoreDecision::Error {
203                    message: format!(
204                        "unknown width specified in `needs-target-has-atomic`: `{width}` is not a \
205                        known `target_has_atomic_width`, known values are `{:?}`",
206                        KNOWN_TARGET_HAS_ATOMIC_WIDTHS
207                    ),
208                };
209            }
210        }
211
212        let satisfies_all_specified_widths = specified_widths
213            .iter()
214            .all(|specified| config.target_cfg().target_has_atomic.contains(specified));
215        if satisfies_all_specified_widths {
216            return IgnoreDecision::Continue;
217        } else {
218            return IgnoreDecision::Ignore {
219                reason: format!(
220                    "skipping test as target does not support all of the required `target_has_atomic` widths `{:?}`",
221                    specified_widths
222                ),
223            };
224        }
225    }
226
227    if !name.starts_with("needs-") {
228        return IgnoreDecision::Continue;
229    }
230
231    // Handled elsewhere.
232    if name == "needs-llvm-components" {
233        return IgnoreDecision::Continue;
234    }
235
236    let mut found_valid = false;
237    for need in needs {
238        if need.name == name {
239            if need.condition {
240                found_valid = true;
241                break;
242            } else {
243                return IgnoreDecision::Ignore {
244                    reason: if let Some(comment) = rest {
245                        format!("{} ({})", need.ignore_reason, comment.trim())
246                    } else {
247                        need.ignore_reason.into()
248                    },
249                };
250            }
251        }
252    }
253
254    if found_valid {
255        IgnoreDecision::Continue
256    } else {
257        IgnoreDecision::Error { message: format!("invalid needs directive: {name}") }
258    }
259}
260
261struct Need {
262    name: &'static str,
263    condition: bool,
264    ignore_reason: &'static str,
265}
266
267pub(super) struct CachedNeedsConditions {
268    sanitizer_support: bool,
269    sanitizer_address: bool,
270    sanitizer_cfi: bool,
271    sanitizer_dataflow: bool,
272    sanitizer_kcfi: bool,
273    sanitizer_kasan: bool,
274    sanitizer_leak: bool,
275    sanitizer_memory: bool,
276    sanitizer_thread: bool,
277    sanitizer_hwaddress: bool,
278    sanitizer_memtag: bool,
279    sanitizer_shadow_call_stack: bool,
280    sanitizer_safestack: bool,
281    xray: bool,
282    rust_lld: bool,
283    dlltool: bool,
284    symlinks: bool,
285    /// Whether LLVM built with zstd, for the `needs-llvm-zstd` directive.
286    llvm_zstd: bool,
287}
288
289impl CachedNeedsConditions {
290    pub(super) fn load(config: &Config) -> Self {
291        let target = &&*config.target;
292        let sanitizers = &config.target_cfg().sanitizers;
293        Self {
294            sanitizer_support: std::env::var_os("RUSTC_SANITIZER_SUPPORT").is_some(),
295            sanitizer_address: sanitizers.contains(&Sanitizer::Address),
296            sanitizer_cfi: sanitizers.contains(&Sanitizer::Cfi),
297            sanitizer_dataflow: sanitizers.contains(&Sanitizer::Dataflow),
298            sanitizer_kcfi: sanitizers.contains(&Sanitizer::Kcfi),
299            sanitizer_kasan: sanitizers.contains(&Sanitizer::KernelAddress),
300            sanitizer_leak: sanitizers.contains(&Sanitizer::Leak),
301            sanitizer_memory: sanitizers.contains(&Sanitizer::Memory),
302            sanitizer_thread: sanitizers.contains(&Sanitizer::Thread),
303            sanitizer_hwaddress: sanitizers.contains(&Sanitizer::Hwaddress),
304            sanitizer_memtag: sanitizers.contains(&Sanitizer::Memtag),
305            sanitizer_shadow_call_stack: sanitizers.contains(&Sanitizer::ShadowCallStack),
306            sanitizer_safestack: sanitizers.contains(&Sanitizer::Safestack),
307            xray: config.target_cfg().xray,
308
309            // For tests using the `needs-rust-lld` directive (e.g. for `-Clink-self-contained=+linker`),
310            // we need to find whether `rust-lld` is present in the compiler under test.
311            //
312            // The --compile-lib-path is the path to host shared libraries, but depends on the OS. For
313            // example:
314            // - on linux, it can be <sysroot>/lib
315            // - on windows, it can be <sysroot>/bin
316            //
317            // However, `rust-lld` is only located under the lib path, so we look for it there.
318            rust_lld: config
319                .compile_lib_path
320                .parent()
321                .expect("couldn't traverse to the parent of the specified --compile-lib-path")
322                .join("lib")
323                .join("rustlib")
324                .join(target)
325                .join("bin")
326                .join(if config.host.contains("windows") { "rust-lld.exe" } else { "rust-lld" })
327                .exists(),
328
329            llvm_zstd: llvm_has_libzstd(&config),
330            dlltool: find_dlltool(&config),
331            symlinks: has_symlinks(),
332        }
333    }
334}
335
336fn find_dlltool(config: &Config) -> bool {
337    let path = std::env::var_os("PATH").expect("missing PATH environment variable");
338    let path = std::env::split_paths(&path).collect::<Vec<_>>();
339
340    // dlltool is used ony by GNU based `*-*-windows-gnu`
341    if !(config.matches_os("windows") && config.matches_env("gnu") && config.matches_abi("")) {
342        return false;
343    }
344
345    // On Windows, dlltool.exe is used for all architectures.
346    // For non-Windows, there are architecture specific dlltool binaries.
347    let dlltool_found = if cfg!(windows) {
348        path.iter().any(|dir| dir.join("dlltool.exe").is_file())
349    } else if config.matches_arch("i686") {
350        path.iter().any(|dir| dir.join("i686-w64-mingw32-dlltool").is_file())
351    } else if config.matches_arch("x86_64") {
352        path.iter().any(|dir| dir.join("x86_64-w64-mingw32-dlltool").is_file())
353    } else {
354        false
355    };
356    dlltool_found
357}
358
359// FIXME(#135928): this is actually not quite right because this detection is run on the **host**.
360// This however still helps the case of windows -> windows local development in case symlinks are
361// not available.
362#[cfg(windows)]
363fn has_symlinks() -> bool {
364    if std::env::var_os("CI").is_some() {
365        return true;
366    }
367    let link = std::env::temp_dir().join("RUST_COMPILETEST_SYMLINK_CHECK");
368    if std::os::windows::fs::symlink_file("DOES NOT EXIST", &link).is_ok() {
369        std::fs::remove_file(&link).unwrap();
370        true
371    } else {
372        false
373    }
374}
375
376#[cfg(not(windows))]
377fn has_symlinks() -> bool {
378    true
379}