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 <[email protected]> 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
