[clang-tools-extra] [clang-tidy] Add check 'bugprone-unsafe-to-allow-exceptions' (PR #176430)
Balázs Kéri via cfe-commits
cfe-commits at lists.llvm.org
Mon Feb 9 03:53:03 PST 2026
https://github.com/balazske updated https://github.com/llvm/llvm-project/pull/176430
>From 5f4e66967cc52652983cd387a544388c65bed727 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= <balazs.keri at ericsson.com>
Date: Fri, 16 Jan 2026 17:31:35 +0100
Subject: [PATCH 1/6] [clang-tidy] Add check
'bugprone-unsafe-to-allow-exceptions'
---
.../bugprone/BugproneTidyModule.cpp | 3 +
.../clang-tidy/bugprone/CMakeLists.txt | 1 +
.../bugprone/UnsafeToAllowExceptionsCheck.cpp | 76 +++++++++++++++++++
.../bugprone/UnsafeToAllowExceptionsCheck.h | 39 ++++++++++
.../bugprone/unsafe-to-allow-exceptions.rst | 31 ++++++++
.../docs/clang-tidy/checks/list.rst | 1 +
.../bugprone/unsafe-to-allow-exceptions.cpp | 45 +++++++++++
7 files changed, 196 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.rst
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-to-allow-exceptions.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
index 4150442c25d61..310184037afbd 100644
--- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp
@@ -106,6 +106,7 @@
#include "UnintendedCharOstreamOutputCheck.h"
#include "UniquePtrArrayMismatchCheck.h"
#include "UnsafeFunctionsCheck.h"
+#include "UnsafeToAllowExceptionsCheck.h"
#include "UnusedLocalNonTrivialVariableCheck.h"
#include "UnusedRaiiCheck.h"
#include "UnusedReturnValueCheck.h"
@@ -308,6 +309,8 @@ class BugproneModule : public ClangTidyModule {
"bugprone-crtp-constructor-accessibility");
CheckFactories.registerCheck<UnsafeFunctionsCheck>(
"bugprone-unsafe-functions");
+ CheckFactories.registerCheck<UnsafeToAllowExceptionsCheck>(
+ "bugprone-unsafe-to-allow-exceptions");
CheckFactories.registerCheck<UnusedLocalNonTrivialVariableCheck>(
"bugprone-unused-local-non-trivial-variable");
CheckFactories.registerCheck<UnusedRaiiCheck>("bugprone-unused-raii");
diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
index db1256d91d311..96ad671d03b39 100644
--- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt
@@ -108,6 +108,7 @@ add_clang_library(clangTidyBugproneModule STATIC
UnhandledSelfAssignmentCheck.cpp
UniquePtrArrayMismatchCheck.cpp
UnsafeFunctionsCheck.cpp
+ UnsafeToAllowExceptionsCheck.cpp
UnusedLocalNonTrivialVariableCheck.cpp
UnusedRaiiCheck.cpp
UnusedReturnValueCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.cpp
new file mode 100644
index 0000000000000..3f159410d6f4d
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.cpp
@@ -0,0 +1,76 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UnsafeToAllowExceptionsCheck.h"
+
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "llvm/ADT/StringSet.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::bugprone {
+namespace {
+
+AST_MATCHER_P(FunctionDecl, isEnabled, llvm::StringSet<>,
+ FunctionsThatShouldNotThrow) {
+ return FunctionsThatShouldNotThrow.contains(Node.getNameAsString());
+}
+
+AST_MATCHER(FunctionDecl, isExplicitThrow) {
+ return isExplicitThrowExceptionSpec(Node.getExceptionSpecType()) &&
+ Node.getExceptionSpecSourceRange().isValid();
+}
+
+AST_MATCHER(FunctionDecl, hasAtLeastOneParameter) {
+ return Node.getNumParams() > 0;
+}
+
+} // namespace
+
+UnsafeToAllowExceptionsCheck::UnsafeToAllowExceptionsCheck(
+ StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ RawCheckedSwapFunctions(
+ Options.get("CheckedSwapFunctions", "swap,iter_swap,iter_move")) {
+ llvm::SmallVector<StringRef, 4> CheckedSwapFunctionsVec;
+ RawCheckedSwapFunctions.split(CheckedSwapFunctionsVec, ",", -1, false);
+ CheckedSwapFunctions.insert_range(CheckedSwapFunctionsVec);
+}
+
+void UnsafeToAllowExceptionsCheck::storeOptions(
+ ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "CheckedSwapFunctions", RawCheckedSwapFunctions);
+}
+
+void UnsafeToAllowExceptionsCheck::registerMatchers(MatchFinder *Finder) {
+ Finder->addMatcher(
+ functionDecl(allOf(isDefinition(), isExplicitThrow(),
+ anyOf(cxxDestructorDecl(),
+ cxxConstructorDecl(isMoveConstructor()),
+ cxxMethodDecl(isMoveAssignmentOperator()),
+ allOf(isEnabled(CheckedSwapFunctions),
+ hasAtLeastOneParameter()),
+ isMain())))
+ .bind("f"),
+ this);
+}
+
+void UnsafeToAllowExceptionsCheck::check(
+ const MatchFinder::MatchResult &Result) {
+ const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("f");
+
+ if (!MatchedDecl)
+ return;
+
+ diag(MatchedDecl->getLocation(),
+ "function %0 should not throw exceptions but "
+ "it is still marked as throwable")
+ << MatchedDecl;
+}
+
+} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.h b/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.h
new file mode 100644
index 0000000000000..90b9d42a7e4c4
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.h
@@ -0,0 +1,39 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSAFETOALLOWEXCEPTIONSCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSAFETOALLOWEXCEPTIONSCHECK_H
+
+#include "../ClangTidyCheck.h"
+#include "llvm/ADT/StringSet.h"
+
+namespace clang::tidy::bugprone {
+
+/// Finds functions where throwing exceptions is unsafe but the function is
+/// still marked as throwable.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.html
+class UnsafeToAllowExceptionsCheck : public ClangTidyCheck {
+public:
+ UnsafeToAllowExceptionsCheck(StringRef Name, ClangTidyContext *Context);
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+ return LangOpts.CPlusPlus && LangOpts.CXXExceptions;
+ }
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+ StringRef RawCheckedSwapFunctions;
+ llvm::StringSet<> CheckedSwapFunctions;
+};
+
+} // namespace clang::tidy::bugprone
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_UNSAFETOALLOWEXCEPTIONSCHECK_H
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.rst
new file mode 100644
index 0000000000000..56d6507f7f207
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.rst
@@ -0,0 +1,31 @@
+.. title:: clang-tidy - bugprone-unsafe-to-allow-exceptions
+
+bugprone-unsafe-to-allow-exceptions
+===================================
+
+Finds functions where throwing exceptions is unsafe but the function is still
+marked as throwable. Throwing exceptions from the following functions can be
+problematic:
+
+* Destructors
+* Move constructors
+* Move assignment operators
+* The ``main()`` functions
+* ``swap()`` functions
+* ``iter_swap()`` functions
+* ``iter_move()`` functions
+
+The check finds any of these functions if it is marked with ``noexcept(false)``
+or ``throw(exception)``. This would indicate that the function is expected to
+throw exceptions. Only the presence of these keywords is checked, not if the
+function actually throws any exception.
+
+Options
+-------
+
+.. option:: CheckedSwapFunctions
+
+ Comma-separated list of checked swap function names (where throwing
+ exceptions is unsafe).
+ Default value is `swap,iter_swap,iter_move`.
+
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 3dabf887dc2e1..5bef16e365524 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -176,6 +176,7 @@ Clang-Tidy Checks
:doc:`bugprone-unintended-char-ostream-output <bugprone/unintended-char-ostream-output>`, "Yes"
:doc:`bugprone-unique-ptr-array-mismatch <bugprone/unique-ptr-array-mismatch>`, "Yes"
:doc:`bugprone-unsafe-functions <bugprone/unsafe-functions>`,
+ :doc:`bugprone-unsafe-to-allow-exceptions <bugprone/unsafe-to-allow-exceptions>`,
:doc:`bugprone-unused-local-non-trivial-variable <bugprone/unused-local-non-trivial-variable>`,
:doc:`bugprone-unused-raii <bugprone/unused-raii>`, "Yes"
:doc:`bugprone-unused-return-value <bugprone/unused-return-value>`,
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-to-allow-exceptions.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-to-allow-exceptions.cpp
new file mode 100644
index 0000000000000..1e24a23e7afc9
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-to-allow-exceptions.cpp
@@ -0,0 +1,45 @@
+// RUN: %check_clang_tidy -std=c++11,c++14 %s bugprone-unsafe-to-allow-exceptions %t -- -- -fexceptions
+
+struct may_throw {
+ may_throw(may_throw&&) throw(int) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'may_throw' should not throw exceptions but it is still marked as throwable [bugprone-unsafe-to-allow-exceptions]
+ }
+ may_throw& operator=(may_throw&&) noexcept(false) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: function 'operator=' should not throw exceptions but it is still marked as throwable
+ }
+ ~may_throw() noexcept(false) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function '~may_throw' should not throw exceptions but it is still marked as throwable
+ }
+
+ void f() noexcept(false) {
+ }
+};
+
+struct no_throw {
+ no_throw(no_throw&&) throw() {
+ }
+ no_throw& operator=(no_throw&&) noexcept(true) {
+ }
+ ~no_throw() noexcept(true) {
+ }
+};
+
+int main() noexcept(false) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: function 'main' should not throw exceptions but it is still marked as throwable
+ return 0;
+}
+
+void swap(int&, int&) noexcept(false) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'swap' should not throw exceptions but it is still marked as throwable
+}
+
+void iter_swap(int&, int&) noexcept(false) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'iter_swap' should not throw exceptions but it is still marked as throwable
+}
+
+void iter_move(int&) noexcept(false) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'iter_move' should not throw exceptions but it is still marked as throwable
+}
+
+void swap(double&, double&) {
+}
>From 722b7275fb625023994974c223f25d363470cddd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= <balazs.keri at ericsson.com>
Date: Tue, 20 Jan 2026 16:52:06 +0100
Subject: [PATCH 2/6] fixed review issues
---
.../bugprone/UnsafeToAllowExceptionsCheck.cpp | 19 ++++++---------
.../bugprone/UnsafeToAllowExceptionsCheck.h | 2 +-
.../checks/bugprone/exception-escape.rst | 4 +++-
.../bugprone/unsafe-to-allow-exceptions.rst | 24 +++++++++++++------
.../bugprone/unsafe-to-allow-exceptions.cpp | 23 ++++++++++++------
5 files changed, 44 insertions(+), 28 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.cpp
index 3f159410d6f4d..e1124b864b23d 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.cpp
@@ -16,9 +16,8 @@ using namespace clang::ast_matchers;
namespace clang::tidy::bugprone {
namespace {
-AST_MATCHER_P(FunctionDecl, isEnabled, llvm::StringSet<>,
- FunctionsThatShouldNotThrow) {
- return FunctionsThatShouldNotThrow.contains(Node.getNameAsString());
+AST_MATCHER_P(FunctionDecl, hasAnyName, llvm::StringSet<>, Names) {
+ return Names.contains(Node.getNameAsString());
}
AST_MATCHER(FunctionDecl, isExplicitThrow) {
@@ -26,19 +25,15 @@ AST_MATCHER(FunctionDecl, isExplicitThrow) {
Node.getExceptionSpecSourceRange().isValid();
}
-AST_MATCHER(FunctionDecl, hasAtLeastOneParameter) {
- return Node.getNumParams() > 0;
-}
-
} // namespace
UnsafeToAllowExceptionsCheck::UnsafeToAllowExceptionsCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
RawCheckedSwapFunctions(
- Options.get("CheckedSwapFunctions", "swap,iter_swap,iter_move")) {
+ Options.get("CheckedSwapFunctions", "swap;iter_swap;iter_move")) {
llvm::SmallVector<StringRef, 4> CheckedSwapFunctionsVec;
- RawCheckedSwapFunctions.split(CheckedSwapFunctionsVec, ",", -1, false);
+ RawCheckedSwapFunctions.split(CheckedSwapFunctionsVec, ";", -1, false);
CheckedSwapFunctions.insert_range(CheckedSwapFunctionsVec);
}
@@ -53,8 +48,8 @@ void UnsafeToAllowExceptionsCheck::registerMatchers(MatchFinder *Finder) {
anyOf(cxxDestructorDecl(),
cxxConstructorDecl(isMoveConstructor()),
cxxMethodDecl(isMoveAssignmentOperator()),
- allOf(isEnabled(CheckedSwapFunctions),
- hasAtLeastOneParameter()),
+ allOf(hasAnyName(CheckedSwapFunctions),
+ unless(parameterCountIs(0))),
isMain())))
.bind("f"),
this);
@@ -69,7 +64,7 @@ void UnsafeToAllowExceptionsCheck::check(
diag(MatchedDecl->getLocation(),
"function %0 should not throw exceptions but "
- "it is still marked as throwable")
+ "it is still marked as potentially throwing")
<< MatchedDecl;
}
diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.h b/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.h
index 90b9d42a7e4c4..a0734a120e3b0 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.h
+++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.h
@@ -15,7 +15,7 @@
namespace clang::tidy::bugprone {
/// Finds functions where throwing exceptions is unsafe but the function is
-/// still marked as throwable.
+/// still marked as potentially throwing.
///
/// For the user-facing documentation see:
/// https://clang.llvm.org/extra/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.html
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 7d724a4581715..65543796c9ffe 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
@@ -28,7 +28,9 @@ Functions declared explicitly with ``noexcept(false)`` or ``throw(exception)``
will be excluded from the analysis, as even though it is not recommended for
functions like ``swap()``, ``main()``, move constructors, move assignment
operators and destructors, it is a clear indication of the developer's
-intention and should be respected.
+intention and should be respected. To still check for this situation, the check
+:doc:`bugprone-unsafe-to-allow-exceptions <unsafe-to-allow-exceptions>` can be
+used.
WARNING! This check may be expensive on large source files.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.rst
index 56d6507f7f207..d5c8d119813ca 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.rst
@@ -4,8 +4,8 @@ bugprone-unsafe-to-allow-exceptions
===================================
Finds functions where throwing exceptions is unsafe but the function is still
-marked as throwable. Throwing exceptions from the following functions can be
-problematic:
+marked as potentially throwing. Throwing exceptions from the following functions
+can be problematic:
* Destructors
* Move constructors
@@ -15,17 +15,27 @@ problematic:
* ``iter_swap()`` functions
* ``iter_move()`` functions
+A destructor throwing an exception may result in undefined behavior, resource
+leaks or unexpected termination of the program. Throwing move constructor or
+move assignment also may result in undefined behavior or resource leak. The
+``swap()`` operations expected to be non throwing most of the cases and they
+are always possible to implement in a non throwing way. Non throwing ``swap()``
+operations are also used to create move operations. A throwing ``main()``
+function also results in unexpected termination.
+
The check finds any of these functions if it is marked with ``noexcept(false)``
or ``throw(exception)``. This would indicate that the function is expected to
throw exceptions. Only the presence of these keywords is checked, not if the
-function actually throws any exception.
+function actually throws any exception. To check if the function actually throws
+exception, the check :doc:`bugprone-exception-escape <exception-escape>` can be
+used (but it does not warn if a function is explicitly marked as throwing).
Options
-------
.. option:: CheckedSwapFunctions
- Comma-separated list of checked swap function names (where throwing
- exceptions is unsafe).
- Default value is `swap,iter_swap,iter_move`.
-
+ Semicolon-separated list of checked swap function names (where throwing
+ exceptions is unsafe). These functions are checked if the parameter count is
+ at least 1.
+ Default value is `swap;iter_swap;iter_move`.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-to-allow-exceptions.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-to-allow-exceptions.cpp
index 1e24a23e7afc9..50f7c1b6e2cfc 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-to-allow-exceptions.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-to-allow-exceptions.cpp
@@ -1,14 +1,19 @@
// RUN: %check_clang_tidy -std=c++11,c++14 %s bugprone-unsafe-to-allow-exceptions %t -- -- -fexceptions
+// RUN: %check_clang_tidy -std=c++11,c++14 -check-suffix=,CUSTOMSWAP %s bugprone-unsafe-to-allow-exceptions %t -- \
+// RUN: -config="{CheckOptions: { \
+// RUN: bugprone-unsafe-to-allow-exceptions.CheckedSwapFunctions: 'swap;iter_swap;iter_move;swap1', \
+// RUN: }}" \
+// RUN: -- -fexceptions
struct may_throw {
may_throw(may_throw&&) throw(int) {
- // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'may_throw' should not throw exceptions but it is still marked as throwable [bugprone-unsafe-to-allow-exceptions]
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'may_throw' should not throw exceptions but it is still marked as potentially throwing [bugprone-unsafe-to-allow-exceptions]
}
may_throw& operator=(may_throw&&) noexcept(false) {
- // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: function 'operator=' should not throw exceptions but it is still marked as throwable
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: function 'operator=' should not throw exceptions but it is still marked as potentially throwing
}
~may_throw() noexcept(false) {
- // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function '~may_throw' should not throw exceptions but it is still marked as throwable
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function '~may_throw' should not throw exceptions but it is still marked as potentially throwing
}
void f() noexcept(false) {
@@ -25,21 +30,25 @@ struct no_throw {
};
int main() noexcept(false) {
- // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: function 'main' should not throw exceptions but it is still marked as throwable
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: function 'main' should not throw exceptions but it is still marked as potentially throwing
return 0;
}
void swap(int&, int&) noexcept(false) {
- // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'swap' should not throw exceptions but it is still marked as throwable
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'swap' should not throw exceptions but it is still marked as potentially throwing
}
void iter_swap(int&, int&) noexcept(false) {
- // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'iter_swap' should not throw exceptions but it is still marked as throwable
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'iter_swap' should not throw exceptions but it is still marked as potentially throwing
}
void iter_move(int&) noexcept(false) {
- // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'iter_move' should not throw exceptions but it is still marked as throwable
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'iter_move' should not throw exceptions but it is still marked as potentially throwing
}
void swap(double&, double&) {
}
+
+void swap1(long&) noexcept(false) {
+ // CHECK-MESSAGES-CUSTOMSWAP: :[[@LINE-1]]:6: warning: function 'swap1' should not throw exceptions but it is still marked as potentially throwing
+}
>From c5012c5e1b05191284824a1c2954ef5fdd3b9687 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= <balazs.keri at ericsson.com>
Date: Wed, 21 Jan 2026 16:06:50 +0100
Subject: [PATCH 3/6] fixed documentation line length
---
.../checks/bugprone/unsafe-to-allow-exceptions.rst | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.rst
index d5c8d119813ca..7d8d1b662a806 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.rst
@@ -4,8 +4,8 @@ bugprone-unsafe-to-allow-exceptions
===================================
Finds functions where throwing exceptions is unsafe but the function is still
-marked as potentially throwing. Throwing exceptions from the following functions
-can be problematic:
+marked as potentially throwing. Throwing exceptions from the following
+functions can be problematic:
* Destructors
* Move constructors
@@ -26,9 +26,10 @@ function also results in unexpected termination.
The check finds any of these functions if it is marked with ``noexcept(false)``
or ``throw(exception)``. This would indicate that the function is expected to
throw exceptions. Only the presence of these keywords is checked, not if the
-function actually throws any exception. To check if the function actually throws
-exception, the check :doc:`bugprone-exception-escape <exception-escape>` can be
-used (but it does not warn if a function is explicitly marked as throwing).
+function actually throws any exception. To check if the function actually
+throws exception, the check :doc:`bugprone-exception-escape <exception-escape>`
+can be used (but it does not warn if a function is explicitly marked as
+throwing).
Options
-------
>From 436f385c258cf31f49725ca5250d2dcd2a987d7b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= <balazs.keri at ericsson.com>
Date: Thu, 22 Jan 2026 10:12:11 +0100
Subject: [PATCH 4/6] added release notes
---
clang-tools-extra/docs/ReleaseNotes.rst | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 69c3bcf67b8db..d9d200a6ecdc9 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -97,6 +97,12 @@ Improvements to clang-tidy
New checks
^^^^^^^^^^
+- New :doc:`bugprone-unsafe-to-allow-exceptions
+ <clang-tidy/checks/bugprone/unsafe-to-allow-exceptions>` check.
+
+ Finds functions where throwing exceptions is unsafe but the function is still
+ marked as potentially throwing.
+
New check aliases
^^^^^^^^^^^^^^^^^
>From b4840d4f2c73b7dc4d3cca49968d9d75a6eb8098 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= <balazs.keri at ericsson.com>
Date: Mon, 2 Feb 2026 15:45:58 +0100
Subject: [PATCH 5/6] re-phrased documentation
---
.../docs/clang-tidy/checks/bugprone/exception-escape.rst | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
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 65543796c9ffe..16a91a368d800 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
@@ -28,7 +28,8 @@ Functions declared explicitly with ``noexcept(false)`` or ``throw(exception)``
will be excluded from the analysis, as even though it is not recommended for
functions like ``swap()``, ``main()``, move constructors, move assignment
operators and destructors, it is a clear indication of the developer's
-intention and should be respected. To still check for this situation, the check
+intention and should be respected. To check if these special functions are
+marked as potentially throwing, the check
:doc:`bugprone-unsafe-to-allow-exceptions <unsafe-to-allow-exceptions>` can be
used.
>From 4462432e1df6fff1763fee651e5dd025eafd950e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bal=C3=A1zs=20K=C3=A9ri?= <balazs.keri at ericsson.com>
Date: Mon, 9 Feb 2026 12:49:14 +0100
Subject: [PATCH 6/6] fixed review issues
---
.../bugprone/UnsafeToAllowExceptionsCheck.cpp | 36 +++++---------
.../bugprone/UnsafeToAllowExceptionsCheck.h | 3 +-
.../bugprone/unsafe-to-allow-exceptions.rst | 3 +-
.../unsafe-to-allow-exceptions-precpp17.cpp | 49 +++++++++++++++++++
.../bugprone/unsafe-to-allow-exceptions.cpp | 6 +--
5 files changed, 67 insertions(+), 30 deletions(-)
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-to-allow-exceptions-precpp17.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.cpp
index e1124b864b23d..f9a236aba4459 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.cpp
@@ -7,19 +7,14 @@
//===----------------------------------------------------------------------===//
#include "UnsafeToAllowExceptionsCheck.h"
-
+#include "../utils/OptionsUtils.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
-#include "llvm/ADT/StringSet.h"
using namespace clang::ast_matchers;
namespace clang::tidy::bugprone {
namespace {
-AST_MATCHER_P(FunctionDecl, hasAnyName, llvm::StringSet<>, Names) {
- return Names.contains(Node.getNameAsString());
-}
-
AST_MATCHER(FunctionDecl, isExplicitThrow) {
return isExplicitThrowExceptionSpec(Node.getExceptionSpecType()) &&
Node.getExceptionSpecSourceRange().isValid();
@@ -30,27 +25,24 @@ AST_MATCHER(FunctionDecl, isExplicitThrow) {
UnsafeToAllowExceptionsCheck::UnsafeToAllowExceptionsCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
- RawCheckedSwapFunctions(
- Options.get("CheckedSwapFunctions", "swap;iter_swap;iter_move")) {
- llvm::SmallVector<StringRef, 4> CheckedSwapFunctionsVec;
- RawCheckedSwapFunctions.split(CheckedSwapFunctionsVec, ";", -1, false);
- CheckedSwapFunctions.insert_range(CheckedSwapFunctionsVec);
-}
+ CheckedSwapFunctions(utils::options::parseStringList(
+ Options.get("CheckedSwapFunctions", "swap;iter_swap;iter_move"))) {}
void UnsafeToAllowExceptionsCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
- Options.store(Opts, "CheckedSwapFunctions", RawCheckedSwapFunctions);
+ Options.store(Opts, "CheckedSwapFunctions",
+ utils::options::serializeStringList(CheckedSwapFunctions));
}
void UnsafeToAllowExceptionsCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
- functionDecl(allOf(isDefinition(), isExplicitThrow(),
- anyOf(cxxDestructorDecl(),
- cxxConstructorDecl(isMoveConstructor()),
- cxxMethodDecl(isMoveAssignmentOperator()),
- allOf(hasAnyName(CheckedSwapFunctions),
- unless(parameterCountIs(0))),
- isMain())))
+ functionDecl(isDefinition(), isExplicitThrow(),
+ anyOf(cxxDestructorDecl(),
+ cxxConstructorDecl(isMoveConstructor()),
+ cxxMethodDecl(isMoveAssignmentOperator()),
+ allOf(hasAnyName(CheckedSwapFunctions),
+ unless(parameterCountIs(0))),
+ isMain()))
.bind("f"),
this);
}
@@ -58,9 +50,7 @@ void UnsafeToAllowExceptionsCheck::registerMatchers(MatchFinder *Finder) {
void UnsafeToAllowExceptionsCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("f");
-
- if (!MatchedDecl)
- return;
+ assert(MatchedDecl);
diag(MatchedDecl->getLocation(),
"function %0 should not throw exceptions but "
diff --git a/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.h b/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.h
index a0734a120e3b0..fcdfb0a16c35b 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.h
+++ b/clang-tools-extra/clang-tidy/bugprone/UnsafeToAllowExceptionsCheck.h
@@ -30,8 +30,7 @@ class UnsafeToAllowExceptionsCheck : public ClangTidyCheck {
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
private:
- StringRef RawCheckedSwapFunctions;
- llvm::StringSet<> CheckedSwapFunctions;
+ std::vector<llvm::StringRef> CheckedSwapFunctions;
};
} // namespace clang::tidy::bugprone
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.rst
index 7d8d1b662a806..894b1415e83dd 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/unsafe-to-allow-exceptions.rst
@@ -38,5 +38,4 @@ Options
Semicolon-separated list of checked swap function names (where throwing
exceptions is unsafe). These functions are checked if the parameter count is
- at least 1.
- Default value is `swap;iter_swap;iter_move`.
+ at least 1. Default value is `swap;iter_swap;iter_move`.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-to-allow-exceptions-precpp17.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-to-allow-exceptions-precpp17.cpp
new file mode 100644
index 0000000000000..4f36557743cbd
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-to-allow-exceptions-precpp17.cpp
@@ -0,0 +1,49 @@
+// RUN: %check_clang_tidy -std=c++11,c++14 %s bugprone-unsafe-to-allow-exceptions %t -- \
+// RUN: -config="{CheckOptions: { \
+// RUN: bugprone-unsafe-to-allow-exceptions.CheckedSwapFunctions: 'swap', \
+// RUN: }}" \
+// RUN: -- -fexceptions
+
+class Exception {};
+
+struct may_throw {
+ may_throw(may_throw&&) throw(int) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'may_throw' should not throw exceptions but it is still marked as potentially throwing [bugprone-unsafe-to-allow-exceptions]
+ }
+ may_throw& operator=(may_throw&&) throw(Exception) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: function 'operator=' should not throw exceptions but it is still marked as potentially throwing
+ }
+ ~may_throw() throw(char, Exception) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function '~may_throw' should not throw exceptions but it is still marked as potentially throwing
+ }
+
+ void f() noexcept(false) {
+ }
+};
+
+struct no_throw {
+ no_throw(no_throw&&) throw() {
+ }
+ no_throw& operator=(no_throw&&) noexcept(true) {
+ }
+ ~no_throw() noexcept(true) {
+ }
+};
+
+int main() throw(char) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: function 'main' should not throw exceptions but it is still marked as potentially throwing
+ return 0;
+}
+
+void swap(no_throw&, no_throw&) throw(bool) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: function 'swap' should not throw exceptions but it is still marked as potentially throwing
+}
+
+void iter_swap(int&, int&) throw(bool) {
+}
+
+void iter_move(int&) throw(bool) {
+}
+
+void swap(double&, double&) {
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-to-allow-exceptions.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-to-allow-exceptions.cpp
index 50f7c1b6e2cfc..c6242881f295a 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-to-allow-exceptions.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unsafe-to-allow-exceptions.cpp
@@ -1,12 +1,12 @@
-// RUN: %check_clang_tidy -std=c++11,c++14 %s bugprone-unsafe-to-allow-exceptions %t -- -- -fexceptions
-// RUN: %check_clang_tidy -std=c++11,c++14 -check-suffix=,CUSTOMSWAP %s bugprone-unsafe-to-allow-exceptions %t -- \
+// RUN: %check_clang_tidy -std=c++17-or-later %s bugprone-unsafe-to-allow-exceptions %t -- -- -fexceptions
+// RUN: %check_clang_tidy -std=c++17-or-later -check-suffix=,CUSTOMSWAP %s bugprone-unsafe-to-allow-exceptions %t -- \
// RUN: -config="{CheckOptions: { \
// RUN: bugprone-unsafe-to-allow-exceptions.CheckedSwapFunctions: 'swap;iter_swap;iter_move;swap1', \
// RUN: }}" \
// RUN: -- -fexceptions
struct may_throw {
- may_throw(may_throw&&) throw(int) {
+ may_throw(may_throw&&) noexcept(false) {
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'may_throw' should not throw exceptions but it is still marked as potentially throwing [bugprone-unsafe-to-allow-exceptions]
}
may_throw& operator=(may_throw&&) noexcept(false) {
More information about the cfe-commits
mailing list