[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
Fri Feb 6 20:47:02 PST 2026
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 01/11] [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 02/11] 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 03/11] 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 04/11] 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();
-}
>From e483b802f665d6d2b8db4180ba2e74a55c2d62b0 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Fri, 28 Nov 2025 00:35:06 +0800
Subject: [PATCH 05/11] [clang-tidy] Fix documentation formatting in
bugprone-exception-escape
---
.../docs/clang-tidy/checks/bugprone/exception-escape.rst | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
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 86fc0e69c3e7d..c5c4bd382b093 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
@@ -77,17 +77,17 @@ Options
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``
+ - `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``
+ - `OnlyUndefined`
The check will consider functions without visible definitions as throwing.
- ``All``
+ - `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``.
+ Default value is `None`.
>From ea6803f05e14d853fa65a7b18dad6adadde9f1fe Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Wed, 10 Dec 2025 03:24:46 +0800
Subject: [PATCH 06/11] Fix bug in clear()
---
clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp | 2 ++
1 file changed, 2 insertions(+)
diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
index 221f7711786de..14766d4cc9eb3 100644
--- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
+++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
@@ -455,6 +455,8 @@ ExceptionAnalyzer::ExceptionInfo::filterIgnoredExceptions(
void ExceptionAnalyzer::ExceptionInfo::clear() {
Behaviour = State::NotThrowing;
ContainsUnknown = false;
+ UnknownFromMissingDefinition = false;
+ UnknownFromKnownUnannotated = false;
ThrownExceptions.clear();
}
>From b6a5da4b74904520eacf9c28267e0099d2bc167f Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Wed, 10 Dec 2025 03:58:49 +0800
Subject: [PATCH 07/11] Add testcases
---
...eat-functions-without-specification-as-throwing.cpp | 10 ++++++++++
1 file changed, 10 insertions(+)
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
index 1a4da6237c230..eb76c69db0fdd 100644
--- 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
@@ -29,6 +29,16 @@ void calls_unknown() noexcept {
extern_declared();
}
+void calls_unknown_caught() noexcept {
+ // CHECK-MESSAGES-ALL-NOT: warning:
+ // CHECK-MESSAGES-UNDEFINED-NOT: warning:
+ // CHECK-MESSAGES-NONE-NOT: warning:
+ try {
+ extern_declared();
+ } catch(...) {
+ }
+}
+
void definitely_nothrow() noexcept {}
void calls_nothrow() noexcept {
>From 6ed55f9936ac906439dd0788211f989b1ab13799 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Tue, 16 Dec 2025 10:18:42 +0800
Subject: [PATCH 08/11] Cleanup (1/N)
---
.../bugprone/ExceptionEscapeCheck.cpp | 30 +++++++------
.../clang-tidy/utils/ExceptionAnalyzer.cpp | 19 ++++-----
.../clang-tidy/utils/ExceptionAnalyzer.h | 42 +++++++++----------
3 files changed, 42 insertions(+), 49 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
index 5f9cf84acf9c4..78e3f743f75c2 100644
--- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
@@ -80,9 +80,20 @@ ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name,
IgnoredExceptions.insert_range(IgnoredExceptionsVec);
Tracer.ignoreExceptions(std::move(IgnoredExceptions));
Tracer.ignoreBadAlloc(true);
- Tracer.assumeUnannotatedFunctionsThrow(
+
+ const auto MissingDefinitionsBehavior =
+ TreatFunctionsWithoutSpecificationAsThrowing !=
+ FunctionsThatShouldNotThrowPolicy::None
+ ? utils::ExceptionAnalyzer::UnknownHandlingBehavior::TreatAsThrowing
+ : utils::ExceptionAnalyzer::UnknownHandlingBehavior::Ignore;
+ Tracer.setMissingDefinitionsBehavior(MissingDefinitionsBehavior);
+
+ const auto UnannotatedFunctionsBehavior =
TreatFunctionsWithoutSpecificationAsThrowing ==
- FunctionsThatShouldNotThrowPolicy::All);
+ FunctionsThatShouldNotThrowPolicy::All
+ ? utils::ExceptionAnalyzer::UnknownHandlingBehavior::TreatAsThrowing
+ : utils::ExceptionAnalyzer::UnknownHandlingBehavior::Ignore;
+ Tracer.setUnannotatedFunctionsBehavior(UnannotatedFunctionsBehavior);
}
void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
@@ -131,20 +142,7 @@ void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) {
const utils::ExceptionAnalyzer::ExceptionInfo Info =
Tracer.analyze(MatchedDecl);
- const auto Behaviour = Info.getBehaviour();
- const bool IsThrowing =
- Behaviour == utils::ExceptionAnalyzer::State::Throwing;
- const bool IsUnknown = Behaviour == utils::ExceptionAnalyzer::State::Unknown;
-
- const bool ReportUnknown =
- IsUnknown && ((TreatFunctionsWithoutSpecificationAsThrowing ==
- FunctionsThatShouldNotThrowPolicy::All &&
- Info.hasUnknownFromKnownUnannotated()) ||
- (TreatFunctionsWithoutSpecificationAsThrowing !=
- FunctionsThatShouldNotThrowPolicy::None &&
- Info.hasUnknownFromMissingDefinition()));
-
- if (!(IsThrowing || ReportUnknown))
+ if (Info.getBehaviour() != utils::ExceptionAnalyzer::State::Throwing)
return;
diag(MatchedDecl->getLocation(), "an exception may be thrown in function "
diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
index 14766d4cc9eb3..e3f7382a48a99 100644
--- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
+++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
@@ -39,10 +39,7 @@ ExceptionAnalyzer::ExceptionInfo &ExceptionAnalyzer::ExceptionInfo::merge(
Behaviour = State::Unknown;
ContainsUnknown = ContainsUnknown || Other.ContainsUnknown;
- UnknownFromMissingDefinition =
- UnknownFromMissingDefinition || Other.UnknownFromMissingDefinition;
- UnknownFromKnownUnannotated =
- UnknownFromKnownUnannotated || Other.UnknownFromKnownUnannotated;
+ ThrowsUnknown = ThrowsUnknown || Other.ThrowsUnknown;
ThrownExceptions.insert_range(Other.ThrownExceptions);
return *this;
}
@@ -455,13 +452,12 @@ ExceptionAnalyzer::ExceptionInfo::filterIgnoredExceptions(
void ExceptionAnalyzer::ExceptionInfo::clear() {
Behaviour = State::NotThrowing;
ContainsUnknown = false;
- UnknownFromMissingDefinition = false;
- UnknownFromKnownUnannotated = false;
+ ThrowsUnknown = false;
ThrownExceptions.clear();
}
void ExceptionAnalyzer::ExceptionInfo::reevaluateBehaviour() {
- if (ThrownExceptions.empty())
+ if (ThrownExceptions.empty() && !ThrowsUnknown)
if (ContainsUnknown)
Behaviour = State::Unknown;
else
@@ -494,15 +490,16 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
// 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;
+ Result.registerUnknownException();
+ return Result;
}
return Result;
}
auto Result = ExceptionInfo::createUnknown();
- Result.markUnknownFromMissingDefinition();
+ if (MissingDefinitionsAreThrowing)
+ Result.registerUnknownException();
+
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 c0356b71383fb..fc755a91213bd 100644
--- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
+++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
@@ -67,22 +67,6 @@ 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,
@@ -127,6 +111,12 @@ class ExceptionAnalyzer {
/// occur. If there is an 'Unknown' element this can not be guaranteed.
bool containsUnknownElements() const { return ContainsUnknown; }
+ void registerUnknownException() {
+ Behaviour = State::Throwing;
+ ThrowsUnknown = true;
+ ContainsUnknown = true;
+ }
+
private:
/// Recalculate the 'Behaviour' for example after filtering.
void reevaluateBehaviour();
@@ -140,8 +130,9 @@ class ExceptionAnalyzer {
/// after filtering.
bool ContainsUnknown;
- bool UnknownFromMissingDefinition = false;
- bool UnknownFromKnownUnannotated = false;
+ /// True if the entity is determined to be Throwing due to an unknown cause,
+ /// based on analyzer configuration.
+ bool ThrowsUnknown = false;
/// 'ThrownException' is empty if the 'Behaviour' is either 'NotThrowing' or
/// 'Unknown'.
@@ -149,10 +140,16 @@ class ExceptionAnalyzer {
};
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;
+
+ enum class UnknownHandlingBehavior { Ignore, TreatAsThrowing };
+
+ void setUnannotatedFunctionsBehavior(UnknownHandlingBehavior Behavior) {
+ AssumeUnannotatedThrowing =
+ Behavior == UnknownHandlingBehavior::TreatAsThrowing;
+ }
+ void setMissingDefinitionsBehavior(UnknownHandlingBehavior Behavior) {
+ MissingDefinitionsAreThrowing =
+ Behavior == UnknownHandlingBehavior::TreatAsThrowing;
}
void ignoreBadAlloc(bool ShallIgnore) { IgnoreBadAlloc = ShallIgnore; }
@@ -179,6 +176,7 @@ class ExceptionAnalyzer {
llvm::StringSet<> IgnoredExceptions;
llvm::DenseMap<const FunctionDecl *, ExceptionInfo> FunctionCache{32U};
bool AssumeUnannotatedThrowing = false;
+ bool MissingDefinitionsAreThrowing = false;
};
} // namespace clang::tidy::utils
>From afd4ed76419a670cfaa681515c8cf3334d1ce1e4 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Mon, 19 Jan 2026 18:11:31 +0800
Subject: [PATCH 09/11] Address Review Feedback
---
.../clang-tidy/utils/ExceptionAnalyzer.cpp | 10 ++++++----
.../clang-tidy/utils/ExceptionAnalyzer.h | 8 ++++----
clang-tools-extra/docs/ReleaseNotes.rst | 10 ++++++----
...nctions-without-specification-as-throwing.cpp | 16 ++++++++++++++--
4 files changed, 30 insertions(+), 14 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
index 6cdc554e7f9c4..3f8bca6ac4d87 100644
--- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
+++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
@@ -487,17 +487,14 @@ 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 &&
+ if (AssumeUnannotatedFunctionsAsThrowing &&
Result.getBehaviour() == State::NotThrowing && canThrow(Func)) {
Result.registerUnknownException();
- return Result;
}
return Result;
}
auto Result = ExceptionInfo::createUnknown();
- if (MissingDefinitionsAreThrowing)
- Result.registerUnknownException();
if (const auto *FPT = Func->getType()->getAs<FunctionProtoType>()) {
for (const QualType &Ex : FPT->exceptions()) {
@@ -508,6 +505,11 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
CallStack.erase(Func);
}
}
+
+ if (AssumeMissingDefinitionsFunctionsAsThrowing &&
+ Result.getBehaviour() == State::Unknown)
+ Result.registerUnknownException();
+
return Result;
}
diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
index fc755a91213bd..790a2e970e9d5 100644
--- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
+++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
@@ -144,11 +144,11 @@ class ExceptionAnalyzer {
enum class UnknownHandlingBehavior { Ignore, TreatAsThrowing };
void setUnannotatedFunctionsBehavior(UnknownHandlingBehavior Behavior) {
- AssumeUnannotatedThrowing =
+ AssumeUnannotatedFunctionsAsThrowing =
Behavior == UnknownHandlingBehavior::TreatAsThrowing;
}
void setMissingDefinitionsBehavior(UnknownHandlingBehavior Behavior) {
- MissingDefinitionsAreThrowing =
+ AssumeMissingDefinitionsFunctionsAsThrowing =
Behavior == UnknownHandlingBehavior::TreatAsThrowing;
}
@@ -175,8 +175,8 @@ class ExceptionAnalyzer {
bool IgnoreBadAlloc = true;
llvm::StringSet<> IgnoredExceptions;
llvm::DenseMap<const FunctionDecl *, ExceptionInfo> FunctionCache{32U};
- bool AssumeUnannotatedThrowing = false;
- bool MissingDefinitionsAreThrowing = false;
+ bool AssumeUnannotatedFunctionsAsThrowing = false;
+ bool AssumeMissingDefinitionsFunctionsAsThrowing = false;
};
} // namespace clang::tidy::utils
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 694fb4dde8216..a2781f320b4a0 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -104,10 +104,12 @@ Changes in existing checks
^^^^^^^^^^^^^^^^^^^^^^^^^^
- Improved :doc:`bugprone-exception-escape
- <clang-tidy/checks/bugprone/exception-escape>` check by adding
- `TreatFunctionsWithoutSpecificationAsThrowing` 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` option 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:`misc-const-correctness
<clang-tidy/checks/misc/const-correctness>` check:
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
index eb76c69db0fdd..6e9aa03323ec7 100644
--- 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
@@ -48,10 +48,22 @@ void calls_nothrow() noexcept {
definitely_nothrow();
}
+void nothrow_nobody() throw();
+
+void call() noexcept {
+ nothrow_nobody();
+}
+
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
+ // CHECK-MESSAGES-ALL: :[[@LINE-3]]:25: note: frame #0: unhandled exception of type 'int' may be thrown in function 'explicit_throw' here
+ // CHECK-MESSAGES-ALL: :[[@LINE+7]]:3: note: frame #1: function 'calls_explicit_throw' calls function 'explicit_throw' here
+ // CHECK-MESSAGES-UNDEFINED: :[[@LINE-4]]:6: warning: an exception may be thrown in function 'calls_explicit_throw' which should not throw exceptions
+ // CHECK-MESSAGES-UNDEFINED: :[[@LINE-6]]:25: note: frame #0: unhandled exception of type 'int' may be thrown in function 'explicit_throw' here
+ // CHECK-MESSAGES-UNDEFINED: :[[@LINE+4]]:3: note: frame #1: function 'calls_explicit_throw' calls function 'explicit_throw' here
+ // CHECK-MESSAGES-NONE: :[[@LINE-7]]:6: warning: an exception may be thrown in function 'calls_explicit_throw' which should not throw exceptions
+ // CHECK-MESSAGES-NONE: :[[@LINE-9]]:25: note: frame #0: unhandled exception of type 'int' may be thrown in function 'explicit_throw' here
+ // CHECK-MESSAGES-NONE: :[[@LINE+1]]:3: note: frame #1: function 'calls_explicit_throw' calls function 'explicit_throw' here
explicit_throw();
}
>From c3aa2efc30348414e8934aec9209da60e4fe0b10 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Fri, 6 Feb 2026 17:41:57 +0800
Subject: [PATCH 10/11] Address review feedback
---
.../bugprone/ExceptionEscapeCheck.cpp | 34 ++++++++-----------
.../bugprone/ExceptionEscapeCheck.h | 4 +--
.../clang-tidy/utils/ExceptionAnalyzer.h | 12 +++----
.../checks/bugprone/exception-escape.rst | 17 +++++-----
4 files changed, 30 insertions(+), 37 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
index aff86aa4600ff..41e0cdaf6ee61 100644
--- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
@@ -17,17 +17,19 @@ namespace clang::tidy {
template <>
struct OptionEnumMapping<
- bugprone::ExceptionEscapeCheck::FunctionsThatShouldNotThrowPolicy> {
- using FunctionsThatShouldNotThrowPolicy =
- bugprone::ExceptionEscapeCheck::FunctionsThatShouldNotThrowPolicy;
+ bugprone::ExceptionEscapeCheck::TreatFunctionsWithoutSpecification> {
+ using TreatFunctionsWithoutSpecification =
+ bugprone::ExceptionEscapeCheck::TreatFunctionsWithoutSpecification;
- static llvm::ArrayRef<std::pair<FunctionsThatShouldNotThrowPolicy, StringRef>>
+ static llvm::ArrayRef<
+ std::pair<TreatFunctionsWithoutSpecification, StringRef>>
getEnumMapping() {
- static constexpr std::pair<FunctionsThatShouldNotThrowPolicy, StringRef>
+ static constexpr std::pair<TreatFunctionsWithoutSpecification, StringRef>
Mapping[] = {
- {FunctionsThatShouldNotThrowPolicy::None, "None"},
- {FunctionsThatShouldNotThrowPolicy::OnlyUndefined, "OnlyUndefined"},
- {FunctionsThatShouldNotThrowPolicy::All, "All"},
+ {TreatFunctionsWithoutSpecification::None, "None"},
+ {TreatFunctionsWithoutSpecification::OnlyUndefined,
+ "OnlyUndefined"},
+ {TreatFunctionsWithoutSpecification::All, "All"},
};
return {Mapping};
}
@@ -65,7 +67,7 @@ ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name,
CheckNothrowFunctions(Options.get("CheckNothrowFunctions", true)),
TreatFunctionsWithoutSpecificationAsThrowing(
Options.get("TreatFunctionsWithoutSpecificationAsThrowing",
- FunctionsThatShouldNotThrowPolicy::None)) {
+ TreatFunctionsWithoutSpecification::None)) {
llvm::SmallVector<StringRef, 8> FunctionsThatShouldNotThrowVec,
IgnoredExceptionsVec, CheckedSwapFunctionsVec;
RawFunctionsThatShouldNotThrow.split(FunctionsThatShouldNotThrowVec, ",", -1,
@@ -81,19 +83,13 @@ ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name,
Tracer.ignoreExceptions(std::move(IgnoredExceptions));
Tracer.ignoreBadAlloc(true);
- const auto MissingDefinitionsBehavior =
+ Tracer.assumeMissingDefinitionsFunctionsAsThrowing(
TreatFunctionsWithoutSpecificationAsThrowing !=
- FunctionsThatShouldNotThrowPolicy::None
- ? utils::ExceptionAnalyzer::UnknownHandlingBehavior::TreatAsThrowing
- : utils::ExceptionAnalyzer::UnknownHandlingBehavior::Ignore;
- Tracer.setMissingDefinitionsBehavior(MissingDefinitionsBehavior);
+ TreatFunctionsWithoutSpecification::None);
- const auto UnannotatedFunctionsBehavior =
+ Tracer.assumeUnannotatedFunctionsAsThrowing(
TreatFunctionsWithoutSpecificationAsThrowing ==
- FunctionsThatShouldNotThrowPolicy::All
- ? utils::ExceptionAnalyzer::UnknownHandlingBehavior::TreatAsThrowing
- : utils::ExceptionAnalyzer::UnknownHandlingBehavior::Ignore;
- Tracer.setUnannotatedFunctionsBehavior(UnannotatedFunctionsBehavior);
+ TreatFunctionsWithoutSpecification::All);
}
void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h
index 38befa5a99957..7c970aa57e2ef 100644
--- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h
+++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h
@@ -32,7 +32,7 @@ class ExceptionEscapeCheck : public ClangTidyCheck {
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
- enum class FunctionsThatShouldNotThrowPolicy {
+ enum class TreatFunctionsWithoutSpecification {
None,
OnlyUndefined,
All,
@@ -48,7 +48,7 @@ class ExceptionEscapeCheck : public ClangTidyCheck {
const bool CheckMain;
const bool CheckNothrowFunctions;
- const FunctionsThatShouldNotThrowPolicy
+ const TreatFunctionsWithoutSpecification
TreatFunctionsWithoutSpecificationAsThrowing;
llvm::StringSet<> FunctionsThatShouldNotThrow;
diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
index 790a2e970e9d5..08479ef58240a 100644
--- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
+++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
@@ -141,15 +141,13 @@ class ExceptionAnalyzer {
ExceptionAnalyzer() = default;
- enum class UnknownHandlingBehavior { Ignore, TreatAsThrowing };
-
- void setUnannotatedFunctionsBehavior(UnknownHandlingBehavior Behavior) {
- AssumeUnannotatedFunctionsAsThrowing =
- Behavior == UnknownHandlingBehavior::TreatAsThrowing;
+ void assumeUnannotatedFunctionsAsThrowing(bool AssumeUnannotatedAsThrowing) {
+ AssumeUnannotatedFunctionsAsThrowing = AssumeUnannotatedAsThrowing;
}
- void setMissingDefinitionsBehavior(UnknownHandlingBehavior Behavior) {
+ void assumeMissingDefinitionsFunctionsAsThrowing(
+ bool AssumeMissingDefinitionsAsThrowing) {
AssumeMissingDefinitionsFunctionsAsThrowing =
- Behavior == UnknownHandlingBehavior::TreatAsThrowing;
+ AssumeMissingDefinitionsAsThrowing;
}
void ignoreBadAlloc(bool ShallIgnore) { IgnoreBadAlloc = ShallIgnore; }
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 c5c4bd382b093..8bd05cb759cae 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
@@ -78,16 +78,15 @@ Options
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.
+ The check will consider functions without an explicit exception
+ specification as throwing only if they have a visible definition which
+ can be deduced to throw.
- `OnlyUndefined`
- The check will consider functions without visible definitions as throwing.
+ The check will consider functions with only a declaration available 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.
+ The check will consider all functions without an explicit exception
+ specification (such as ``noexcept``) as throwing, even if they have a
+ visible definition and do not contain any throwing statements.
Default value is `None`.
>From 06f992a4e3f65b34179f534554c12a92ee963538 Mon Sep 17 00:00:00 2001
From: mitchell <mitchell.xu2 at gmail.com>
Date: Sat, 7 Feb 2026 12:46:46 +0800
Subject: [PATCH 11/11] Apply suggestion from @vbvictor
Co-authored-by: Baranov Victor <bar.victor.2002 at gmail.com>
---
.../docs/clang-tidy/checks/bugprone/exception-escape.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
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 8bd05cb759cae..a655661c57871 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
@@ -82,8 +82,8 @@ Options
specification as throwing only if they have a visible definition which
can be deduced to throw.
- `OnlyUndefined`
- The check will consider functions with only a declaration available as
- throwing.
+ The check will consider functions with only a declaration available and
+ no visible definition as throwing.
- `All`
The check will consider all functions without an explicit exception
specification (such as ``noexcept``) as throwing, even if they have a
More information about the cfe-commits
mailing list