https://github.com/j39m created https://github.com/llvm/llvm-project/pull/170822
This is a follow-up to pull 148323. It mints `-fsanitize-ignore-for-ubsan-feature=...`, accepting a list of (UBSan) sanitizers that should not cause `__has_feature(undefined_behavior_sanitizer)` to evaluate true. >From 30462f19f6ba2bf53eb6440dff018c40282bf3fd Mon Sep 17 00:00:00 2001 From: Kalvin Lee <[email protected]> Date: Fri, 5 Dec 2025 17:37:29 +0900 Subject: [PATCH] Add fine-grained `__has_feature()` cutout This is a follow-up to pull 148323. It mints `-fsanitize-ignore-for-ubsan-feature=...`, accepting a list of (UBSan) sanitizers that should not cause `__has_feature(undefined_behavior_sanitizer)` to evaluate true. --- .../clang/Basic/DiagnosticDriverKinds.td | 1 + clang/include/clang/Basic/Features.def | 2 +- clang/include/clang/Basic/LangOptions.h | 3 ++ clang/include/clang/Driver/SanitizerArgs.h | 1 + clang/include/clang/Options/Options.td | 8 +++++ clang/lib/Driver/SanitizerArgs.cpp | 29 ++++++++++++++++++ clang/lib/Frontend/CompilerInvocation.cpp | 15 ++++++++++ ...s_feature_undefined_behavior_sanitizer.cpp | 30 +++++++++++++++++++ 8 files changed, 88 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td index aeffe96e806bd..9e5ca9f76397b 100644 --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -277,6 +277,7 @@ def err_drv_malformed_sanitizer_metadata_ignorelist : Error< "malformed sanitizer metadata ignorelist: '%0'">; def err_drv_unsupported_static_sanitizer_darwin : Error< "static %0 runtime is not supported on darwin">; +def err_drv_not_a_ubsan_sanitizer : Error<"not a UBSan sanitizer: '%0'">; def err_drv_duplicate_config : Error< "no more than one option '--config' is allowed">; def err_drv_cannot_open_config_file : Error< diff --git a/clang/include/clang/Basic/Features.def b/clang/include/clang/Basic/Features.def index 0e91b42a132c1..52dd3a72392ee 100644 --- a/clang/include/clang/Basic/Features.def +++ b/clang/include/clang/Basic/Features.def @@ -53,7 +53,7 @@ FEATURE(memtag_globals, LangOpts.Sanitize.has(SanitizerKind::MemtagGlobals)) FEATURE(xray_instrument, LangOpts.XRayInstrument) FEATURE(undefined_behavior_sanitizer, - LangOpts.Sanitize.hasOneOf(SanitizerKind::Undefined)) + LangOpts.Sanitize.hasOneOf(SanitizerKind::Undefined & ~LangOpts.UBSanFeatureSuppressedSanitize.Mask)) FEATURE(undefined_behavior_sanitizer_finegrained_feature_checks, true) // These are all part of undefined_behavior_sanitizer: FEATURE(alignment_sanitizer, diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h index 3f042f8ddb5a1..66fe01e877cb1 100644 --- a/clang/include/clang/Basic/LangOptions.h +++ b/clang/include/clang/Basic/LangOptions.h @@ -441,6 +441,9 @@ class LangOptions : public LangOptionsBase { SanitizerSet Sanitize; /// Is at least one coverage instrumentation type enabled. bool SanitizeCoverage = false; + /// Set of enabled (undefined behavior) sanitizers that do not cause + /// `__has_feature(undefined_behavior_sanitizer)` to evaluate true. + SanitizerSet UBSanFeatureSuppressedSanitize; /// Paths to files specifying which objects /// (files, functions, variables) should not be instrumented. diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h index 84fb66e16bee3..aa8aa9be287c6 100644 --- a/clang/include/clang/Driver/SanitizerArgs.h +++ b/clang/include/clang/Driver/SanitizerArgs.h @@ -28,6 +28,7 @@ class SanitizerArgs { SanitizerSet MergeHandlers; SanitizerMaskCutoffs SkipHotCutoffs; SanitizerSet AnnotateDebugInfo; + SanitizerSet SuppressUBSanFeature; std::vector<std::string> UserIgnorelistFiles; std::vector<std::string> SystemIgnorelistFiles; diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index c6841937c8d39..84243ad80f2a1 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -2404,6 +2404,14 @@ def fsanitize_EQ : CommaJoined<["-"], "fsanitize=">, Group<f_clang_Group>, "or suspicious behavior. See user manual for available checks">; def fno_sanitize_EQ : CommaJoined<["-"], "fno-sanitize=">, Group<f_clang_Group>, Visibility<[ClangOption, CLOption]>; +def fsanitize_ignore_for_ubsan_feature_EQ + : CommaJoined<["-"], "fsanitize-ignore-for-ubsan-feature=">, + Group<f_clang_Group>, + MetaVarName<"<check>">, + HelpText< + "Prevents `__has_feature(undefined_behavior_sanitizer)` from " + "evaluating true for " + "these UBSan checks. See user manual for available UBSan checks">; def fsanitize_ignorelist_EQ : Joined<["-"], "fsanitize-ignorelist=">, Group<f_clang_Group>, HelpText<"Path to ignorelist file for sanitizers">; diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp index be068b2381d06..08db579626437 100644 --- a/clang/lib/Driver/SanitizerArgs.cpp +++ b/clang/lib/Driver/SanitizerArgs.cpp @@ -408,6 +408,8 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, // unused-argument diagnostics. SanitizerMask DiagnosedKinds; // All Kinds we have diagnosed up to now. // Used to deduplicate diagnostics. + SanitizerMask IgnoreForUbsanFeature; // Accumulated set of values passed to + // `-fsanitize-ignore-for-ubsan-feature`. SanitizerMask Kinds; const SanitizerMask Supported = setGroupBits(TC.getSupportedSanitizers()); @@ -612,6 +614,11 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, Arg->claim(); SanitizerMask Remove = parseArgValues(D, Arg, DiagnoseErrors); AllRemove |= expandSanitizerGroups(Remove); + } else if (Arg->getOption().matches( + options::OPT_fsanitize_ignore_for_ubsan_feature_EQ)) { + Arg->claim(); + SanitizerMask Suppress = parseArgValues(D, Arg, DiagnoseErrors); + IgnoreForUbsanFeature |= expandSanitizerGroups(Suppress); } } @@ -736,6 +743,22 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, // -f(-no)sanitize=leak should change whether leak detection is enabled by // default in ASan? + // Error if a non-UBSan sanitizer is passed to + // `-fsanitize-ignore-for-ubsan-feature=`. + // + // `shift` is a `SANITIZER_GROUP()`, and so is expanded into its constituents + // by `expandSanitizerGroups()` above, though the physical bit is not included + // in `SanitizerKind::Undefined`. + const SanitizerMask not_ubsan_mask = + IgnoreForUbsanFeature & + ~(SanitizerKind::Undefined | SanitizerKind::ShiftGroup); + if (not_ubsan_mask && DiagnoseErrors) { + SanitizerSet not_ubsan; + not_ubsan.set(not_ubsan_mask); + D.Diag(clang::diag::err_drv_not_a_ubsan_sanitizer) << toString(not_ubsan); + } + IgnoreForUbsanFeature &= SanitizerKind::Undefined; + // Parse -f(no-)?sanitize-recover flags. SanitizerMask RecoverableKinds = parseSanitizeArgs( D, Args, DiagnoseErrors, RecoverableByDefault, AlwaysRecoverable, @@ -1212,6 +1235,7 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC, MergeHandlers.Mask |= MergeKinds; AnnotateDebugInfo.Mask |= AnnotateDebugInfoKinds; + SuppressUBSanFeature.Mask |= IgnoreForUbsanFeature; // Zero out SkipHotCutoffs for unused sanitizers SkipHotCutoffs.clear(~Sanitizers.Mask); @@ -1392,6 +1416,11 @@ void SanitizerArgs::addArgs(const ToolChain &TC, const llvm::opt::ArgList &Args, return; CmdArgs.push_back(Args.MakeArgString("-fsanitize=" + toString(Sanitizers))); + if (!SuppressUBSanFeature.empty()) + CmdArgs.push_back( + Args.MakeArgString("-fsanitize-ignore-for-ubsan-feature=" + + toString(SuppressUBSanFeature))); + if (!RecoverableSanitizers.empty()) CmdArgs.push_back(Args.MakeArgString("-fsanitize-recover=" + toString(RecoverableSanitizers))); diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index b42c263abd2c7..66dd3bc458efe 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -3693,6 +3693,10 @@ void CompilerInvocationBase::GenerateLangArgs(const LangOptions &Opts, GenerateArg(Consumer, OPT_pic_is_pie); for (StringRef Sanitizer : serializeSanitizerKinds(Opts.Sanitize)) GenerateArg(Consumer, OPT_fsanitize_EQ, Sanitizer); + for (StringRef Sanitizer : + serializeSanitizerKinds(Opts.UBSanFeatureSuppressedSanitize)) + GenerateArg(Consumer, OPT_fsanitize_ignore_for_ubsan_feature_EQ, + Sanitizer); return; } @@ -3892,6 +3896,9 @@ void CompilerInvocationBase::GenerateLangArgs(const LangOptions &Opts, for (StringRef Sanitizer : serializeSanitizerKinds(Opts.Sanitize)) GenerateArg(Consumer, OPT_fsanitize_EQ, Sanitizer); + for (StringRef Sanitizer : + serializeSanitizerKinds(Opts.UBSanFeatureSuppressedSanitize)) + GenerateArg(Consumer, OPT_fsanitize_ignore_for_ubsan_feature_EQ, Sanitizer); // Conflating '-fsanitize-system-ignorelist' and '-fsanitize-ignorelist'. for (const std::string &F : Opts.NoSanitizeFiles) @@ -3973,6 +3980,10 @@ bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args, Opts.PIE = Args.hasArg(OPT_pic_is_pie); parseSanitizerKinds("-fsanitize=", Args.getAllArgValues(OPT_fsanitize_EQ), Diags, Opts.Sanitize); + parseSanitizerKinds( + "-fsanitize-ignore-for-ubsan-feature=", + Args.getAllArgValues(OPT_fsanitize_ignore_for_ubsan_feature_EQ), Diags, + Opts.UBSanFeatureSuppressedSanitize); return Diags.getNumErrors() == NumErrorsBefore; } @@ -4390,6 +4401,10 @@ bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args, // Parse -fsanitize= arguments. parseSanitizerKinds("-fsanitize=", Args.getAllArgValues(OPT_fsanitize_EQ), Diags, Opts.Sanitize); + parseSanitizerKinds( + "-fsanitize-ignore-for-ubsan-feature=", + Args.getAllArgValues(OPT_fsanitize_ignore_for_ubsan_feature_EQ), Diags, + Opts.UBSanFeatureSuppressedSanitize); Opts.NoSanitizeFiles = Args.getAllArgValues(OPT_fsanitize_ignorelist_EQ); std::vector<std::string> systemIgnorelists = Args.getAllArgValues(OPT_fsanitize_system_ignorelist_EQ); diff --git a/clang/test/Lexer/has_feature_undefined_behavior_sanitizer.cpp b/clang/test/Lexer/has_feature_undefined_behavior_sanitizer.cpp index e1a07d215b549..de4dd1f1cfebc 100644 --- a/clang/test/Lexer/has_feature_undefined_behavior_sanitizer.cpp +++ b/clang/test/Lexer/has_feature_undefined_behavior_sanitizer.cpp @@ -23,6 +23,36 @@ // RUN: %clang -E %s -o - | FileCheck --check-prefix=CHECK-NO-UBSAN %s +// Specifying a specific sanitizer under UBSan and immediately suppressing +// `__has_feature(undefined_behavior_sanitizer)` for the same should result in +// "no-UBSan." +// BUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=undefined -fsanitize-ignore-for-ubsan-feature=undefined %s -o - | FileCheck --check-prefix=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=alignment -fsanitize-ignore-for-ubsan-feature=alignment %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=bool -fsanitize-ignore-for-ubsan-feature=bool %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=builtin -fsanitize-ignore-for-ubsan-feature=builtin %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=array-bounds -fsanitize-ignore-for-ubsan-feature=array-bounds %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=enum -fsanitize-ignore-for-ubsan-feature=enum %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=float-cast-overflow -fsanitize-ignore-for-ubsan-feature=float-cast-overflow %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=integer-divide-by-zero -fsanitize-ignore-for-ubsan-feature=integer-divide-by-zero %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=nonnull-attribute -fsanitize-ignore-for-ubsan-feature=nonnull-attribute %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=null -fsanitize-ignore-for-ubsan-feature=null %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// object-size is a no-op at O0. +// RUN: %clang -E -target x86_64-unknown-linux-gnu -O2 -fsanitize=object-size -fsanitize-ignore-for-ubsan-feature=object-size %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=pointer-overflow -fsanitize-ignore-for-ubsan-feature=pointer-overflow %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=return -fsanitize-ignore-for-ubsan-feature=return %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=returns-nonnull-attribute -fsanitize-ignore-for-ubsan-feature=returns-nonnull-attribute %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=shift-base -fsanitize-ignore-for-ubsan-feature=shift-base %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=shift-exponent -fsanitize-ignore-for-ubsan-feature=shift-exponent %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=shift -fsanitize-ignore-for-ubsan-feature=shift %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=signed-integer-overflow -fsanitize-ignore-for-ubsan-feature=signed-integer-overflow %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=unreachable -fsanitize-ignore-for-ubsan-feature=unreachable %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=vla-bound -fsanitize-ignore-for-ubsan-feature=vla-bound %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=function -fsanitize-ignore-for-ubsan-feature=function %s -o - | FileCheck --check-prefixes=CHECK-NO-UBSAN %s + +// Spot check: suppressing an unrelated sanitizer should still result in a "has +// UBSan" configuration. +// RUN: %clang -E -target x86_64-unknown-linux-gnu -fsanitize=function -fsanitize-ignore-for-ubsan-feature=alignment %s -o - | FileCheck --check-prefixes=CHECK-UBSAN,CHECK-FUNCTION %s + // REQUIRES: x86-registered-target #if !__has_feature(undefined_behavior_sanitizer_finegrained_feature_checks) _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
