[clang-tools-extra] [clang-tidy] Add AssumedNonThrowingFunctions option to bugprone-exception-escape (PR #193127)
Zeyi Xu via cfe-commits
cfe-commits at lists.llvm.org
Mon Apr 20 19:06:47 PDT 2026
https://github.com/zeyi2 created https://github.com/llvm/llvm-project/pull/193127
None
>From a504e9baf8d0cc3f76b696fd9f3b155001ff2a57 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Tue, 21 Apr 2026 10:06:30 +0800
Subject: [PATCH] [clang-tidy] Add AssumedNonThrowingFunctions option to
bugprone-exception-escape
---
.../bugprone/ExceptionEscapeCheck.cpp | 12 ++++-
.../bugprone/ExceptionEscapeCheck.h | 2 +
.../clang-tidy/utils/ExceptionAnalyzer.cpp | 4 +-
.../clang-tidy/utils/ExceptionAnalyzer.h | 4 ++
clang-tools-extra/docs/ReleaseNotes.rst | 14 +++--
.../checks/bugprone/exception-escape.rst | 18 +++++--
...-escape-assumed-non-throwing-functions.cpp | 53 +++++++++++++++++++
7 files changed, 97 insertions(+), 10 deletions(-)
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-assumed-non-throwing-functions.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
index 7d23e2d2b5630..82e59611c00b9 100644
--- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
@@ -58,6 +58,8 @@ ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context), RawFunctionsThatShouldNotThrow(Options.get(
"FunctionsThatShouldNotThrow", "")),
+ RawAssumedNonThrowingFunctions(
+ Options.get("AssumedNonThrowingFunctions", "")),
RawIgnoredExceptions(Options.get("IgnoredExceptions", "")),
RawCheckedSwapFunctions(
Options.get("CheckedSwapFunctions", "swap,iter_swap,iter_move")),
@@ -69,11 +71,16 @@ ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name,
Options.get("TreatFunctionsWithoutSpecificationAsThrowing",
TreatFunctionsWithoutSpecification::None)) {
SmallVector<StringRef, 8> FunctionsThatShouldNotThrowVec,
- IgnoredExceptionsVec, CheckedSwapFunctionsVec;
+ AssumedNonThrowingFunctionsVec, IgnoredExceptionsVec,
+ CheckedSwapFunctionsVec;
RawFunctionsThatShouldNotThrow.split(FunctionsThatShouldNotThrowVec, ",", -1,
false);
FunctionsThatShouldNotThrow.insert_range(FunctionsThatShouldNotThrowVec);
+ RawAssumedNonThrowingFunctions.split(AssumedNonThrowingFunctionsVec, ",", -1,
+ false);
+ AssumedNonThrowingFunctions.insert_range(AssumedNonThrowingFunctionsVec);
+
RawCheckedSwapFunctions.split(CheckedSwapFunctionsVec, ",", -1, false);
CheckedSwapFunctions.insert_range(CheckedSwapFunctionsVec);
@@ -81,6 +88,7 @@ ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name,
RawIgnoredExceptions.split(IgnoredExceptionsVec, ",", -1, false);
IgnoredExceptions.insert_range(IgnoredExceptionsVec);
Tracer.ignoreExceptions(std::move(IgnoredExceptions));
+ Tracer.assumeNonThrowingFunctions(std::move(AssumedNonThrowingFunctions));
Tracer.ignoreBadAlloc(true);
Tracer.assumeMissingDefinitionsFunctionsAsThrowing(
@@ -95,6 +103,8 @@ ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name,
void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "FunctionsThatShouldNotThrow",
RawFunctionsThatShouldNotThrow);
+ Options.store(Opts, "AssumedNonThrowingFunctions",
+ RawAssumedNonThrowingFunctions);
Options.store(Opts, "IgnoredExceptions", RawIgnoredExceptions);
Options.store(Opts, "CheckedSwapFunctions", RawCheckedSwapFunctions);
Options.store(Opts, "CheckDestructors", CheckDestructors);
diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h
index 7c970aa57e2ef..cf61a94664148 100644
--- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h
+++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h
@@ -40,6 +40,7 @@ class ExceptionEscapeCheck : public ClangTidyCheck {
private:
StringRef RawFunctionsThatShouldNotThrow;
+ StringRef RawAssumedNonThrowingFunctions;
StringRef RawIgnoredExceptions;
StringRef RawCheckedSwapFunctions;
@@ -52,6 +53,7 @@ class ExceptionEscapeCheck : public ClangTidyCheck {
TreatFunctionsWithoutSpecificationAsThrowing;
llvm::StringSet<> FunctionsThatShouldNotThrow;
+ llvm::StringSet<> AssumedNonThrowingFunctions;
llvm::StringSet<> CheckedSwapFunctions;
utils::ExceptionAnalyzer Tracer;
};
diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
index 8f0d6b1360cd5..cffb0f4089032 100644
--- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
+++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
@@ -475,7 +475,9 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
const FunctionDecl *Func, const ExceptionInfo::Throwables &Caught,
CallStack &CallStack, SourceLocation CallLoc) {
if (!Func || CallStack.contains(Func) ||
- (!CallStack.empty() && !canThrow(Func)))
+ (!CallStack.empty() &&
+ (AssumedNonThrowingFunctions.contains(Func->getNameAsString()) ||
+ !canThrow(Func))))
return ExceptionInfo::createNonThrowing();
if (const Stmt *Body = Func->getBody()) {
diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
index 08479ef58240a..edb0775ead3ef 100644
--- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
+++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
@@ -154,6 +154,9 @@ class ExceptionAnalyzer {
void ignoreExceptions(llvm::StringSet<> ExceptionNames) {
IgnoredExceptions = std::move(ExceptionNames);
}
+ void assumeNonThrowingFunctions(llvm::StringSet<> FunctionNames) {
+ AssumedNonThrowingFunctions = std::move(FunctionNames);
+ }
ExceptionInfo analyze(const FunctionDecl *Func);
ExceptionInfo analyze(const Stmt *Stmt);
@@ -172,6 +175,7 @@ class ExceptionAnalyzer {
bool IgnoreBadAlloc = true;
llvm::StringSet<> IgnoredExceptions;
+ llvm::StringSet<> AssumedNonThrowingFunctions;
llvm::DenseMap<const FunctionDecl *, ExceptionInfo> FunctionCache{32U};
bool AssumeUnannotatedFunctionsAsThrowing = false;
bool AssumeMissingDefinitionsFunctionsAsThrowing = false;
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 3d6235cb3a78c..952945e932e48 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -242,10 +242,16 @@ Changes in existing checks
correctly ignoring function templates.
- Improved :doc:`bugprone-exception-escape
- <clang-tidy/checks/bugprone/exception-escape>` check by adding
- `TreatFunctionsWithoutSpecificationAsThrowing` option to support reporting
- for unannotated functions, enabling reporting when no explicit ``throw``
- is seen and allowing separate tuning for known and unknown implementations.
+ <clang-tidy/checks/bugprone/exception-escape>` check:
+
+ - Added ``TreatFunctionsWithoutSpecificationAsThrowing`` to support
+ reporting for unannotated functions, enabling reporting when no explicit
+ ``throw`` is seen and allowing separate tuning for known and unknown
+ implementations.
+
+ - Added ``AssumedNonThrowingFunctions`` to suppress diagnostics for
+ user-specified functions that should be treated as non-throwing during
+ exception analysis.
- Improved :doc:`bugprone-fold-init-type
<clang-tidy/checks/bugprone/fold-init-type>` check by detecting precision
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/exception-escape.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/exception-escape.rst
index 0a0c21f62c7a0..755a942055477 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/exception-escape.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/exception-escape.rst
@@ -65,10 +65,20 @@ Options
.. option:: FunctionsThatShouldNotThrow
- Comma separated list containing function names which should not throw. An
- example value for this parameter can be ``WinMain`` which adds function
- ``WinMain()`` in the Windows API to the list of the functions which should
- not throw. Default value is an empty string.
+ Comma separated list containing function names which should not throw. These
+ names add functions to the set of declarations diagnosed by this check if
+ they may throw. An example value for this parameter can be ``WinMain`` which
+ adds function ``WinMain()`` in the Windows API to the list of functions that
+ are checked to not throw. Default value is an empty string.
+
+.. option:: AssumedNonThrowingFunctions
+
+ Comma separated list containing function names which are assumed not to
+ throw when analyzing calls. Unlike ``FunctionsThatShouldNotThrow``, this
+ does not add functions to the set of declarations diagnosed by the check.
+ Instead, it suppresses warnings caused by calls to functions without visible
+ definitions or explicit exception specifications that are known not to
+ throw, such as ``fclose``. Default value is an empty string.
.. option:: IgnoredExceptions
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-assumed-non-throwing-functions.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-assumed-non-throwing-functions.cpp
new file mode 100644
index 0000000000000..0903ac1e1ff1f
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-assumed-non-throwing-functions.cpp
@@ -0,0 +1,53 @@
+// RUN: %check_clang_tidy -check-suffixes=DEFAULT -std=c++11-or-later %s bugprone-exception-escape %t -- \
+// RUN: -config='{"CheckOptions": { \
+// RUN: "bugprone-exception-escape.TreatFunctionsWithoutSpecificationAsThrowing": "OnlyUndefined" \
+// RUN: }}' -- -fexceptions
+// RUN: %check_clang_tidy -check-suffixes=WHITELIST -std=c++11-or-later %s bugprone-exception-escape %t -- \
+// RUN: -config='{"CheckOptions": { \
+// RUN: "bugprone-exception-escape.TreatFunctionsWithoutSpecificationAsThrowing": "OnlyUndefined", \
+// RUN: "bugprone-exception-escape.AssumedNonThrowingFunctions": "fclose" \
+// RUN: }}' -- -fexceptions
+// RUN: %check_clang_tidy -check-suffixes=OVERLAP -std=c++11-or-later %s bugprone-exception-escape %t -- \
+// RUN: -config='{"CheckOptions": { \
+// RUN: "bugprone-exception-escape.FunctionsThatShouldNotThrow": "cleanup", \
+// RUN: "bugprone-exception-escape.AssumedNonThrowingFunctions": "cleanup" \
+// RUN: }}' -- -fexceptions
+
+struct FILE;
+
+extern int fclose(FILE *);
+extern int other_close(FILE *);
+
+struct FileCloser {
+ FileCloser(FILE *File) : File(File) {}
+
+ ~FileCloser() {
+ // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:3: warning: an exception may be thrown in function '~FileCloser' which should not throw exceptions
+ // CHECK-MESSAGES-DEFAULT: :[[@LINE-8]]:12: note: frame #0: an exception of unknown type may be thrown in function 'fclose' here
+ // CHECK-MESSAGES-DEFAULT: :[[@LINE+1]]:5: note: frame #1: function '~FileCloser' calls function 'fclose' here
+ fclose(File);
+ }
+
+ FILE *File;
+};
+
+struct OtherCloser {
+ OtherCloser(FILE *File) : File(File) {}
+
+ ~OtherCloser() {
+ // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:3: warning: an exception may be thrown in function '~OtherCloser' which should not throw exceptions
+ // CHECK-MESSAGES-DEFAULT: :[[@LINE-20]]:12: note: frame #0: an exception of unknown type may be thrown in function 'other_close' here
+ // CHECK-MESSAGES-DEFAULT: :[[@LINE+4]]:5: note: frame #1: function '~OtherCloser' calls function 'other_close' here
+ // CHECK-MESSAGES-WHITELIST: :[[@LINE-4]]:3: warning: an exception may be thrown in function '~OtherCloser' which should not throw exceptions
+ // CHECK-MESSAGES-WHITELIST: :[[@LINE-23]]:12: note: frame #0: an exception of unknown type may be thrown in function 'other_close' here
+ // CHECK-MESSAGES-WHITELIST: :[[@LINE+1]]:5: note: frame #1: function '~OtherCloser' calls function 'other_close' here
+ other_close(File);
+ }
+
+ FILE *File;
+};
+
+void cleanup() {
+ // CHECK-MESSAGES-OVERLAP: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'cleanup' which should not throw exceptions
+ throw 1;
+}
More information about the cfe-commits
mailing list