[clang-tools-extra] [clang-tidy] Add options to throw unannotated functions in `bugprone-exception-escape` (PR #168324)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Nov 27 00:11:35 PST 2025
https://github.com/zeyi2 updated https://github.com/llvm/llvm-project/pull/168324
>From 9c755180c54ab395d9631a35bad621b466162033 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Mon, 17 Nov 2025 15:21:17 +0800
Subject: [PATCH 1/4] [clang-tidy] Add options to throw unannotated functions
in `bugprone-exception-escape`
---
.../bugprone/ExceptionEscapeCheck.cpp | 80 ++++++++++++-------
.../bugprone/ExceptionEscapeCheck.h | 3 +
.../clang-tidy/utils/ExceptionAnalyzer.cpp | 13 +++
.../clang-tidy/utils/ExceptionAnalyzer.h | 27 ++++++-
clang-tools-extra/docs/ReleaseNotes.rst | 6 +-
.../checks/bugprone/exception-escape.rst | 12 +++
...eption-escape-known-unannotated-option.cpp | 25 ++++++
.../exception-escape-unannotated-option.cpp | 25 ++++++
8 files changed, 158 insertions(+), 33 deletions(-)
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-known-unannotated-option.cpp
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-unannotated-option.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
index b7de8395ffa05..b2415b59b135d 100644
--- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
@@ -42,7 +42,10 @@ ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name,
CheckDestructors(Options.get("CheckDestructors", true)),
CheckMoveMemberFunctions(Options.get("CheckMoveMemberFunctions", true)),
CheckMain(Options.get("CheckMain", true)),
- CheckNothrowFunctions(Options.get("CheckNothrowFunctions", true)) {
+ CheckNothrowFunctions(Options.get("CheckNothrowFunctions", true)),
+ KnownUnannotatedAsThrowing(
+ Options.get("KnownUnannotatedAsThrowing", false)),
+ UnknownAsThrowing(Options.get("UnknownAsThrowing", false)) {
llvm::SmallVector<StringRef, 8> FunctionsThatShouldNotThrowVec,
IgnoredExceptionsVec, CheckedSwapFunctionsVec;
RawFunctionsThatShouldNotThrow.split(FunctionsThatShouldNotThrowVec, ",", -1,
@@ -57,6 +60,7 @@ ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name,
IgnoredExceptions.insert_range(IgnoredExceptionsVec);
Tracer.ignoreExceptions(std::move(IgnoredExceptions));
Tracer.ignoreBadAlloc(true);
+ Tracer.assumeUnannotatedFunctionsThrow(KnownUnannotatedAsThrowing);
}
void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
@@ -68,6 +72,8 @@ void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "CheckMoveMemberFunctions", CheckMoveMemberFunctions);
Options.store(Opts, "CheckMain", CheckMain);
Options.store(Opts, "CheckNothrowFunctions", CheckNothrowFunctions);
+ Options.store(Opts, "KnownUnannotatedAsThrowing", KnownUnannotatedAsThrowing);
+ Options.store(Opts, "UnknownAsThrowing", UnknownAsThrowing);
}
void ExceptionEscapeCheck::registerMatchers(MatchFinder *Finder) {
@@ -103,41 +109,53 @@ void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) {
const utils::ExceptionAnalyzer::ExceptionInfo Info =
Tracer.analyze(MatchedDecl);
- if (Info.getBehaviour() != utils::ExceptionAnalyzer::State::Throwing)
- return;
-
- diag(MatchedDecl->getLocation(), "an exception may be thrown in function "
- "%0 which should not throw exceptions")
- << MatchedDecl;
+ const auto Behaviour = Info.getBehaviour();
+ const bool IsThrowing =
+ Behaviour == utils::ExceptionAnalyzer::State::Throwing;
+ const bool IsUnknown = Behaviour == utils::ExceptionAnalyzer::State::Unknown;
- const auto &[ThrowType, ThrowInfo] = *Info.getExceptions().begin();
+ const bool ReportUnknown =
+ IsUnknown &&
+ ((KnownUnannotatedAsThrowing && Info.hasUnknownFromKnownUnannotated()) ||
+ (UnknownAsThrowing && Info.hasUnknownFromMissingDefinition()));
- if (ThrowInfo.Loc.isInvalid())
+ if (!(IsThrowing || ReportUnknown))
return;
- const utils::ExceptionAnalyzer::CallStack &Stack = ThrowInfo.Stack;
- diag(ThrowInfo.Loc,
- "frame #0: unhandled exception of type %0 may be thrown in function %1 "
- "here",
- DiagnosticIDs::Note)
- << QualType(ThrowType, 0U) << Stack.back().first;
-
- size_t FrameNo = 1;
- for (auto CurrIt = ++Stack.rbegin(), PrevIt = Stack.rbegin();
- CurrIt != Stack.rend(); ++CurrIt, ++PrevIt) {
- const FunctionDecl *CurrFunction = CurrIt->first;
- const FunctionDecl *PrevFunction = PrevIt->first;
- const SourceLocation PrevLocation = PrevIt->second;
- if (PrevLocation.isValid()) {
- diag(PrevLocation, "frame #%0: function %1 calls function %2 here",
- DiagnosticIDs::Note)
- << FrameNo << CurrFunction << PrevFunction;
- } else {
- diag(CurrFunction->getLocation(),
- "frame #%0: function %1 calls function %2", DiagnosticIDs::Note)
- << FrameNo << CurrFunction << PrevFunction;
+ diag(MatchedDecl->getLocation(), "an exception may be thrown in function %0 "
+ "which should not throw exceptions")
+ << MatchedDecl;
+
+ if (!Info.getExceptions().empty()) {
+ const auto &[ThrowType, ThrowInfo] = *Info.getExceptions().begin();
+
+ if (ThrowInfo.Loc.isInvalid())
+ return;
+
+ const utils::ExceptionAnalyzer::CallStack &Stack = ThrowInfo.Stack;
+ diag(ThrowInfo.Loc,
+ "frame #0: unhandled exception of type %0 may be thrown in function "
+ "%1 here",
+ DiagnosticIDs::Note)
+ << QualType(ThrowType, 0U) << Stack.back().first;
+
+ size_t FrameNo = 1;
+ for (auto CurrIt = ++Stack.rbegin(), PrevIt = Stack.rbegin();
+ CurrIt != Stack.rend(); ++CurrIt, ++PrevIt) {
+ const FunctionDecl *CurrFunction = CurrIt->first;
+ const FunctionDecl *PrevFunction = PrevIt->first;
+ const SourceLocation PrevLocation = PrevIt->second;
+ if (PrevLocation.isValid()) {
+ diag(PrevLocation, "frame #%0: function %1 calls function %2 here",
+ DiagnosticIDs::Note)
+ << FrameNo << CurrFunction << PrevFunction;
+ } else {
+ diag(CurrFunction->getLocation(),
+ "frame #%0: function %1 calls function %2", DiagnosticIDs::Note)
+ << FrameNo << CurrFunction << PrevFunction;
+ }
+ ++FrameNo;
}
- ++FrameNo;
}
}
diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h
index c3bf4a4335273..ba65640435368 100644
--- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h
+++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h
@@ -42,6 +42,9 @@ class ExceptionEscapeCheck : public ClangTidyCheck {
const bool CheckMain;
const bool CheckNothrowFunctions;
+ const bool KnownUnannotatedAsThrowing;
+ const bool UnknownAsThrowing;
+
llvm::StringSet<> FunctionsThatShouldNotThrow;
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 c774f54b1da5a..221f7711786de 100644
--- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
+++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
@@ -39,6 +39,10 @@ ExceptionAnalyzer::ExceptionInfo &ExceptionAnalyzer::ExceptionInfo::merge(
Behaviour = State::Unknown;
ContainsUnknown = ContainsUnknown || Other.ContainsUnknown;
+ UnknownFromMissingDefinition =
+ UnknownFromMissingDefinition || Other.UnknownFromMissingDefinition;
+ UnknownFromKnownUnannotated =
+ UnknownFromKnownUnannotated || Other.UnknownFromKnownUnannotated;
ThrownExceptions.insert_range(Other.ThrownExceptions);
return *this;
}
@@ -484,10 +488,19 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
}
CallStack.erase(Func);
+ // Optionally treat unannotated functions as potentially throwing if they
+ // are not explicitly non-throwing and no throw was discovered.
+ if (AssumeUnannotatedThrowing &&
+ Result.getBehaviour() == State::NotThrowing && canThrow(Func)) {
+ auto Unknown = ExceptionInfo::createUnknown();
+ Unknown.markUnknownFromKnownUnannotated();
+ return Unknown;
+ }
return Result;
}
auto Result = ExceptionInfo::createUnknown();
+ Result.markUnknownFromMissingDefinition();
if (const auto *FPT = Func->getType()->getAs<FunctionProtoType>()) {
for (const QualType &Ex : FPT->exceptions()) {
CallStack.insert({Func, CallLoc});
diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
index 1a277c8a6d3b2..c0356b71383fb 100644
--- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
+++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
@@ -52,7 +52,7 @@ class ExceptionAnalyzer {
using Throwables = llvm::SmallDenseMap<const Type *, ThrowInfo, 2>;
static ExceptionInfo createUnknown() { return {State::Unknown}; }
- static ExceptionInfo createNonThrowing() { return {State::Throwing}; }
+ static ExceptionInfo createNonThrowing() { return {State::NotThrowing}; }
/// By default the exception situation is unknown and must be
/// clarified step-wise.
@@ -67,6 +67,22 @@ class ExceptionAnalyzer {
State getBehaviour() const { return Behaviour; }
+ /// Unknown cause tracking.
+ void markUnknownFromMissingDefinition() {
+ UnknownFromMissingDefinition = true;
+ ContainsUnknown = true;
+ }
+ void markUnknownFromKnownUnannotated() {
+ UnknownFromKnownUnannotated = true;
+ ContainsUnknown = true;
+ }
+ bool hasUnknownFromMissingDefinition() const {
+ return UnknownFromMissingDefinition;
+ }
+ bool hasUnknownFromKnownUnannotated() const {
+ return UnknownFromKnownUnannotated;
+ }
+
/// Register a single exception type as recognized potential exception to be
/// thrown.
void registerException(const Type *ExceptionType,
@@ -124,12 +140,20 @@ class ExceptionAnalyzer {
/// after filtering.
bool ContainsUnknown;
+ bool UnknownFromMissingDefinition = false;
+ bool UnknownFromKnownUnannotated = false;
+
/// 'ThrownException' is empty if the 'Behaviour' is either 'NotThrowing' or
/// 'Unknown'.
Throwables ThrownExceptions;
};
ExceptionAnalyzer() = default;
+ /// When enabled, treat any function that is not explicitly non-throwing
+ /// as potentially throwing, even if its body analysis finds no throw.
+ void assumeUnannotatedFunctionsThrow(bool Enable) {
+ AssumeUnannotatedThrowing = Enable;
+ }
void ignoreBadAlloc(bool ShallIgnore) { IgnoreBadAlloc = ShallIgnore; }
void ignoreExceptions(llvm::StringSet<> ExceptionNames) {
@@ -154,6 +178,7 @@ class ExceptionAnalyzer {
bool IgnoreBadAlloc = true;
llvm::StringSet<> IgnoredExceptions;
llvm::DenseMap<const FunctionDecl *, ExceptionInfo> FunctionCache{32U};
+ bool AssumeUnannotatedThrowing = false;
};
} // namespace clang::tidy::utils
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index b982216297919..d671efa8b388f 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -328,7 +328,11 @@ Changes in existing checks
where the check wouldn't diagnose throws in arguments to functions or
constructors. Added fine-grained configuration via options
`CheckDestructors`, `CheckMoveMemberFunctions`, `CheckMain`,
- `CheckedSwapFunctions`, and `CheckNothrowFunctions`.
+ `CheckedSwapFunctions`, and `CheckNothrowFunctions`; and added
+ ``KnownUnannotatedAsThrowing`` and ``UnknownAsThrowing`` to support
+ reporting for unannotated functions, enabling reporting when no explicit
+ ``throw`` is seen and allowing separate tuning for known and unknown
+ implementations.
- Improved :doc:`bugprone-infinite-loop
<clang-tidy/checks/bugprone/infinite-loop>` check by adding detection for
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..66e8acaa242cf 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
@@ -71,3 +71,15 @@ Options
Comma separated list containing type names which are not counted as thrown
exceptions in the check. Default value is an empty string.
+
+.. option:: KnownUnannotatedAsThrowing
+
+ When `true`, treat calls to functions with visible definitions that are not
+ explicitly declared as non-throwing (i.e. lack ``noexcept`` or ``throw()``)
+ as potentially throwing, even if their bodies are visible and no explicit
+ throw is found. Default value is `false`.
+
+.. option:: UnknownAsThrowing
+
+ When `true`, treat calls to functions without visible definitions as
+ potentially throwing. Default value is `false`.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-known-unannotated-option.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-known-unannotated-option.cpp
new file mode 100644
index 0000000000000..7f72870f43ea9
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-known-unannotated-option.cpp
@@ -0,0 +1,25 @@
+// RUN: %check_clang_tidy -std=c++11-or-later %s bugprone-exception-escape %t -- \
+// RUN: -config='{"CheckOptions": { \
+// RUN: "bugprone-exception-escape.KnownUnannotatedAsThrowing": true \
+// RUN: }}' -- -fexceptions
+
+void unannotated_no_throw_body() {}
+
+void calls_unannotated() noexcept {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'calls_unannotated' which should not throw exceptions
+ unannotated_no_throw_body();
+}
+
+void extern_declared();
+
+void calls_unknown() noexcept {
+ // CHECK-MESSAGES-NOT: warning:
+ extern_declared();
+}
+
+void definitely_nothrow() noexcept {}
+
+void calls_nothrow() noexcept {
+ // CHECK-MESSAGES-NOT: warning:
+ definitely_nothrow();
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-unannotated-option.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-unannotated-option.cpp
new file mode 100644
index 0000000000000..c92eaa75c6adf
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-unannotated-option.cpp
@@ -0,0 +1,25 @@
+// RUN: %check_clang_tidy -std=c++11-or-later %s bugprone-exception-escape %t -- \
+// RUN: -config='{"CheckOptions": { \
+// RUN: "bugprone-exception-escape.UnknownAsThrowing": true \
+// RUN: }}' -- -fexceptions
+
+void unannotated_no_throw_body() {}
+
+void calls_unannotated() noexcept {
+ // CHECK-MESSAGES-NOT: warning:
+ unannotated_no_throw_body();
+}
+
+void extern_declared();
+
+void calls_unknown() noexcept {
+ // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'calls_unknown' which should not throw exceptions
+ extern_declared();
+}
+
+void definitely_nothrow() noexcept {}
+
+void calls_nothrow() noexcept {
+ // CHECK-MESSAGES-NOT: warning:
+ definitely_nothrow();
+}
>From 179ad90a5cfaf7ce8f5cbc20a973e2dc6a1312fe Mon Sep 17 00:00:00 2001
From: mitchell <mitchell.xu2 at gmail.com>
Date: Tue, 18 Nov 2025 09:06:32 +0800
Subject: [PATCH 2/4] Update clang-tools-extra/docs/ReleaseNotes.rst
Co-authored-by: EugeneZelenko <eugene.zelenko at gmail.com>
---
clang-tools-extra/docs/ReleaseNotes.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index d671efa8b388f..13b0db2840d2c 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -329,7 +329,7 @@ Changes in existing checks
constructors. Added fine-grained configuration via options
`CheckDestructors`, `CheckMoveMemberFunctions`, `CheckMain`,
`CheckedSwapFunctions`, and `CheckNothrowFunctions`; and added
- ``KnownUnannotatedAsThrowing`` and ``UnknownAsThrowing`` to support
+ `KnownUnannotatedAsThrowing` and `UnknownAsThrowing` to support
reporting for unannotated functions, enabling reporting when no explicit
``throw`` is seen and allowing separate tuning for known and unknown
implementations.
>From 4c72dbdc1ed0112efd3b4295c2c3c40ca38e705b Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Wed, 19 Nov 2025 10:08:22 +0800
Subject: [PATCH 3/4] Fix
---
.../bugprone/ExceptionEscapeCheck.cpp | 59 ++++++++++---------
1 file changed, 30 insertions(+), 29 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
index b2415b59b135d..c9d4e270d29fa 100644
--- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
@@ -126,36 +126,37 @@ void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) {
"which should not throw exceptions")
<< MatchedDecl;
- if (!Info.getExceptions().empty()) {
- const auto &[ThrowType, ThrowInfo] = *Info.getExceptions().begin();
-
- if (ThrowInfo.Loc.isInvalid())
- return;
-
- const utils::ExceptionAnalyzer::CallStack &Stack = ThrowInfo.Stack;
- diag(ThrowInfo.Loc,
- "frame #0: unhandled exception of type %0 may be thrown in function "
- "%1 here",
- DiagnosticIDs::Note)
- << QualType(ThrowType, 0U) << Stack.back().first;
-
- size_t FrameNo = 1;
- for (auto CurrIt = ++Stack.rbegin(), PrevIt = Stack.rbegin();
- CurrIt != Stack.rend(); ++CurrIt, ++PrevIt) {
- const FunctionDecl *CurrFunction = CurrIt->first;
- const FunctionDecl *PrevFunction = PrevIt->first;
- const SourceLocation PrevLocation = PrevIt->second;
- if (PrevLocation.isValid()) {
- diag(PrevLocation, "frame #%0: function %1 calls function %2 here",
- DiagnosticIDs::Note)
- << FrameNo << CurrFunction << PrevFunction;
- } else {
- diag(CurrFunction->getLocation(),
- "frame #%0: function %1 calls function %2", DiagnosticIDs::Note)
- << FrameNo << CurrFunction << PrevFunction;
- }
- ++FrameNo;
+ if (Info.getExceptions().empty())
+ return;
+
+ const auto &[ThrowType, ThrowInfo] = *Info.getExceptions().begin();
+
+ if (ThrowInfo.Loc.isInvalid())
+ return;
+
+ const utils::ExceptionAnalyzer::CallStack &Stack = ThrowInfo.Stack;
+ diag(ThrowInfo.Loc,
+ "frame #0: unhandled exception of type %0 may be thrown in function "
+ "%1 here",
+ DiagnosticIDs::Note)
+ << QualType(ThrowType, 0U) << Stack.back().first;
+
+ size_t FrameNo = 1;
+ for (auto CurrIt = ++Stack.rbegin(), PrevIt = Stack.rbegin();
+ CurrIt != Stack.rend(); ++CurrIt, ++PrevIt) {
+ const FunctionDecl *CurrFunction = CurrIt->first;
+ const FunctionDecl *PrevFunction = PrevIt->first;
+ const SourceLocation PrevLocation = PrevIt->second;
+ if (PrevLocation.isValid()) {
+ diag(PrevLocation, "frame #%0: function %1 calls function %2 here",
+ DiagnosticIDs::Note)
+ << FrameNo << CurrFunction << PrevFunction;
+ } else {
+ diag(CurrFunction->getLocation(),
+ "frame #%0: function %1 calls function %2", DiagnosticIDs::Note)
+ << FrameNo << CurrFunction << PrevFunction;
}
+ ++FrameNo;
}
}
>From 9bea25a48a1ca2d7fe405a1523a25b6778a87ac7 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Thu, 27 Nov 2025 16:06:23 +0800
Subject: [PATCH 4/4] Fix
---
.../bugprone/ExceptionEscapeCheck.cpp | 56 ++++++++++++++-----
.../bugprone/ExceptionEscapeCheck.h | 10 +++-
clang-tools-extra/docs/ReleaseNotes.rst | 7 +--
.../checks/bugprone/exception-escape.rst | 30 ++++++----
...eption-escape-known-unannotated-option.cpp | 25 ---------
...ions-without-specification-as-throwing.cpp | 47 ++++++++++++++++
.../exception-escape-unannotated-option.cpp | 25 ---------
7 files changed, 118 insertions(+), 82 deletions(-)
delete mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-known-unannotated-option.cpp
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-treat-functions-without-specification-as-throwing.cpp
delete mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-unannotated-option.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
index c9d4e270d29fa..5f9cf84acf9c4 100644
--- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
@@ -13,7 +13,27 @@
using namespace clang::ast_matchers;
-namespace clang::tidy::bugprone {
+namespace clang::tidy {
+
+template <>
+struct OptionEnumMapping<
+ bugprone::ExceptionEscapeCheck::FunctionsThatShouldNotThrowPolicy> {
+ using FunctionsThatShouldNotThrowPolicy =
+ bugprone::ExceptionEscapeCheck::FunctionsThatShouldNotThrowPolicy;
+
+ static llvm::ArrayRef<std::pair<FunctionsThatShouldNotThrowPolicy, StringRef>>
+ getEnumMapping() {
+ static constexpr std::pair<FunctionsThatShouldNotThrowPolicy, StringRef>
+ Mapping[] = {
+ {FunctionsThatShouldNotThrowPolicy::None, "None"},
+ {FunctionsThatShouldNotThrowPolicy::OnlyUndefined, "OnlyUndefined"},
+ {FunctionsThatShouldNotThrowPolicy::All, "All"},
+ };
+ return {Mapping};
+ }
+};
+
+namespace bugprone {
namespace {
AST_MATCHER_P(FunctionDecl, isEnabled, llvm::StringSet<>,
@@ -43,9 +63,9 @@ ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name,
CheckMoveMemberFunctions(Options.get("CheckMoveMemberFunctions", true)),
CheckMain(Options.get("CheckMain", true)),
CheckNothrowFunctions(Options.get("CheckNothrowFunctions", true)),
- KnownUnannotatedAsThrowing(
- Options.get("KnownUnannotatedAsThrowing", false)),
- UnknownAsThrowing(Options.get("UnknownAsThrowing", false)) {
+ TreatFunctionsWithoutSpecificationAsThrowing(
+ Options.get("TreatFunctionsWithoutSpecificationAsThrowing",
+ FunctionsThatShouldNotThrowPolicy::None)) {
llvm::SmallVector<StringRef, 8> FunctionsThatShouldNotThrowVec,
IgnoredExceptionsVec, CheckedSwapFunctionsVec;
RawFunctionsThatShouldNotThrow.split(FunctionsThatShouldNotThrowVec, ",", -1,
@@ -60,7 +80,9 @@ ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name,
IgnoredExceptions.insert_range(IgnoredExceptionsVec);
Tracer.ignoreExceptions(std::move(IgnoredExceptions));
Tracer.ignoreBadAlloc(true);
- Tracer.assumeUnannotatedFunctionsThrow(KnownUnannotatedAsThrowing);
+ Tracer.assumeUnannotatedFunctionsThrow(
+ TreatFunctionsWithoutSpecificationAsThrowing ==
+ FunctionsThatShouldNotThrowPolicy::All);
}
void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
@@ -72,8 +94,8 @@ void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "CheckMoveMemberFunctions", CheckMoveMemberFunctions);
Options.store(Opts, "CheckMain", CheckMain);
Options.store(Opts, "CheckNothrowFunctions", CheckNothrowFunctions);
- Options.store(Opts, "KnownUnannotatedAsThrowing", KnownUnannotatedAsThrowing);
- Options.store(Opts, "UnknownAsThrowing", UnknownAsThrowing);
+ Options.store(Opts, "TreatFunctionsWithoutSpecificationAsThrowing",
+ TreatFunctionsWithoutSpecificationAsThrowing);
}
void ExceptionEscapeCheck::registerMatchers(MatchFinder *Finder) {
@@ -115,15 +137,18 @@ void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) {
const bool IsUnknown = Behaviour == utils::ExceptionAnalyzer::State::Unknown;
const bool ReportUnknown =
- IsUnknown &&
- ((KnownUnannotatedAsThrowing && Info.hasUnknownFromKnownUnannotated()) ||
- (UnknownAsThrowing && Info.hasUnknownFromMissingDefinition()));
+ IsUnknown && ((TreatFunctionsWithoutSpecificationAsThrowing ==
+ FunctionsThatShouldNotThrowPolicy::All &&
+ Info.hasUnknownFromKnownUnannotated()) ||
+ (TreatFunctionsWithoutSpecificationAsThrowing !=
+ FunctionsThatShouldNotThrowPolicy::None &&
+ Info.hasUnknownFromMissingDefinition()));
if (!(IsThrowing || ReportUnknown))
return;
- diag(MatchedDecl->getLocation(), "an exception may be thrown in function %0 "
- "which should not throw exceptions")
+ diag(MatchedDecl->getLocation(), "an exception may be thrown in function "
+ "%0 which should not throw exceptions")
<< MatchedDecl;
if (Info.getExceptions().empty())
@@ -136,8 +161,8 @@ void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) {
const utils::ExceptionAnalyzer::CallStack &Stack = ThrowInfo.Stack;
diag(ThrowInfo.Loc,
- "frame #0: unhandled exception of type %0 may be thrown in function "
- "%1 here",
+ "frame #0: unhandled exception of type %0 may be thrown in function %1 "
+ "here",
DiagnosticIDs::Note)
<< QualType(ThrowType, 0U) << Stack.back().first;
@@ -160,4 +185,5 @@ void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) {
}
}
-} // namespace clang::tidy::bugprone
+} // namespace bugprone
+} // namespace clang::tidy
diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h
index ba65640435368..38befa5a99957 100644
--- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h
+++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h
@@ -32,6 +32,12 @@ class ExceptionEscapeCheck : public ClangTidyCheck {
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ enum class FunctionsThatShouldNotThrowPolicy {
+ None,
+ OnlyUndefined,
+ All,
+ };
+
private:
StringRef RawFunctionsThatShouldNotThrow;
StringRef RawIgnoredExceptions;
@@ -42,8 +48,8 @@ class ExceptionEscapeCheck : public ClangTidyCheck {
const bool CheckMain;
const bool CheckNothrowFunctions;
- const bool KnownUnannotatedAsThrowing;
- const bool UnknownAsThrowing;
+ const FunctionsThatShouldNotThrowPolicy
+ TreatFunctionsWithoutSpecificationAsThrowing;
llvm::StringSet<> FunctionsThatShouldNotThrow;
llvm::StringSet<> CheckedSwapFunctions;
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 13b0db2840d2c..0072f536928a4 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -329,10 +329,9 @@ Changes in existing checks
constructors. Added fine-grained configuration via options
`CheckDestructors`, `CheckMoveMemberFunctions`, `CheckMain`,
`CheckedSwapFunctions`, and `CheckNothrowFunctions`; and added
- `KnownUnannotatedAsThrowing` and `UnknownAsThrowing` to support
- reporting for unannotated functions, enabling reporting when no explicit
- ``throw`` is seen and allowing separate tuning for known and unknown
- implementations.
+ `TreatFunctionsWithoutSpecificationAsThrowing` to support reporting for
+ unannotated functions, enabling reporting when no explicit ``throw`` is
+ seen and allowing separate tuning for known and unknown implementations.
- Improved :doc:`bugprone-infinite-loop
<clang-tidy/checks/bugprone/infinite-loop>` check by adding detection for
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 66e8acaa242cf..86fc0e69c3e7d 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
@@ -72,14 +72,22 @@ Options
Comma separated list containing type names which are not counted as thrown
exceptions in the check. Default value is an empty string.
-.. option:: KnownUnannotatedAsThrowing
-
- When `true`, treat calls to functions with visible definitions that are not
- explicitly declared as non-throwing (i.e. lack ``noexcept`` or ``throw()``)
- as potentially throwing, even if their bodies are visible and no explicit
- throw is found. Default value is `false`.
-
-.. option:: UnknownAsThrowing
-
- When `true`, treat calls to functions without visible definitions as
- potentially throwing. Default value is `false`.
+.. option:: TreatFunctionsWithoutSpecificationAsThrowing
+
+ Determines which functions are considered as throwing if they do not have
+ an explicit exception specification. It can be set to the following values:
+
+ ``None``
+ The check will not consider functions without explicitly declared exception
+ specification as throwing, unless they have a body which is visible to the
+ check and the check can deduce that the function throws.
+ ``OnlyUndefined``
+ The check will consider functions without visible definitions as throwing.
+ ``All``
+ The check will consider functions without visible definitions as throwing,
+ and will also consider calls to functions with visible definitions that
+ are not explicitly declared as non-throwing (i.e. lack ``noexcept`` or
+ ``throw()``) as throwing, even if their bodies are visible and no explicit
+ throw is found.
+
+ Default value is ``None``.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-known-unannotated-option.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-known-unannotated-option.cpp
deleted file mode 100644
index 7f72870f43ea9..0000000000000
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-known-unannotated-option.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-// RUN: %check_clang_tidy -std=c++11-or-later %s bugprone-exception-escape %t -- \
-// RUN: -config='{"CheckOptions": { \
-// RUN: "bugprone-exception-escape.KnownUnannotatedAsThrowing": true \
-// RUN: }}' -- -fexceptions
-
-void unannotated_no_throw_body() {}
-
-void calls_unannotated() noexcept {
- // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'calls_unannotated' which should not throw exceptions
- unannotated_no_throw_body();
-}
-
-void extern_declared();
-
-void calls_unknown() noexcept {
- // CHECK-MESSAGES-NOT: warning:
- extern_declared();
-}
-
-void definitely_nothrow() noexcept {}
-
-void calls_nothrow() noexcept {
- // CHECK-MESSAGES-NOT: warning:
- definitely_nothrow();
-}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-treat-functions-without-specification-as-throwing.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-treat-functions-without-specification-as-throwing.cpp
new file mode 100644
index 0000000000000..1a4da6237c230
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-treat-functions-without-specification-as-throwing.cpp
@@ -0,0 +1,47 @@
+// RUN: %check_clang_tidy -check-suffixes=ALL -std=c++11-or-later %s bugprone-exception-escape %t -- \
+// RUN: -config='{"CheckOptions": { \
+// RUN: "bugprone-exception-escape.TreatFunctionsWithoutSpecificationAsThrowing": "All" \
+// RUN: }}' -- -fexceptions
+// RUN: %check_clang_tidy -check-suffixes=UNDEFINED -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=NONE -std=c++11-or-later %s bugprone-exception-escape %t -- \
+// RUN: -config='{"CheckOptions": { \
+// RUN: "bugprone-exception-escape.TreatFunctionsWithoutSpecificationAsThrowing": "None" \
+// RUN: }}' -- -fexceptions
+
+void unannotated_no_throw_body() {}
+
+void calls_unannotated() noexcept {
+ // CHECK-MESSAGES-ALL: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'calls_unannotated' which should not throw exceptions
+ // CHECK-MESSAGES-UNDEFINED-NOT: warning:
+ // CHECK-MESSAGES-NONE-NOT: warning:
+ unannotated_no_throw_body();
+}
+
+void extern_declared();
+
+void calls_unknown() noexcept {
+ // CHECK-MESSAGES-ALL: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'calls_unknown' which should not throw exceptions
+ // CHECK-MESSAGES-UNDEFINED: :[[@LINE-2]]:6: warning: an exception may be thrown in function 'calls_unknown' which should not throw exceptions
+ // CHECK-MESSAGES-NONE-NOT: warning:
+ extern_declared();
+}
+
+void definitely_nothrow() noexcept {}
+
+void calls_nothrow() noexcept {
+ // CHECK-MESSAGES-ALL-NOT: warning:
+ // CHECK-MESSAGES-UNDEFINED-NOT: warning:
+ // CHECK-MESSAGES-NONE-NOT: warning:
+ definitely_nothrow();
+}
+
+void explicit_throw() { throw 1; }
+void calls_explicit_throw() noexcept {
+ // CHECK-MESSAGES-ALL: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'calls_explicit_throw' which should not throw exceptions
+ // CHECK-MESSAGES-UNDEFINED: :[[@LINE-2]]:6: warning: an exception may be thrown in function 'calls_explicit_throw' which should not throw exceptions
+ // CHECK-MESSAGES-NONE: :[[@LINE-3]]:6: warning: an exception may be thrown in function 'calls_explicit_throw' which should not throw exceptions
+ explicit_throw();
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-unannotated-option.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-unannotated-option.cpp
deleted file mode 100644
index c92eaa75c6adf..0000000000000
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-unannotated-option.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-// RUN: %check_clang_tidy -std=c++11-or-later %s bugprone-exception-escape %t -- \
-// RUN: -config='{"CheckOptions": { \
-// RUN: "bugprone-exception-escape.UnknownAsThrowing": true \
-// RUN: }}' -- -fexceptions
-
-void unannotated_no_throw_body() {}
-
-void calls_unannotated() noexcept {
- // CHECK-MESSAGES-NOT: warning:
- unannotated_no_throw_body();
-}
-
-void extern_declared();
-
-void calls_unknown() noexcept {
- // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: an exception may be thrown in function 'calls_unknown' which should not throw exceptions
- extern_declared();
-}
-
-void definitely_nothrow() noexcept {}
-
-void calls_nothrow() noexcept {
- // CHECK-MESSAGES-NOT: warning:
- definitely_nothrow();
-}
More information about the cfe-commits
mailing list