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