[clang] 0316441 - Add fine-grained `__has_feature()` cutout (#170822)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Jan 13 09:09:12 PST 2026
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 <kdlee at chromium.org>
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)
More information about the cfe-commits
mailing list