From 9dbd3608577c7ec4887a612c633b3121f3306f62 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Sat, 18 Oct 2025 15:52:12 +0300 Subject: [PATCH 1/4] [clang-tidy] Add fine-graded configuration for 'bugprone-exception-escape' --- .../bugprone/ExceptionEscapeCheck.cpp | 39 +++++++++++---- .../bugprone/ExceptionEscapeCheck.h | 7 +++ clang-tools-extra/docs/ReleaseNotes.rst | 4 +- .../checks/bugprone/exception-escape.rst | 25 ++++++++++ .../bugprone/exception-escape-options.cpp | 49 +++++++++++++++++++ 5 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-options.cpp diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp index 837a86ff8655e..b07aad09ed2af 100644 --- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp @@ -36,13 +36,22 @@ ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), RawFunctionsThatShouldNotThrow(Options.get( "FunctionsThatShouldNotThrow", "")), - RawIgnoredExceptions(Options.get("IgnoredExceptions", "")) { + RawIgnoredExceptions(Options.get("IgnoredExceptions", "")), + RawCheckedSwapFunctions( + Options.get("CheckedSwapFunctions", "swap,iter_swap,iter_move")), + CheckDestructors(Options.get("CheckDestructors", true)), + CheckMoveMemberFunctions(Options.get("CheckMoveMemberFunctions", true)), + CheckMain(Options.get("CheckMain", true)), + CheckNothrowFunctions(Options.get("CheckNothrowFunctions", true)) { llvm::SmallVector FunctionsThatShouldNotThrowVec, - IgnoredExceptionsVec; + IgnoredExceptionsVec, CheckedSwapFunctionsVec; RawFunctionsThatShouldNotThrow.split(FunctionsThatShouldNotThrowVec, ",", -1, false); FunctionsThatShouldNotThrow.insert_range(FunctionsThatShouldNotThrowVec); + RawCheckedSwapFunctions.split(CheckedSwapFunctionsVec, ",", -1, false); + CheckedSwapFunctions.insert_range(CheckedSwapFunctionsVec); + llvm::StringSet<> IgnoredExceptions; RawIgnoredExceptions.split(IgnoredExceptionsVec, ",", -1, false); IgnoredExceptions.insert_range(IgnoredExceptionsVec); @@ -54,20 +63,30 @@ void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "FunctionsThatShouldNotThrow", RawFunctionsThatShouldNotThrow); Options.store(Opts, "IgnoredExceptions", RawIgnoredExceptions); + Options.store(Opts, "CheckedSwapFunctions", RawCheckedSwapFunctions); + Options.store(Opts, "CheckDestructors", CheckDestructors); + Options.store(Opts, "CheckMoveMemberFunctions", CheckMoveMemberFunctions); + Options.store(Opts, "CheckMain", CheckMain); + Options.store(Opts, "CheckNothrowFunctions", CheckNothrowFunctions); } void ExceptionEscapeCheck::registerMatchers(MatchFinder *Finder) { + ast_matchers::internal::Matcher Nothing = unless(anything()); Finder->addMatcher( functionDecl( isDefinition(), - anyOf(isNoThrow(), - allOf(anyOf(cxxDestructorDecl(), - cxxConstructorDecl(isMoveConstructor()), - cxxMethodDecl(isMoveAssignmentOperator()), isMain(), - allOf(hasAnyName("swap", "iter_swap", "iter_move"), - hasAtLeastOneParameter())), - unless(isExplicitThrow())), - isEnabled(FunctionsThatShouldNotThrow))) + anyOf( + CheckNothrowFunctions ? isNoThrow() : Nothing, + allOf(anyOf(CheckDestructors ? cxxDestructorDecl() : Nothing, + CheckMoveMemberFunctions + ? anyOf(cxxConstructorDecl(isMoveConstructor()), + cxxMethodDecl(isMoveAssignmentOperator())) + : Nothing, + CheckMain ? isMain() : Nothing, + allOf(isEnabled(CheckedSwapFunctions), + hasAtLeastOneParameter())), + unless(isExplicitThrow())), + isEnabled(FunctionsThatShouldNotThrow))) .bind("thrower"), this); } diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h index 31d9e85082c52..c3bf4a4335273 100644 --- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h +++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.h @@ -35,8 +35,15 @@ class ExceptionEscapeCheck : public ClangTidyCheck { private: StringRef RawFunctionsThatShouldNotThrow; StringRef RawIgnoredExceptions; + StringRef RawCheckedSwapFunctions; + + const bool CheckDestructors; + const bool CheckMoveMemberFunctions; + const bool CheckMain; + const bool CheckNothrowFunctions; llvm::StringSet<> FunctionsThatShouldNotThrow; + llvm::StringSet<> CheckedSwapFunctions; utils::ExceptionAnalyzer Tracer; }; diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 48a2a1f5d39d5..1e2053225ee27 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -302,7 +302,9 @@ Changes in existing checks exceptions from captures are now diagnosed, exceptions in the bodies of lambdas that aren't actually invoked are not. Additionally, fixed an issue where the check wouldn't diagnose throws in arguments to functions or - constructors. + constructors. Added fine-grained configuration via options + `CheckDestructors`, `CheckMoveMemberFunctions`, `CheckMain`, + `CheckedSwapFunctions`, and `CheckNothrowFunctions`. - Improved :doc:`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 182fade7f47a0..07abbedd109b0 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 @@ -35,6 +35,31 @@ WARNING! This check may be expensive on large source files. Options ------- +.. option:: CheckDestructors + + When `true`, destructors are analyzed to not throw exceptions. + Default value is `true`. + +.. option:: CheckMoveMemberFunctions + + When `true`, move constructors and move assignment operators are analyzed + to not throw exceptions. Default value is `true`. + +.. option:: CheckMain + + When `true`, ``main()`` function is analyzed to not throw exceptions. + Default value is `true`. + +.. option:: CheckNothrowFunctions + + When `true`, functions marked with ``noexcept`` or ``throw()`` exception + specifications are analyzed to not throw exceptions. Default value is `true`. + +.. option:: CheckedSwapFunctions + + Comma separated list of swap function names which should not throw exceptions. + Default value is `swap,iter_swap,iter_move`. + .. option:: FunctionsThatShouldNotThrow Comma separated list containing function names which should not throw. An diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-options.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-options.cpp new file mode 100644 index 0000000000000..b448770561dbb --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-options.cpp @@ -0,0 +1,49 @@ +// RUN: %check_clang_tidy -std=c++11-or-later %s bugprone-exception-escape %t -- \ +// RUN: -config="{CheckOptions: { \ +// RUN: bugprone-exception-escape.CheckDestructors: false, \ +// RUN: bugprone-exception-escape.CheckMoveMemberFunctions: false, \ +// RUN: bugprone-exception-escape.CheckMain: false, \ +// RUN: bugprone-exception-escape.CheckedSwapFunctions: '', \ +// RUN: bugprone-exception-escape.CheckNothrowFunctions: false \ +// RUN: }}" \ +// RUN: -- -fexceptions + +// CHECK-MESSAGES-NOT: warning: + +struct destructor { + ~destructor() { + throw 1; + } +}; + +struct move { + move(const move&) { throw 42; } + move(move&&) { throw 42; } + move& operator=(const move&) { throw 42; } + move& operator=(move&&) { throw 42; } +}; + +void swap(int&, int&) { + throw 1; +} + +void iter_swap(int&, int&) { + throw 1; +} + +void iter_move(int&) { + throw 1; +} + +void nothrow_func() throw() { + throw 1; +} + +void noexcept_func() noexcept { + throw 1; +} + +int main() { + throw 1; + return 0; +} From 39881dc58cd04b2747d080897badc78fa5b88f74 Mon Sep 17 00:00:00 2001 From: Baranov Victor Date: Sat, 18 Oct 2025 17:40:29 +0300 Subject: [PATCH 2/4] Update clang-tools-extra/docs/clang-tidy/checks/bugprone/exception-escape.rst Co-authored-by: EugeneZelenko --- .../docs/clang-tidy/checks/bugprone/exception-escape.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 07abbedd109b0..2e0b37f7e2e8b 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 @@ -57,7 +57,7 @@ Options .. option:: CheckedSwapFunctions - Comma separated list of swap function names which should not throw exceptions. + Comma-separated list of swap function names which should not throw exceptions. Default value is `swap,iter_swap,iter_move`. .. option:: FunctionsThatShouldNotThrow From eb069ae78d06c1c84916c8cc702e05ce7ba9ee70 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Mon, 10 Nov 2025 00:19:47 +0300 Subject: [PATCH 3/4] add MatchIf --- .../bugprone/ExceptionEscapeCheck.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp index b07aad09ed2af..b7de8395ffa05 100644 --- a/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/ExceptionEscapeCheck.cpp @@ -71,18 +71,21 @@ void ExceptionEscapeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { } void ExceptionEscapeCheck::registerMatchers(MatchFinder *Finder) { - ast_matchers::internal::Matcher Nothing = unless(anything()); + auto MatchIf = [](bool Enabled, const auto &Matcher) { + ast_matchers::internal::Matcher Nothing = unless(anything()); + return Enabled ? Matcher : Nothing; + }; Finder->addMatcher( functionDecl( isDefinition(), anyOf( - CheckNothrowFunctions ? isNoThrow() : Nothing, - allOf(anyOf(CheckDestructors ? cxxDestructorDecl() : Nothing, - CheckMoveMemberFunctions - ? anyOf(cxxConstructorDecl(isMoveConstructor()), - cxxMethodDecl(isMoveAssignmentOperator())) - : Nothing, - CheckMain ? isMain() : Nothing, + MatchIf(CheckNothrowFunctions, isNoThrow()), + allOf(anyOf(MatchIf(CheckDestructors, cxxDestructorDecl()), + MatchIf( + CheckMoveMemberFunctions, + anyOf(cxxConstructorDecl(isMoveConstructor()), + cxxMethodDecl(isMoveAssignmentOperator()))), + MatchIf(CheckMain, isMain()), allOf(isEnabled(CheckedSwapFunctions), hasAtLeastOneParameter())), unless(isExplicitThrow())), From 0787c21a3184341666066fc3670699bb736323aa Mon Sep 17 00:00:00 2001 From: Baranov Victor Date: Mon, 10 Nov 2025 01:56:41 +0300 Subject: [PATCH 4/4] Apply suggestions from code review Co-authored-by: Victor Chernyakin --- .../docs/clang-tidy/checks/bugprone/exception-escape.rst | 2 +- .../clang-tidy/checkers/bugprone/exception-escape-options.cpp | 2 -- 2 files changed, 1 insertion(+), 3 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 2e0b37f7e2e8b..7eaa333d5403a 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 @@ -47,7 +47,7 @@ Options .. option:: CheckMain - When `true`, ``main()`` function is analyzed to not throw exceptions. + When `true`, the ``main()`` function is analyzed to not throw exceptions. Default value is `true`. .. option:: CheckNothrowFunctions diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-options.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-options.cpp index b448770561dbb..48c9bacd1b2e5 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-options.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/exception-escape-options.cpp @@ -17,9 +17,7 @@ struct destructor { }; struct move { - move(const move&) { throw 42; } move(move&&) { throw 42; } - move& operator=(const move&) { throw 42; } move& operator=(move&&) { throw 42; } };