Author: Kalvin
Date: 2026-01-13T17:09:06Z
New Revision: 0316441d5ca6cb4bab3dc4e27a3794992eb5cbf5

URL: 
https://github.com/llvm/llvm-project/commit/0316441d5ca6cb4bab3dc4e27a3794992eb5cbf5
DIFF: 
https://github.com/llvm/llvm-project/commit/0316441d5ca6cb4bab3dc4e27a3794992eb5cbf5.diff

LOG: Add fine-grained `__has_feature()` cutout (#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.

---------

Co-authored-by: Kalvin Lee <[email protected]>

Added: 
    

Modified: 
    clang/include/clang/Basic/Features.def
    clang/include/clang/Basic/LangOptions.h
    clang/include/clang/Driver/SanitizerArgs.h
    clang/include/clang/Options/Options.td
    clang/lib/Driver/SanitizerArgs.cpp
    clang/lib/Frontend/CompilerInvocation.cpp
    clang/test/Lexer/has_feature_undefined_behavior_sanitizer.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/Features.def 
b/clang/include/clang/Basic/Features.def
index 0e91b42a132c1..ea99a1d5dbbbf 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.UBSanFeatureIgnoredSanitize.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 4fa2dcffc75b6..ebd0436fa154b 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -458,6 +458,9 @@ class LangOptions : public LangOptionsBase {
   SanitizerSet Sanitize;
   /// Is at least one coverage instrumentation type enabled.
   bool SanitizeCoverage = false;
+  /// Set of (UBSan) sanitizers that when enabled do not cause
+  /// `__has_feature(undefined_behavior_sanitizer)` to evaluate true.
+  SanitizerSet UBSanFeatureIgnoredSanitize;
 
   /// 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 d48ca15864060..2f57a5b13b917 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -2421,6 +2421,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..7c51d66645198 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();
+      IgnoreForUbsanFeature |=
+          expandSanitizerGroups(parseArgValues(D, Arg, DiagnoseErrors));
     }
   }
 
@@ -1212,6 +1219,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 +1400,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)));
@@ -1615,7 +1628,9 @@ SanitizerMask parseArgValues(const Driver &D, const 
llvm::opt::Arg *A,
        A->getOption().matches(options::OPT_fno_sanitize_merge_handlers_EQ) ||
        A->getOption().matches(options::OPT_fsanitize_annotate_debug_info_EQ) ||
        A->getOption().matches(
-           options::OPT_fno_sanitize_annotate_debug_info_EQ)) &&
+           options::OPT_fno_sanitize_annotate_debug_info_EQ) ||
+       A->getOption().matches(
+           options::OPT_fsanitize_ignore_for_ubsan_feature_EQ)) &&
       "Invalid argument in parseArgValues!");
   SanitizerMask Kinds;
   for (int i = 0, n = A->getNumValues(); i != n; ++i) {

diff  --git a/clang/lib/Frontend/CompilerInvocation.cpp 
b/clang/lib/Frontend/CompilerInvocation.cpp
index 477406f2526c0..ab14661e06e11 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.UBSanFeatureIgnoredSanitize))
+      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.UBSanFeatureIgnoredSanitize))
+    GenerateArg(Consumer, OPT_fsanitize_ignore_for_ubsan_feature_EQ, 
Sanitizer);
 
   // Conflating '-fsanitize-system-ignorelist' and '-fsanitize-ignorelist'.
   for (const std::string &F : Opts.NoSanitizeFiles)
@@ -3982,6 +3989,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.UBSanFeatureIgnoredSanitize);
 
     return Diags.getNumErrors() == NumErrorsBefore;
   }
@@ -4399,6 +4410,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.UBSanFeatureIgnoredSanitize);
   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..8e668745a5844 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."
+// RUN: %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

Reply via email to