[clang-tools-extra] [clang-tidy] Add frames for bugprone-exception-escape options (PR #187971)
Zeyi Xu via cfe-commits
cfe-commits at lists.llvm.org
Sun Mar 22 21:46:10 PDT 2026
https://github.com/zeyi2 created https://github.com/llvm/llvm-project/pull/187971
None
>From c2add4b0e75cacf95491dcd52078f38c62daec98 Mon Sep 17 00:00:00 2001
From: mtx <mitchell.xu2 at gmail.com>
Date: Mon, 23 Mar 2026 12:45:53 +0800
Subject: [PATCH] [clang-tidy] Add frames for bugprone-exception-escape options
---
.../bugprone/ExceptionEscapeCheck.cpp | 39 ++++++++++++-------
.../clang-tidy/utils/ExceptionAnalyzer.cpp | 14 +++++--
.../clang-tidy/utils/ExceptionAnalyzer.h | 14 +++++++
...ions-without-specification-as-throwing.cpp | 31 ++++++++++++++-
4 files changed, 80 insertions(+), 18 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
index 41e0cdaf6ee61..07b7eb9a8ba94 100644
--- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp
@@ -146,24 +146,35 @@ void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) {
"%0 which should not throw exceptions")
<< MatchedDecl;
- if (Info.getExceptions().empty())
+ const utils::ExceptionAnalyzer::ExceptionInfo::Throwables &Exceptions =
+ Info.getExceptions();
+ const utils::ExceptionAnalyzer::ExceptionInfo::ThrowInfo *TI = nullptr;
+ if (!Exceptions.empty())
+ TI = &Exceptions.begin()->second;
+ else if (Info.containsUnknownElements())
+ TI = &Info.getUnknownThrowInfo();
+
+ if (!TI || TI->Loc.isInvalid())
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;
+ if (!Exceptions.empty()) {
+ const auto &[ThrowType, ThrowInfo] = *Exceptions.begin();
+ diag(ThrowInfo.Loc,
+ "frame #0: unhandled exception of type %0 may be thrown in function "
+ "%1 here",
+ DiagnosticIDs::Note)
+ << QualType(ThrowType, 0U) << ThrowInfo.Stack.back().first;
+ } else {
+ diag(TI->Loc,
+ "frame #0: an exception of unknown type may be thrown in function %0 "
+ "here",
+ DiagnosticIDs::Note)
+ << TI->Stack.back().first;
+ }
size_t FrameNo = 1;
- for (auto CurrIt = ++Stack.rbegin(), PrevIt = Stack.rbegin();
- CurrIt != Stack.rend(); ++CurrIt, ++PrevIt) {
+ for (auto CurrIt = ++TI->Stack.rbegin(), PrevIt = TI->Stack.rbegin();
+ CurrIt != TI->Stack.rend(); ++CurrIt, ++PrevIt) {
const FunctionDecl *CurrFunction = CurrIt->first;
const FunctionDecl *PrevFunction = PrevIt->first;
const SourceLocation PrevLocation = PrevIt->second;
diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
index 60dade82e6155..9bffa5d1cf441 100644
--- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
+++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp
@@ -39,6 +39,8 @@ ExceptionAnalyzer::ExceptionInfo &ExceptionAnalyzer::ExceptionInfo::merge(
Behaviour = State::Unknown;
ContainsUnknown = ContainsUnknown || Other.ContainsUnknown;
+ if (!ThrowsUnknown && Other.ThrowsUnknown)
+ UnknownThrowInfo = Other.UnknownThrowInfo;
ThrowsUnknown = ThrowsUnknown || Other.ThrowsUnknown;
ThrownExceptions.insert_range(Other.ThrownExceptions);
return *this;
@@ -452,6 +454,7 @@ void ExceptionAnalyzer::ExceptionInfo::clear() {
Behaviour = State::NotThrowing;
ContainsUnknown = false;
ThrowsUnknown = false;
+ UnknownThrowInfo = {};
ThrownExceptions.clear();
}
@@ -489,7 +492,9 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
// are not explicitly non-throwing and no throw was discovered.
if (AssumeUnannotatedFunctionsAsThrowing &&
Result.getBehaviour() == State::NotThrowing && canThrow(Func)) {
- Result.registerUnknownException();
+ CallStack.insert({Func, CallLoc});
+ Result.registerUnknownException({Func->getLocation(), CallStack});
+ CallStack.erase(Func);
}
return Result;
}
@@ -507,8 +512,11 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException(
}
if (AssumeMissingDefinitionsFunctionsAsThrowing &&
- Result.getBehaviour() == State::Unknown)
- Result.registerUnknownException();
+ Result.getBehaviour() == State::Unknown) {
+ CallStack.insert({Func, CallLoc});
+ Result.registerUnknownException({Func->getLocation(), CallStack});
+ CallStack.erase(Func);
+ }
return Result;
}
diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
index 08479ef58240a..c433cdb5d5193 100644
--- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
+++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h
@@ -117,6 +117,16 @@ class ExceptionAnalyzer {
ContainsUnknown = true;
}
+ /// Mark the entity as throwing due to an unknown cause and record Info
+ /// as the location for diagnostic notes.
+ void registerUnknownException(ThrowInfo Info) {
+ registerUnknownException();
+ UnknownThrowInfo = std::move(Info);
+ }
+
+ /// Return the location info recorded for the unknown throw, if any.
+ const ThrowInfo &getUnknownThrowInfo() const { return UnknownThrowInfo; }
+
private:
/// Recalculate the 'Behaviour' for example after filtering.
void reevaluateBehaviour();
@@ -134,6 +144,10 @@ class ExceptionAnalyzer {
/// based on analyzer configuration.
bool ThrowsUnknown = false;
+ /// Location info for the assumed-throwing function when ThrowsUnknown is
+ /// true. May be invalid if no location is available.
+ ThrowInfo UnknownThrowInfo;
+
/// 'ThrownException' is empty if the 'Behaviour' is either 'NotThrowing' or
/// 'Unknown'.
Throwables ThrownExceptions;
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 6e9aa03323ec7..d9d6b2d922511 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
@@ -15,6 +15,8 @@ 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-ALL: :[[@LINE-4]]:6: note: frame #0: an exception of unknown type may be thrown in function 'unannotated_no_throw_body' here
+ // CHECK-MESSAGES-ALL: :[[@LINE+3]]:3: note: frame #1: function 'calls_unannotated' calls function 'unannotated_no_throw_body' here
// CHECK-MESSAGES-UNDEFINED-NOT: warning:
// CHECK-MESSAGES-NONE-NOT: warning:
unannotated_no_throw_body();
@@ -24,7 +26,11 @@ 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-ALL: :[[@LINE-4]]:6: note: frame #0: an exception of unknown type may be thrown in function 'extern_declared' here
+ // CHECK-MESSAGES-ALL: :[[@LINE+5]]:3: note: frame #1: function 'calls_unknown' calls function 'extern_declared' here
+ // CHECK-MESSAGES-UNDEFINED: :[[@LINE-4]]:6: warning: an exception may be thrown in function 'calls_unknown' which should not throw exceptions
+ // CHECK-MESSAGES-UNDEFINED: :[[@LINE-7]]:6: note: frame #0: an exception of unknown type may be thrown in function 'extern_declared' here
+ // CHECK-MESSAGES-UNDEFINED: :[[@LINE+2]]:3: note: frame #1: function 'calls_unknown' calls function 'extern_declared' here
// CHECK-MESSAGES-NONE-NOT: warning:
extern_declared();
}
@@ -54,6 +60,29 @@ void call() noexcept {
nothrow_nobody();
}
+struct Member {
+ Member() noexcept {}
+ Member(const Member &) noexcept {}
+ Member &operator=(const Member &) noexcept { return *this; }
+ ~Member() noexcept {}
+};
+
+struct S {
+ // CHECK-MESSAGES-ALL: :[[@LINE-1]]:8: warning: an exception may be thrown in function 'S' which should not throw exceptions
+ // CHECK-MESSAGES-ALL: :[[@LINE-2]]:8: note: frame #0: an exception of unknown type may be thrown in function 'S' here
+ // CHECK-MESSAGES-ALL: :[[@LINE-3]]:8: warning: an exception may be thrown in function 'operator=' which should not throw exceptions
+ // CHECK-MESSAGES-ALL: :[[@LINE-4]]:8: note: frame #0: an exception of unknown type may be thrown in function 'operator=' here
+ // CHECK-MESSAGES-ALL: :[[@LINE-5]]:8: warning: an exception may be thrown in function '~S' which should not throw exceptions
+ // CHECK-MESSAGES-ALL: :[[@LINE-6]]:8: note: frame #0: an exception of unknown type may be thrown in function '~S' here
+ // CHECK-MESSAGES-UNDEFINED: :[[@LINE-7]]:8: warning: an exception may be thrown in function 'S' which should not throw exceptions
+ // CHECK-MESSAGES-UNDEFINED: :[[@LINE-8]]:8: note: frame #0: an exception of unknown type may be thrown in function 'S' here
+ // CHECK-MESSAGES-UNDEFINED: :[[@LINE-9]]:8: warning: an exception may be thrown in function 'operator=' which should not throw exceptions
+ // CHECK-MESSAGES-UNDEFINED: :[[@LINE-10]]:8: note: frame #0: an exception of unknown type may be thrown in function 'operator=' here
+ // CHECK-MESSAGES-UNDEFINED: :[[@LINE-11]]:8: warning: an exception may be thrown in function '~S' which should not throw exceptions
+ // CHECK-MESSAGES-UNDEFINED: :[[@LINE-12]]:8: note: frame #0: an exception of unknown type may be thrown in function '~S' here
+ Member m;
+};
+
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
More information about the cfe-commits
mailing list