https://github.com/zeyi2 updated https://github.com/llvm/llvm-project/pull/187971
>From c2add4b0e75cacf95491dcd52078f38c62daec98 Mon Sep 17 00:00:00 2001 From: mtx <[email protected]> Date: Mon, 23 Mar 2026 12:45:53 +0800 Subject: [PATCH 1/4] [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 >From cd6ae99635bc537f6cac2b5220468d1af1021882 Mon Sep 17 00:00:00 2001 From: mtx <[email protected]> Date: Tue, 24 Mar 2026 11:33:46 +0800 Subject: [PATCH 2/4] better? --- clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp index 9bffa5d1cf441..306fb3a82d0be 100644 --- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp +++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp @@ -487,15 +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 (AssumeUnannotatedFunctionsAsThrowing && Result.getBehaviour() == State::NotThrowing && canThrow(Func)) { - CallStack.insert({Func, CallLoc}); Result.registerUnknownException({Func->getLocation(), CallStack}); - CallStack.erase(Func); } + + CallStack.erase(Func); return Result; } >From 69f8d9b87c8c32772c9549387058697f4395437f Mon Sep 17 00:00:00 2001 From: mtx <[email protected]> Date: Sat, 28 Mar 2026 19:19:14 +0800 Subject: [PATCH 3/4] add fixme --- ...-escape-treat-functions-without-specification-as-throwing.cpp | 1 + 1 file changed, 1 insertion(+) 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 d9d6b2d922511..6f955fa5a012a 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 @@ -80,6 +80,7 @@ struct S { // 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 + // FIXME: clearly non-throwing functions should not be marked as throwing Member m; }; >From fa9757b08d1928002ed39ff35ce3b0fce21d39a8 Mon Sep 17 00:00:00 2001 From: mtx <[email protected]> Date: Sun, 5 Apr 2026 19:18:40 +0800 Subject: [PATCH 4/4] ~ --- .../bugprone/ExceptionEscapeCheck.cpp | 29 +++++++++---------- .../clang-tidy/utils/ExceptionAnalyzer.cpp | 11 +++---- .../clang-tidy/utils/ExceptionAnalyzer.h | 14 --------- 3 files changed, 17 insertions(+), 37 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp index 07b7eb9a8ba94..96a11c0d1bdd8 100644 --- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp @@ -146,35 +146,32 @@ void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) { "%0 which should not throw exceptions") << MatchedDecl; - 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()) + if (Info.getExceptions().empty()) return; - if (!Exceptions.empty()) { - const auto &[ThrowType, ThrowInfo] = *Exceptions.begin(); + const auto &[ThrowType, ThrowInfo] = *Info.getExceptions().begin(); + + if (ThrowInfo.Loc.isInvalid()) + return; + + const utils::ExceptionAnalyzer::CallStack &Stack = ThrowInfo.Stack; + if (ThrowType) { 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; + << QualType(ThrowType, 0U) << Stack.back().first; } else { - diag(TI->Loc, + diag(ThrowInfo.Loc, "frame #0: an exception of unknown type may be thrown in function %0 " "here", DiagnosticIDs::Note) - << TI->Stack.back().first; + << Stack.back().first; } size_t FrameNo = 1; - for (auto CurrIt = ++TI->Stack.rbegin(), PrevIt = TI->Stack.rbegin(); - CurrIt != TI->Stack.rend(); ++CurrIt, ++PrevIt) { + 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; diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp index 306fb3a82d0be..203eaa90419d8 100644 --- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp +++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.cpp @@ -12,7 +12,6 @@ namespace clang::tidy::utils { void ExceptionAnalyzer::ExceptionInfo::registerException( const Type *ExceptionType, const ThrowInfo &ThrowInfo) { - assert(ExceptionType != nullptr && "Only valid types are accepted"); Behaviour = State::Throwing; ThrownExceptions.insert({ExceptionType, ThrowInfo}); } @@ -39,9 +38,6 @@ 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; } @@ -434,6 +430,8 @@ ExceptionAnalyzer::ExceptionInfo::filterIgnoredExceptions( // Therefore this slightly hacky implementation is required. for (const auto &ThrownException : ThrownExceptions) { const Type *T = ThrownException.getFirst(); + if (!T) + continue; if (const auto *TD = T->getAsTagDecl()) { if (TD->getDeclName().isIdentifier()) { if ((IgnoreBadAlloc && @@ -454,7 +452,6 @@ void ExceptionAnalyzer::ExceptionInfo::clear() { Behaviour = State::NotThrowing; ContainsUnknown = false; ThrowsUnknown = false; - UnknownThrowInfo = {}; ThrownExceptions.clear(); } @@ -491,7 +488,7 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException( // are not explicitly non-throwing and no throw was discovered. if (AssumeUnannotatedFunctionsAsThrowing && Result.getBehaviour() == State::NotThrowing && canThrow(Func)) { - Result.registerUnknownException({Func->getLocation(), CallStack}); + Result.registerException(nullptr, {Func->getLocation(), CallStack}); } CallStack.erase(Func); @@ -513,7 +510,7 @@ ExceptionAnalyzer::ExceptionInfo ExceptionAnalyzer::throwsException( if (AssumeMissingDefinitionsFunctionsAsThrowing && Result.getBehaviour() == State::Unknown) { CallStack.insert({Func, CallLoc}); - Result.registerUnknownException({Func->getLocation(), CallStack}); + Result.registerException(nullptr, {Func->getLocation(), CallStack}); CallStack.erase(Func); } diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h index c433cdb5d5193..08479ef58240a 100644 --- a/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h +++ b/clang-tools-extra/clang-tidy/utils/ExceptionAnalyzer.h @@ -117,16 +117,6 @@ 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(); @@ -144,10 +134,6 @@ 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; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
