From aea43673f2efce50501e55c5186805cebc3deb4a Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Fri, 14 Apr 2023 11:36:09 +0200 Subject: [PATCH] refactor needs, validate them, and add ignore reasons --- src/tools/compiletest/src/header.rs | 128 +++--------- src/tools/compiletest/src/header/needs.rs | 226 ++++++++++++++++++++++ 2 files changed, 250 insertions(+), 104 deletions(-) create mode 100644 src/tools/compiletest/src/header/needs.rs diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs index bc90c413cfd..7613508077f 100644 --- a/src/tools/compiletest/src/header.rs +++ b/src/tools/compiletest/src/header.rs @@ -11,10 +11,10 @@ use crate::common::{Config, Debugger, FailMode, Mode, PassMode}; use crate::header::cfg::parse_cfg_name_directive; use crate::header::cfg::MatchOutcome; -use crate::util; use crate::{extract_cdb_version, extract_gdb_version}; mod cfg; +mod needs; #[cfg(test)] mod tests; @@ -660,14 +660,6 @@ fn parse_custom_normalization(&self, mut line: &str, prefix: &str) -> Option<(St } } - fn parse_needs_matching_clang(&self, line: &str) -> bool { - self.parse_name_directive(line, "needs-matching-clang") - } - - fn parse_needs_profiler_support(&self, line: &str) -> bool { - self.parse_name_directive(line, "needs-profiler-support") - } - fn has_cfg_prefix(&self, line: &str, prefix: &str) -> bool { // returns whether this line contains this prefix or not. For prefix // "ignore", returns true if line says "ignore-x86_64", "ignore-arch", @@ -871,69 +863,13 @@ pub fn make_test_description( let mut ignore_message = None; let mut should_fail = false; - let rustc_has_profiler_support = env::var_os("RUSTC_PROFILER_SUPPORT").is_some(); - let rustc_has_sanitizer_support = env::var_os("RUSTC_SANITIZER_SUPPORT").is_some(); - let has_asm_support = config.has_asm_support(); - let has_asan = util::ASAN_SUPPORTED_TARGETS.contains(&&*config.target); - let has_cfi = util::CFI_SUPPORTED_TARGETS.contains(&&*config.target); - let has_kcfi = util::KCFI_SUPPORTED_TARGETS.contains(&&*config.target); - let has_kasan = util::KASAN_SUPPORTED_TARGETS.contains(&&*config.target); - let has_lsan = util::LSAN_SUPPORTED_TARGETS.contains(&&*config.target); - let has_msan = util::MSAN_SUPPORTED_TARGETS.contains(&&*config.target); - let has_tsan = util::TSAN_SUPPORTED_TARGETS.contains(&&*config.target); - let has_hwasan = util::HWASAN_SUPPORTED_TARGETS.contains(&&*config.target); - let has_memtag = util::MEMTAG_SUPPORTED_TARGETS.contains(&&*config.target); - let has_shadow_call_stack = util::SHADOWCALLSTACK_SUPPORTED_TARGETS.contains(&&*config.target); - let has_xray = util::XRAY_SUPPORTED_TARGETS.contains(&&*config.target); - - // For tests using the `needs-rust-lld` directive (e.g. for `-Zgcc-ld=lld`), we need to find - // whether `rust-lld` is present in the compiler under test. - // - // The --compile-lib-path is the path to host shared libraries, but depends on the OS. For - // example: - // - on linux, it can be /lib - // - on windows, it can be /bin - // - // However, `rust-lld` is only located under the lib path, so we look for it there. - let has_rust_lld = config - .compile_lib_path - .parent() - .expect("couldn't traverse to the parent of the specified --compile-lib-path") - .join("lib") - .join("rustlib") - .join(&config.target) - .join("bin") - .join(if config.host.contains("windows") { "rust-lld.exe" } else { "rust-lld" }) - .exists(); - - fn is_on_path(file: &'static str) -> impl Fn() -> bool { - move || env::split_paths(&env::var_os("PATH").unwrap()).any(|dir| dir.join(file).is_file()) - } - - // On Windows, dlltool.exe is used for all architectures. - #[cfg(windows)] - let (has_i686_dlltool, has_x86_64_dlltool) = - (is_on_path("dlltool.exe"), is_on_path("dlltool.exe")); - // For non-Windows, there are architecture specific dlltool binaries. - #[cfg(not(windows))] - let (has_i686_dlltool, has_x86_64_dlltool) = - (is_on_path("i686-w64-mingw32-dlltool"), is_on_path("x86_64-w64-mingw32-dlltool")); + let needs_cache = needs::CachedNeedsConditions::load(config); iter_header(path, src, &mut |revision, ln| { if revision.is_some() && revision != cfg { return; } - macro_rules! reason { - ($e:expr) => { - ignore |= match $e { - true => { - ignore_message = Some(stringify!($e)); - true - } - false => ignore, - } - }; - } + macro_rules! decision { ($e:expr) => { match $e { @@ -944,6 +880,10 @@ macro_rules! decision { // compiletest so it won't grow indefinitely. ignore_message = Some(Box::leak(Box::::from(reason))); } + IgnoreDecision::Error { message } => { + eprintln!("error: {}: {message}", path.display()); + panic!(); + } IgnoreDecision::Continue => {} } }; @@ -989,48 +929,27 @@ macro_rules! decision { }; } + decision!(needs::handle_needs(&needs_cache, config, ln)); decision!(ignore_llvm(config, ln)); decision!(ignore_cdb(config, ln)); decision!(ignore_gdb(config, ln)); decision!(ignore_lldb(config, ln)); - reason!( - config.run_clang_based_tests_with.is_none() && config.parse_needs_matching_clang(ln) - ); - reason!(!has_asm_support && config.parse_name_directive(ln, "needs-asm-support")); - reason!(!rustc_has_profiler_support && config.parse_needs_profiler_support(ln)); - reason!(!config.run_enabled() && config.parse_name_directive(ln, "needs-run-enabled")); - reason!( - !rustc_has_sanitizer_support - && config.parse_name_directive(ln, "needs-sanitizer-support") - ); - reason!(!has_asan && config.parse_name_directive(ln, "needs-sanitizer-address")); - reason!(!has_cfi && config.parse_name_directive(ln, "needs-sanitizer-cfi")); - reason!(!has_kcfi && config.parse_name_directive(ln, "needs-sanitizer-kcfi")); - reason!(!has_kasan && config.parse_name_directive(ln, "needs-sanitizer-kasan")); - reason!(!has_lsan && config.parse_name_directive(ln, "needs-sanitizer-leak")); - reason!(!has_msan && config.parse_name_directive(ln, "needs-sanitizer-memory")); - reason!(!has_tsan && config.parse_name_directive(ln, "needs-sanitizer-thread")); - reason!(!has_hwasan && config.parse_name_directive(ln, "needs-sanitizer-hwaddress")); - reason!(!has_memtag && config.parse_name_directive(ln, "needs-sanitizer-memtag")); - reason!( - !has_shadow_call_stack - && config.parse_name_directive(ln, "needs-sanitizer-shadow-call-stack") - ); - reason!(!config.can_unwind() && config.parse_name_directive(ln, "needs-unwind")); - reason!(!has_xray && config.parse_name_directive(ln, "needs-xray")); - reason!( - config.target == "wasm32-unknown-unknown" - && config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS) - ); - reason!(!has_rust_lld && config.parse_name_directive(ln, "needs-rust-lld")); - reason!(config.parse_name_directive(ln, "needs-i686-dlltool") && !has_i686_dlltool()); - reason!(config.parse_name_directive(ln, "needs-x86_64-dlltool") && !has_x86_64_dlltool()); - reason!( - config.parse_name_directive(ln, "rust-lldb") - && config.debugger == Some(Debugger::Lldb) - && !config.lldb_native_rust - ); + if config.target == "wasm32-unknown-unknown" { + if config.parse_name_directive(ln, directives::CHECK_RUN_RESULTS) { + decision!(IgnoreDecision::Ignore { + reason: "ignored when checking the run results on WASM".into(), + }); + } + } + + if config.debugger == Some(Debugger::Lldb) && !config.lldb_native_rust { + if config.parse_name_directive(ln, "rust-lldb") { + decision!(IgnoreDecision::Ignore { + reason: "ignored on targets wihtout Rust's LLDB".into() + }); + } + } should_fail |= config.parse_name_directive(ln, "should-fail"); }); @@ -1226,4 +1145,5 @@ fn ignore_llvm(config: &Config, line: &str) -> IgnoreDecision { enum IgnoreDecision { Ignore { reason: String }, Continue, + Error { message: String }, } diff --git a/src/tools/compiletest/src/header/needs.rs b/src/tools/compiletest/src/header/needs.rs new file mode 100644 index 00000000000..9a7c4b86115 --- /dev/null +++ b/src/tools/compiletest/src/header/needs.rs @@ -0,0 +1,226 @@ +use crate::common::Config; +use crate::header::IgnoreDecision; +use crate::util; + +pub(super) fn handle_needs( + cache: &CachedNeedsConditions, + config: &Config, + ln: &str, +) -> IgnoreDecision { + // Note thet we intentionally still put the needs- prefix here to make the file show up when + // grepping for a directive name, even though we could technically strip that. + let needs = &[ + Need { + name: "needs-asm-support", + condition: config.has_asm_support(), + ignore_reason: "ignored on targets without inline assembly support", + }, + Need { + name: "needs-sanitizer-support", + condition: cache.sanitizer_support, + ignore_reason: "ignored on targets without sanitizers support", + }, + Need { + name: "needs-sanitizer-address", + condition: cache.sanitizer_address, + ignore_reason: "ignored on targets without address sanitizer", + }, + Need { + name: "needs-sanitizer-cfi", + condition: cache.sanitizer_cfi, + ignore_reason: "ignored on targets without CFI sanitizer", + }, + Need { + name: "needs-sanitizer-kcfi", + condition: cache.sanitizer_kcfi, + ignore_reason: "ignored on targets without kernel CFI sanitizer", + }, + Need { + name: "needs-sanitizer-kasan", + condition: cache.sanitizer_kasan, + ignore_reason: "ignored on targets without kernel address sanitizer", + }, + Need { + name: "needs-sanitizer-leak", + condition: cache.sanitizer_leak, + ignore_reason: "ignored on targets without leak sanitizer", + }, + Need { + name: "needs-sanitizer-memory", + condition: cache.sanitizer_memory, + ignore_reason: "ignored on targets without memory sanitizer", + }, + Need { + name: "needs-sanitizer-thread", + condition: cache.sanitizer_thread, + ignore_reason: "ignored on targets without thread sanitizer", + }, + Need { + name: "needs-sanitizer-hwaddress", + condition: cache.sanitizer_hwaddress, + ignore_reason: "ignored on targets without hardware-assisted address sanitizer", + }, + Need { + name: "needs-sanitizer-memtag", + condition: cache.sanitizer_memtag, + ignore_reason: "ignored on targets without memory tagging sanitizer", + }, + Need { + name: "needs-sanitizer-shadow-call-stack", + condition: cache.sanitizer_shadow_call_stack, + ignore_reason: "ignored on targets without shadow call stacks", + }, + Need { + name: "needs-run-enabled", + condition: config.run_enabled(), + ignore_reason: "ignored when running the resulting test binaries is disabled", + }, + Need { + name: "needs-unwind", + condition: config.can_unwind(), + ignore_reason: "ignored on targets without unwinding support", + }, + Need { + name: "needs-profiler-support", + condition: std::env::var_os("RUSTC_PROFILER_SUPPORT").is_some(), + ignore_reason: "ignored when profiler support is disabled", + }, + Need { + name: "needs-matching-clang", + condition: config.run_clang_based_tests_with.is_some(), + ignore_reason: "ignored when the used clang does not match the built LLVM", + }, + Need { + name: "needs-xray", + condition: cache.xray, + ignore_reason: "ignored on targets without xray tracing", + }, + Need { + name: "needs-rust-lld", + condition: cache.rust_lld, + ignore_reason: "ignored on targets without Rust's LLD", + }, + Need { + name: "needs-i686-dlltool", + condition: cache.i686_dlltool, + ignore_reason: "ignored when dlltool for i686 is not present", + }, + Need { + name: "needs-x86_64-dlltool", + condition: cache.x86_64_dlltool, + ignore_reason: "ignored when dlltool for x86_64 is not present", + }, + ]; + + let (name, comment) = match ln.split_once([':', ' ']) { + Some((name, comment)) => (name, Some(comment)), + None => (ln, None), + }; + + if !name.starts_with("needs-") { + return IgnoreDecision::Continue; + } + + let mut found_valid = false; + for need in needs { + if need.name == name { + if need.condition { + found_valid = true; + break; + } else { + return IgnoreDecision::Ignore { + reason: if let Some(comment) = comment { + format!("{} ({comment})", need.ignore_reason) + } else { + need.ignore_reason.into() + }, + }; + } + } + } + + if found_valid { + IgnoreDecision::Continue + } else { + IgnoreDecision::Error { message: format!("invalid needs directive: {name}") } + } +} + +struct Need { + name: &'static str, + condition: bool, + ignore_reason: &'static str, +} + +pub(super) struct CachedNeedsConditions { + sanitizer_support: bool, + sanitizer_address: bool, + sanitizer_cfi: bool, + sanitizer_kcfi: bool, + sanitizer_kasan: bool, + sanitizer_leak: bool, + sanitizer_memory: bool, + sanitizer_thread: bool, + sanitizer_hwaddress: bool, + sanitizer_memtag: bool, + sanitizer_shadow_call_stack: bool, + xray: bool, + rust_lld: bool, + i686_dlltool: bool, + x86_64_dlltool: bool, +} + +impl CachedNeedsConditions { + pub(super) fn load(config: &Config) -> Self { + let path = std::env::var_os("PATH").expect("missing PATH environment variable"); + let path = std::env::split_paths(&path).collect::>(); + + let target = &&*config.target; + Self { + sanitizer_support: std::env::var_os("RUSTC_SANITIZER_SUPPORT").is_some(), + sanitizer_address: util::ASAN_SUPPORTED_TARGETS.contains(target), + sanitizer_cfi: util::CFI_SUPPORTED_TARGETS.contains(target), + sanitizer_kcfi: util::KCFI_SUPPORTED_TARGETS.contains(target), + sanitizer_kasan: util::KASAN_SUPPORTED_TARGETS.contains(target), + sanitizer_leak: util::LSAN_SUPPORTED_TARGETS.contains(target), + sanitizer_memory: util::MSAN_SUPPORTED_TARGETS.contains(target), + sanitizer_thread: util::TSAN_SUPPORTED_TARGETS.contains(target), + sanitizer_hwaddress: util::HWASAN_SUPPORTED_TARGETS.contains(target), + sanitizer_memtag: util::MEMTAG_SUPPORTED_TARGETS.contains(target), + sanitizer_shadow_call_stack: util::SHADOWCALLSTACK_SUPPORTED_TARGETS.contains(target), + xray: util::XRAY_SUPPORTED_TARGETS.contains(target), + + // For tests using the `needs-rust-lld` directive (e.g. for `-Zgcc-ld=lld`), we need to find + // whether `rust-lld` is present in the compiler under test. + // + // The --compile-lib-path is the path to host shared libraries, but depends on the OS. For + // example: + // - on linux, it can be /lib + // - on windows, it can be /bin + // + // However, `rust-lld` is only located under the lib path, so we look for it there. + rust_lld: config + .compile_lib_path + .parent() + .expect("couldn't traverse to the parent of the specified --compile-lib-path") + .join("lib") + .join("rustlib") + .join(target) + .join("bin") + .join(if config.host.contains("windows") { "rust-lld.exe" } else { "rust-lld" }) + .exists(), + + // On Windows, dlltool.exe is used for all architectures. + #[cfg(windows)] + i686_dlltool: path.iter().any(|dir| dir.join("dlltool.exe").is_file()), + #[cfg(windows)] + x86_64_dlltool: path.iter().any(|dir| dir.join("dlltool.exe").is_file()), + + // For non-Windows, there are architecture specific dlltool binaries. + #[cfg(not(windows))] + i686_dlltool: path.iter().any(|dir| dir.join("i686-w64-mingw32-dlltool").is_file()), + #[cfg(not(windows))] + x86_64_dlltool: path.iter().any(|dir| dir.join("x86_64-w64-mingw32-dlltool").is_file()), + } + } +}