From e82ff98aaf7b40fd5d598b486243adc54009bffc Mon Sep 17 00:00:00 2001 From: AMS21 Date: Thu, 5 Oct 2023 23:39:42 +0200 Subject: [PATCH] [clang-tidy] Improve `ExceptionSpecAnalyzer`s handling of conditional noexcept expressions The previous code was pretty messy and treated value dependant expressions which could not be evaluated the same as if they evaluted to `false`. Which was obviously not correct. We now check if we can evaluate the dependant expressions and if not we truthfully return that we don't know if the function is declared as `noexcept` or not. This fixes #68101 --- .../utils/ExceptionSpecAnalyzer.cpp | 21 ++++++++++---- clang-tools-extra/docs/ReleaseNotes.rst | 7 ++++- .../performance/noexcept-move-constructor.cpp | 18 ++++++++++++ .../checkers/performance/noexcept-swap.cpp | 28 ++++++++++++++----- 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.cpp b/clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.cpp index 4dd4a95f880ac..1dde049051785 100644 --- a/clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.cpp +++ b/clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.cpp @@ -142,13 +142,22 @@ ExceptionSpecAnalyzer::analyzeFunctionEST(const FunctionDecl *FuncDecl, return State::NotThrowing; case CT_Dependent: { const Expr *NoexceptExpr = FuncProto->getNoexceptExpr(); + if (!NoexceptExpr) + return State::NotThrowing; + + // We can't resolve value dependence so just return unknown + if (NoexceptExpr->isValueDependent()) + return State::Unknown; + + // Try to evaluate the expression to a boolean value bool Result = false; - return (NoexceptExpr && !NoexceptExpr->isValueDependent() && - NoexceptExpr->EvaluateAsBooleanCondition( - Result, FuncDecl->getASTContext(), true) && - Result) - ? State::NotThrowing - : State::Throwing; + if (NoexceptExpr->EvaluateAsBooleanCondition( + Result, FuncDecl->getASTContext(), true)) + return Result ? State::NotThrowing : State::Throwing; + + // The noexcept expression is not value dependent but we can't evaluate it + // as a boolean condition so we have no idea if its throwing or not + return State::Unknown; } default: return State::Throwing; diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 0ecf786e73bc4..8ca816bfc2d60 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -284,9 +284,14 @@ Changes in existing checks ` check to properly escape single quotes. +- Improved :doc:`performance-noexcept-move-constructor + ` to better handle + conditional noexcept expressions, eliminating false-positives. + - Improved :doc:`performance-noexcept-swap ` check to enforce a stricter - match with the swap function signature, eliminating false-positives. + match with the swap function signature and better handling of condition + noexcept expressions, eliminating false-positives. - Improved :doc:`readability-braces-around-statements ` check to diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/noexcept-move-constructor.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/noexcept-move-constructor.cpp index 60596c2876c0a..347a1e3220061 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/performance/noexcept-move-constructor.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/performance/noexcept-move-constructor.cpp @@ -1,5 +1,14 @@ // RUN: %check_clang_tidy %s performance-noexcept-move-constructor %t -- -- -fexceptions +namespace std +{ + template + struct is_nothrow_move_constructible + { + static constexpr bool value = __is_nothrow_constructible(T, __add_rvalue_reference(T)); + }; +} // namespace std + struct Empty {}; @@ -379,3 +388,12 @@ struct OK31 { OK31(OK31 &&) noexcept(TrueT::value) = default; OK31& operator=(OK31 &&) noexcept(TrueT::value) = default; }; + +namespace gh68101 +{ + template + class Container { + public: + Container(Container&&) noexcept(std::is_nothrow_move_constructible::value); + }; +} // namespace gh68101 diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/noexcept-swap.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/noexcept-swap.cpp index 392b5d17d456e..dfc71a2bb9ab3 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/performance/noexcept-swap.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/performance/noexcept-swap.cpp @@ -1,5 +1,14 @@ // RUN: %check_clang_tidy %s performance-noexcept-swap %t -- -- -fexceptions +namespace std +{ + template + struct is_nothrow_move_constructible + { + static constexpr bool value = __is_nothrow_constructible(T, __add_rvalue_reference(T)); + }; +} // namespace std + void throwing_function() noexcept(false); void noexcept_function() noexcept; @@ -54,9 +63,6 @@ struct D { // CHECK-MESSAGES: :[[@LINE-1]]:27: warning: noexcept specifier on swap function evaluates to 'false' [performance-noexcept-swap] }; -template -void swap(D &, D &) noexcept(D::kFalse); -// CHECK-MESSAGES: :[[@LINE-1]]:36: warning: noexcept specifier on swap function evaluates to 'false' [performance-noexcept-swap] void swap(D &, D &) noexcept(D::kFalse); // CHECK-MESSAGES: :[[@LINE-1]]:40: warning: noexcept specifier on swap function evaluates to 'false' [performance-noexcept-swap] @@ -151,9 +157,8 @@ struct OK16 { void swap(OK16 &) noexcept(kTrue); }; -// FIXME: This gives a warning, but it should be OK. -//template -//void swap(OK16 &, OK16 &) noexcept(OK16::kTrue); +template +void swap(OK16 &, OK16 &) noexcept(OK16::kTrue); template void swap(OK16 &, OK16 &) noexcept(OK16::kTrue); @@ -217,4 +222,13 @@ namespace PR64303 { friend void swap(Test&, Test&); // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: swap functions should be marked noexcept [performance-noexcept-swap] }; -} +} // namespace PR64303 + +namespace gh68101 +{ + template + class Container { + public: + void swap(Container&) noexcept(std::is_nothrow_move_constructible::value); + }; +} // namespace gh68101