[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