[clang] Add fine-grained `__has_feature()` cutout (PR #170822)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Dec 5 01:19:24 PST 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Kalvin (j39m)
<details>
<summary>Changes</summary>
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.
---
Full diff: https://github.com/llvm/llvm-project/pull/170822.diff
8 Files Affected:
- (modified) clang/include/clang/Basic/DiagnosticDriverKinds.td (+1)
- (modified) clang/include/clang/Basic/Features.def (+1-1)
- (modified) clang/include/clang/Basic/LangOptions.h (+3)
- (modified) clang/include/clang/Driver/SanitizerArgs.h (+1)
- (modified) clang/include/clang/Options/Options.td (+8)
- (modified) clang/lib/Driver/SanitizerArgs.cpp (+29)
- (modified) clang/lib/Frontend/CompilerInvocation.cpp (+15)
- (modified) clang/test/Lexer/has_feature_undefined_behavior_sanitizer.cpp (+30)
``````````diff
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)
``````````
</details>
https://github.com/llvm/llvm-project/pull/170822
More information about the cfe-commits
mailing list