diff --git a/clang-tools-extra/clang-tidy/performance/NoexceptMoveConstructorCheck.h b/clang-tools-extra/clang-tidy/performance/NoexceptMoveConstructorCheck.h --- a/clang-tools-extra/clang-tidy/performance/NoexceptMoveConstructorCheck.h +++ b/clang-tools-extra/clang-tidy/performance/NoexceptMoveConstructorCheck.h @@ -10,6 +10,7 @@ #define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_NOEXCEPTMOVECONSTRUCTORCHECK_H #include "../ClangTidyCheck.h" +#include "../utils/ExceptionSpecAnalyzer.h" namespace clang::tidy::performance { @@ -25,10 +26,13 @@ NoexceptMoveConstructorCheck(StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context) {} bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { - return LangOpts.CPlusPlus11; + return LangOpts.CPlusPlus11 && LangOpts.CXXExceptions; } void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + utils::ExceptionSpecAnalyzer SpecAnalyzer; }; } // namespace clang::tidy::performance diff --git a/clang-tools-extra/clang-tidy/performance/NoexceptMoveConstructorCheck.cpp b/clang-tools-extra/clang-tidy/performance/NoexceptMoveConstructorCheck.cpp --- a/clang-tools-extra/clang-tidy/performance/NoexceptMoveConstructorCheck.cpp +++ b/clang-tools-extra/clang-tidy/performance/NoexceptMoveConstructorCheck.cpp @@ -18,61 +18,55 @@ void NoexceptMoveConstructorCheck::registerMatchers(MatchFinder *Finder) { Finder->addMatcher( - cxxMethodDecl(anyOf(cxxConstructorDecl(), hasOverloadedOperatorName("=")), - unless(isImplicit()), unless(isDeleted())) + cxxMethodDecl(unless(isImplicit()), unless(isDeleted()), + anyOf(cxxConstructorDecl(isMoveConstructor()), + isMoveAssignmentOperator())) .bind("decl"), this); } void NoexceptMoveConstructorCheck::check( const MatchFinder::MatchResult &Result) { - if (const auto *Decl = Result.Nodes.getNodeAs("decl")) { - bool IsConstructor = false; - if (const auto *Ctor = dyn_cast(Decl)) { - if (!Ctor->isMoveConstructor()) - return; - IsConstructor = true; - } else if (!Decl->isMoveAssignmentOperator()) { - return; - } - - const auto *ProtoType = Decl->getType()->castAs(); + const auto *Decl = Result.Nodes.getNodeAs("decl"); + if (!Decl) + return; - if (isUnresolvedExceptionSpec(ProtoType->getExceptionSpecType())) - return; + if (SpecAnalyzer.analyze(Decl) != + utils::ExceptionSpecAnalyzer::State::Throwing) + return; - if (!isNoexceptExceptionSpec(ProtoType->getExceptionSpecType())) { - auto Diag = diag(Decl->getLocation(), - "move %select{assignment operator|constructor}0s should " - "be marked noexcept") - << IsConstructor; - // Add FixIt hints. - SourceManager &SM = *Result.SourceManager; - assert(Decl->getNumParams() > 0); - SourceLocation NoexceptLoc = Decl->getParamDecl(Decl->getNumParams() - 1) - ->getSourceRange() - .getEnd(); - if (NoexceptLoc.isValid()) - NoexceptLoc = Lexer::findLocationAfterToken( - NoexceptLoc, tok::r_paren, SM, Result.Context->getLangOpts(), true); - if (NoexceptLoc.isValid()) - Diag << FixItHint::CreateInsertion(NoexceptLoc, " noexcept "); - return; - } + const bool IsConstructor = CXXConstructorDecl::classof(Decl); - // Don't complain about nothrow(false), but complain on nothrow(expr) - // where expr evaluates to false. - if (ProtoType->canThrow() == CT_Can) { - Expr *E = ProtoType->getNoexceptExpr(); - E = E->IgnoreImplicit(); - if (!isa(E)) { - diag(E->getExprLoc(), - "noexcept specifier on the move %select{assignment " - "operator|constructor}0 evaluates to 'false'") - << IsConstructor; - } + // Don't complain about nothrow(false), but complain on nothrow(expr) + // where expr evaluates to false. + const auto *ProtoType = Decl->getType()->castAs(); + const Expr *NoexceptExpr = ProtoType->getNoexceptExpr(); + if (NoexceptExpr) { + NoexceptExpr = NoexceptExpr->IgnoreImplicit(); + if (!isa(NoexceptExpr)) { + diag(NoexceptExpr->getExprLoc(), + "noexcept specifier on the move %select{assignment " + "operator|constructor}0 evaluates to 'false'") + << IsConstructor; } + return; } + + auto Diag = diag(Decl->getLocation(), + "move %select{assignment operator|constructor}0s should " + "be marked noexcept") + << IsConstructor; + // Add FixIt hints. + const SourceManager &SM = *Result.SourceManager; + assert(Decl->getNumParams() > 0); + SourceLocation NoexceptLoc = + Decl->getParamDecl(Decl->getNumParams() - 1)->getSourceRange().getEnd(); + if (NoexceptLoc.isValid()) + NoexceptLoc = Lexer::findLocationAfterToken( + NoexceptLoc, tok::r_paren, SM, Result.Context->getLangOpts(), true); + if (NoexceptLoc.isValid()) + Diag << FixItHint::CreateInsertion(NoexceptLoc, " noexcept "); + return; } } // namespace clang::tidy::performance diff --git a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/utils/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/utils/CMakeLists.txt @@ -8,6 +8,7 @@ ASTUtils.cpp DeclRefExprUtils.cpp ExceptionAnalyzer.cpp + ExceptionSpecAnalyzer.cpp ExprSequence.cpp FileExtensionsUtils.cpp FixItHintUtils.cpp diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.h b/clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.h @@ -0,0 +1,88 @@ +//===--- ExceptionSpecAnalyzer.h - clang-tidy -------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_EXCEPTION_SPEC_ANALYZER_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_EXCEPTION_SPEC_ANALYZER_H + +#include "clang/AST/DeclCXX.h" +#include "llvm/ADT/DenseMap.h" + +namespace clang::tidy::utils { + +/// This class analysis if a `FunctionDecl` has been declared implicitly through +/// defaulting or explicitly as throwing or not and evaluates noexcept +/// expressions if needed. Unlike the `ExceptionAnalyzer` however it can't tell +/// you if the function will actually throw an exception or not. +class ExceptionSpecAnalyzer { +public: + enum class State { + Throwing, ///< This function has been declared as possibly throwing. + NotThrowing, ///< This function has been declared as not throwing. + Unknown, ///< We're unable to tell if this function is declared as throwing + ///< or not. + }; + + ExceptionSpecAnalyzer() = default; + + State analyze(const FunctionDecl *FuncDecl); + +private: + enum class DefaultableMemberKind { + DefaultConstructor, + CopyConstructor, + MoveConstructor, + CopyAssignment, + MoveAssignment, + Destructor, + + CompareEqual, + CompareNotEqual, + CompareThreeWay, + CompareRelational, + + None, + }; + + State analyzeImpl(const FunctionDecl *FuncDecl); + + State analyzeUnresolvedOrDefaulted(const CXXMethodDecl *MethodDecl, + const FunctionProtoType *FuncProto); + + State analyzeFieldDecl(const FieldDecl *FDecl, DefaultableMemberKind Kind); + + State analyzeBase(const CXXBaseSpecifier &Base, DefaultableMemberKind Kind); + + enum class SkipMethods : bool { + Yes = true, + No = false, + }; + + State analyzeRecord(const CXXRecordDecl *RecDecl, DefaultableMemberKind Kind, + SkipMethods SkipMethods = SkipMethods::No); + + static State analyzeFunctionEST(const FunctionDecl *FuncDecl, + const FunctionProtoType *FuncProto); + + static bool hasTrivialMemberKind(const CXXRecordDecl *RecDecl, + DefaultableMemberKind Kind); + + static bool isConstructor(DefaultableMemberKind Kind); + + static bool isSpecialMember(DefaultableMemberKind Kind); + + static bool isComparison(DefaultableMemberKind Kind); + + static DefaultableMemberKind + getDefaultableMemberKind(const FunctionDecl *FuncDecl); + + llvm::DenseMap FunctionCache{32u}; +}; + +} // namespace clang::tidy::utils + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_EXCEPTION_SPEC_ANALYZER_H diff --git a/clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.cpp b/clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/utils/ExceptionSpecAnalyzer.cpp @@ -0,0 +1,273 @@ +//===--- ExceptionSpecAnalyzer.cpp - clang-tidy ---------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ExceptionSpecAnalyzer.h" + +#include "clang/AST/Expr.h" + +namespace clang::tidy::utils { + +ExceptionSpecAnalyzer::State +ExceptionSpecAnalyzer::analyze(const FunctionDecl *FuncDecl) { + ExceptionSpecAnalyzer::State State; + + // Check if the function has already been analyzed and reuse that result. + const auto CacheEntry = FunctionCache.find(FuncDecl); + if (CacheEntry == FunctionCache.end()) { + State = analyzeImpl(FuncDecl); + + // Cache the result of the analysis. + FunctionCache.try_emplace(FuncDecl, State); + } else + State = CacheEntry->getSecond(); + + return State; +} + +ExceptionSpecAnalyzer::State +ExceptionSpecAnalyzer::analyzeUnresolvedOrDefaulted( + const CXXMethodDecl *MethodDecl, const FunctionProtoType *FuncProto) { + if (!FuncProto || !MethodDecl) + return State::Unknown; + + const DefaultableMemberKind Kind = getDefaultableMemberKind(MethodDecl); + + if (Kind == DefaultableMemberKind::None) + return State::Unknown; + + return analyzeRecord(MethodDecl->getParent(), Kind, SkipMethods::Yes); +} + +ExceptionSpecAnalyzer::State +ExceptionSpecAnalyzer::analyzeFieldDecl(const FieldDecl *FDecl, + DefaultableMemberKind Kind) { + if (!FDecl) + return State::Unknown; + + if (const CXXRecordDecl *RecDecl = + FDecl->getType()->getUnqualifiedDesugaredType()->getAsCXXRecordDecl()) + return analyzeRecord(RecDecl, Kind); + + // Trivial types do not throw + if (FDecl->getType().isTrivialType(FDecl->getASTContext())) + return State::NotThrowing; + + return State::Unknown; +} + +ExceptionSpecAnalyzer::State +ExceptionSpecAnalyzer::analyzeBase(const CXXBaseSpecifier &Base, + DefaultableMemberKind Kind) { + const auto *RecType = Base.getType()->getAs(); + if (!RecType) + return State::Unknown; + + const auto *BaseClass = cast(RecType->getDecl()); + + return analyzeRecord(BaseClass, Kind); +} + +ExceptionSpecAnalyzer::State +ExceptionSpecAnalyzer::analyzeRecord(const CXXRecordDecl *RecordDecl, + DefaultableMemberKind Kind, + SkipMethods SkipMethods) { + if (!RecordDecl) + return State::Unknown; + + // Trivial implies noexcept + if (hasTrivialMemberKind(RecordDecl, Kind)) + return State::NotThrowing; + + if (SkipMethods == SkipMethods::No) + for (const auto *MethodDecl : RecordDecl->methods()) + if (getDefaultableMemberKind(MethodDecl) == Kind) + return analyze(MethodDecl); + + for (const auto &BaseSpec : RecordDecl->bases()) { + State Result = analyzeBase(BaseSpec, Kind); + if (Result == State::Throwing || Result == State::Unknown) + return Result; + } + + for (const auto &BaseSpec : RecordDecl->vbases()) { + State Result = analyzeBase(BaseSpec, Kind); + if (Result == State::Throwing || Result == State::Unknown) + return Result; + } + + for (const auto *FDecl : RecordDecl->fields()) + if (!FDecl->isInvalidDecl() && !FDecl->isUnnamedBitfield()) { + State Result = analyzeFieldDecl(FDecl, Kind); + if (Result == State::Throwing || Result == State::Unknown) + return Result; + } + + return State::NotThrowing; +} + +ExceptionSpecAnalyzer::State +ExceptionSpecAnalyzer::analyzeImpl(const FunctionDecl *FuncDecl) { + const auto *FuncProto = FuncDecl->getType()->getAs(); + if (!FuncProto) + return State::Unknown; + + const ExceptionSpecificationType EST = FuncProto->getExceptionSpecType(); + + if (EST == EST_Unevaluated || (EST == EST_None && FuncDecl->isDefaulted())) + return analyzeUnresolvedOrDefaulted(cast(FuncDecl), + FuncProto); + + return analyzeFunctionEST(FuncDecl, FuncProto); +} + +ExceptionSpecAnalyzer::State +ExceptionSpecAnalyzer::analyzeFunctionEST(const FunctionDecl *FuncDecl, + const FunctionProtoType *FuncProto) { + if (!FuncDecl || !FuncProto) + return State::Unknown; + + if (isUnresolvedExceptionSpec(FuncProto->getExceptionSpecType())) + return State::Unknown; + + switch (FuncProto->canThrow()) { + case CT_Cannot: + return State::NotThrowing; + case CT_Dependent: { + const Expr *NoexceptExpr = FuncProto->getNoexceptExpr(); + bool Result; + return (NoexceptExpr && !NoexceptExpr->isValueDependent() && + NoexceptExpr->EvaluateAsBooleanCondition( + Result, FuncDecl->getASTContext(), true) && + Result) + ? State::NotThrowing + : State::Throwing; + } + default: + return State::Throwing; + }; +} + +bool ExceptionSpecAnalyzer::hasTrivialMemberKind(const CXXRecordDecl *RecDecl, + DefaultableMemberKind Kind) { + if (!RecDecl) + return false; + + switch (Kind) { + case DefaultableMemberKind::DefaultConstructor: + return RecDecl->hasTrivialDefaultConstructor(); + case DefaultableMemberKind::CopyConstructor: + return RecDecl->hasTrivialCopyConstructor(); + case DefaultableMemberKind::MoveConstructor: + return RecDecl->hasTrivialMoveConstructor(); + case DefaultableMemberKind::CopyAssignment: + return RecDecl->hasTrivialCopyAssignment(); + case DefaultableMemberKind::MoveAssignment: + return RecDecl->hasTrivialMoveAssignment(); + case DefaultableMemberKind::Destructor: + return RecDecl->hasTrivialDestructor(); + + default: + return false; + } +} + +bool ExceptionSpecAnalyzer::isConstructor(DefaultableMemberKind Kind) { + switch (Kind) { + case DefaultableMemberKind::DefaultConstructor: + case DefaultableMemberKind::CopyConstructor: + case DefaultableMemberKind::MoveConstructor: + return true; + + default: + return false; + } +} + +bool ExceptionSpecAnalyzer::isSpecialMember(DefaultableMemberKind Kind) { + switch (Kind) { + case DefaultableMemberKind::DefaultConstructor: + case DefaultableMemberKind::CopyConstructor: + case DefaultableMemberKind::MoveConstructor: + case DefaultableMemberKind::CopyAssignment: + case DefaultableMemberKind::MoveAssignment: + case DefaultableMemberKind::Destructor: + return true; + default: + return false; + } +} + +bool ExceptionSpecAnalyzer::isComparison(DefaultableMemberKind Kind) { + switch (Kind) { + case DefaultableMemberKind::CompareEqual: + case DefaultableMemberKind::CompareNotEqual: + case DefaultableMemberKind::CompareRelational: + case DefaultableMemberKind::CompareThreeWay: + return true; + default: + return false; + } +} + +ExceptionSpecAnalyzer::DefaultableMemberKind +ExceptionSpecAnalyzer::getDefaultableMemberKind(const FunctionDecl *FuncDecl) { + if (const auto *MethodDecl = dyn_cast(FuncDecl)) { + if (const auto *Ctor = dyn_cast(FuncDecl)) { + if (Ctor->isDefaultConstructor()) + return DefaultableMemberKind::DefaultConstructor; + + if (Ctor->isCopyConstructor()) + return DefaultableMemberKind::CopyConstructor; + + if (Ctor->isMoveConstructor()) + return DefaultableMemberKind::MoveConstructor; + } + + if (MethodDecl->isCopyAssignmentOperator()) + return DefaultableMemberKind::CopyAssignment; + + if (MethodDecl->isMoveAssignmentOperator()) + return DefaultableMemberKind::MoveAssignment; + + if (isa(FuncDecl)) + return DefaultableMemberKind::Destructor; + } + + const LangOptions &LangOpts = FuncDecl->getLangOpts(); + + switch (FuncDecl->getDeclName().getCXXOverloadedOperator()) { + case OO_EqualEqual: + return DefaultableMemberKind::CompareEqual; + + case OO_ExclaimEqual: + return DefaultableMemberKind::CompareNotEqual; + + case OO_Spaceship: + // No point allowing this if <=> doesn't exist in the current language mode. + if (!LangOpts.CPlusPlus20) + break; + return DefaultableMemberKind::CompareThreeWay; + + case OO_Less: + case OO_LessEqual: + case OO_Greater: + case OO_GreaterEqual: + // No point allowing this if <=> doesn't exist in the current language mode. + if (!LangOpts.CPlusPlus20) + break; + return DefaultableMemberKind::CompareRelational; + + default: + break; + } + + // Not a defaultable member kind + return DefaultableMemberKind::None; +} + +} // namespace clang::tidy::utils diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -248,6 +248,10 @@ ` to understand that there is a sequence point between designated initializers. +- Fixed an issue in the :doc:`performance-noexcept-move-constructor + ` checker that was causing + false-positives when the move constructor or move assign operator were defaulted. + - Fixed an issue in :doc:`readability-identifier-naming ` when specifying an empty string for ``Prefix`` or ``Suffix`` options could result in the style not diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/noexcept-move-constructor-fix.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/noexcept-move-constructor-fix.cpp --- a/clang-tools-extra/test/clang-tidy/checkers/performance/noexcept-move-constructor-fix.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/performance/noexcept-move-constructor-fix.cpp @@ -1,4 +1,4 @@ -// RUN: %check_clang_tidy %s performance-noexcept-move-constructor %t +// RUN: %check_clang_tidy %s performance-noexcept-move-constructor %t -- -- -fexceptions struct C_1 { ~C_1() {} @@ -31,9 +31,7 @@ }; C_3::C_3(C_3&& a) = default; -// CHECK-FIXES: ){{.*}}noexcept{{.*}} = default; C_3& C_3::operator=(C_3&& a) = default; -// CHECK-FIXES: ){{.*}}noexcept{{.*}} = default; template struct C_4 { @@ -41,7 +39,6 @@ // CHECK-FIXES: ){{.*}}noexcept{{.*}} {} ~C_4() {} C_4& operator=(C_4&& a) = default; -// CHECK-FIXES: ){{.*}}noexcept{{.*}} = default; }; template @@ -50,7 +47,6 @@ // CHECK-FIXES:){{.*}}noexcept{{.*}} {} ~C_5() {} auto operator=(C_5&& a)->C_5 = default; -// CHECK-FIXES:){{.*}}noexcept{{.*}} = default; }; template @@ -64,4 +60,4 @@ template auto C_6::operator=(C_6&& a) -> C_6 {} -// CHECK-FIXES: ){{.*}}noexcept{{.*}} {} \ No newline at end of file +// CHECK-FIXES: ){{.*}}noexcept{{.*}} {} 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 --- 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,10 +1,37 @@ -// RUN: %check_clang_tidy %s performance-noexcept-move-constructor %t +// RUN: %check_clang_tidy %s performance-noexcept-move-constructor %t -- -- -fexceptions + +struct Empty +{}; + +struct IntWrapper { + int value; +}; + +template +struct FalseT { + static constexpr bool value = false; +}; + +template +struct TrueT { + static constexpr bool value = true; +}; + +struct ThrowingMoveConstructor +{ + ThrowingMoveConstructor() = default; + ThrowingMoveConstructor(ThrowingMoveConstructor&&) noexcept(false) { + } + ThrowingMoveConstructor& operator=(ThrowingMoveConstructor &&) noexcept(false) { + return *this; + } +}; class A { A(A &&); // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: move constructors should be marked noexcept [performance-noexcept-move-constructor] A &operator=(A &&); - // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: move assignment operators should + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: move assignment operators should be marked noexcept [performance-noexcept-move-constructor] }; struct B { @@ -13,6 +40,125 @@ // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: noexcept specifier on the move constructor evaluates to 'false' [performance-noexcept-move-constructor] }; +template +struct C +{ + C(C &&); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: move constructors should be marked noexcept [performance-noexcept-move-constructor] + C& operator=(C &&); + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: move assignment operators should be marked noexcept [performance-noexcept-move-constructor] +}; + +struct D +{ + static constexpr bool kFalse = false; + D(D &&) noexcept(kFalse) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: noexcept specifier on the move constructor evaluates to 'false' [performance-noexcept-move-constructor] + D& operator=(D &&) noexcept(kFalse) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: noexcept specifier on the move assignment operator evaluates to 'false' +}; + +template +struct E +{ + static constexpr bool kFalse = false; + E(E &&) noexcept(kFalse); + // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: noexcept specifier on the move constructor evaluates to 'false' [performance-noexcept-move-constructor] + E& operator=(E &&) noexcept(kFalse); + // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: noexcept specifier on the move assignment operator evaluates to 'false' +}; + +template +struct F +{ + static constexpr bool kFalse = false; + F(F &&) noexcept(kFalse) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: noexcept specifier on the move constructor evaluates to 'false' [performance-noexcept-move-constructor] + F& operator=(F &&) noexcept(kFalse) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: noexcept specifier on the move assignment operator evaluates to 'false' +}; + +struct G { + G(G &&) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: move constructors should be marked noexcept [performance-noexcept-move-constructor] + G& operator=(G &&) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: move assignment operators should be marked noexcept [performance-noexcept-move-constructor] + + ThrowingMoveConstructor field; +}; + +void throwing_function() noexcept(false) {} + +struct H { + H(H &&) noexcept(noexcept(throwing_function())); + // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: noexcept specifier on the move constructor evaluates to 'false' [performance-noexcept-move-constructor] + H &operator=(H &&) noexcept(noexcept(throwing_function())); + // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: noexcept specifier on the move assignment operator evaluates to 'false' +}; + +template +struct I { + I(I &&) noexcept(noexcept(throwing_function())); + // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: noexcept specifier on the move constructor evaluates to 'false' [performance-noexcept-move-constructor] + I &operator=(I &&) noexcept(noexcept(throwing_function())); + // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: noexcept specifier on the move assignment operator evaluates to 'false' +}; + +template struct TemplatedType { + static void f() {} +}; + +template <> struct TemplatedType { + static void f() noexcept {} +}; + +struct J { + J(J &&) noexcept(noexcept(TemplatedType::f())); + // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: noexcept specifier on the move constructor evaluates to 'false' [performance-noexcept-move-constructor] + J &operator=(J &&) noexcept(noexcept(TemplatedType::f())); + // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: noexcept specifier on the move assignment operator evaluates to 'false' [performance-noexcept-move-constructor] +}; + +struct K : public ThrowingMoveConstructor { + K(K &&) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: move constructors should be marked noexcept [performance-noexcept-move-constructor] + K &operator=(K &&) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: move assignment operators should be marked noexcept [performance-noexcept-move-constructor] +}; + +struct InheritFromThrowingMoveConstrcutor : public ThrowingMoveConstructor +{}; + +struct L { + L(L &&) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: move constructors should be marked noexcept [performance-noexcept-move-constructor] + L &operator=(L &&) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: move assignment operators should be marked noexcept [performance-noexcept-move-constructor] + + InheritFromThrowingMoveConstrcutor IFF; +}; + +struct M : public InheritFromThrowingMoveConstrcutor { + M(M &&) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: move constructors should be marked noexcept [performance-noexcept-move-constructor] + M &operator=(M &&) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: move assignment operators should be marked noexcept [performance-noexcept-move-constructor] +}; + +struct N : public IntWrapper, ThrowingMoveConstructor { + N(N &&) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: move constructors should be marked noexcept [performance-noexcept-move-constructor] + N &operator=(N &&) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: move assignment operators should be marked noexcept [performance-noexcept-move-constructor] +}; + +struct O : virtual IntWrapper, ThrowingMoveConstructor { + O(O &&) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: move constructors should be marked noexcept [performance-noexcept-move-constructor] + O &operator=(O &&) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: move assignment operators should be marked noexcept [performance-noexcept-move-constructor] +}; + class OK {}; void f() { @@ -30,10 +176,9 @@ void g() noexcept; }; -class OK2 { +struct OK2 { static constexpr bool kTrue = true; -public: OK2(OK2 &&) noexcept(true) {} OK2 &operator=(OK2 &&) noexcept(kTrue) { return *this; } }; @@ -52,3 +197,182 @@ OK5(OK5 &&) noexcept(true) = default; OK5 &operator=(OK5 &&) noexcept(true) = default; }; + +struct OK6 { + OK6(OK6 &&) = default; + OK6& operator=(OK6 &&) = default; +}; + +template +struct OK7 { + OK7(OK7 &&) = default; + OK7& operator=(OK7 &&) = default; +}; + +template +struct OK8 { + OK8(OK8 &&) noexcept = default; + OK8& operator=(OK8 &&) noexcept = default; +}; + +template +struct OK9 { + OK9(OK9 &&) noexcept(true) = default; + OK9& operator=(OK9 &&) noexcept(true) = default; +}; + +template +struct OK10 { + OK10(OK10 &&) noexcept(false) = default; + OK10& operator=(OK10 &&) noexcept(false) = default; +}; + +template +struct OK11 { + OK11(OK11 &&) = delete; + OK11& operator=(OK11 &&) = delete; +}; + +void noexcept_function() noexcept {} + +struct OK12 { + OK12(OK12 &&) noexcept(noexcept(noexcept_function())); + OK12 &operator=(OK12 &&) noexcept(noexcept(noexcept_function)); +}; + +struct OK13 { + OK13(OK13 &&) noexcept(noexcept(noexcept_function)) = default; + OK13 &operator=(OK13 &&) noexcept(noexcept(noexcept_function)) = default; +}; + +template struct OK14 { + OK14(OK14 &&) noexcept(noexcept(TemplatedType::f())); + OK14 &operator=(OK14 &&) noexcept(noexcept(TemplatedType::f())); +}; + +struct OK15 { + OK15(OK15 &&) = default; + OK15 &operator=(OK15 &&) = default; + + int member; +}; + +template +struct OK16 { + OK16(OK16 &&) = default; + OK16 &operator=(OK16 &&) = default; + + int member; +}; + +struct OK17 +{ + OK17(OK17 &&) = default; + OK17 &operator=(OK17 &&) = default; + OK empty_field; +}; + +template +struct OK18 +{ + OK18(OK18 &&) = default; + OK18 &operator=(OK18 &&) = default; + + OK empty_field; +}; + +struct OK19 : public OK +{ + OK19(OK19 &&) = default; + OK19 &operator=(OK19 &&) = default; +}; + +struct OK20 : virtual OK +{ + OK20(OK20 &&) = default; + OK20 &operator=(OK20 &&) = default; +}; + +template +struct OK21 : public T +{ + OK21() = default; + OK21(OK21 &&) = default; + OK21 &operator=(OK21 &&) = default; +}; + +template +struct OK22 : virtual T +{ + OK22() = default; + OK22(OK22 &&) = default; + OK22 &operator=(OK22 &&) = default; +}; + +template +struct OK23 { + OK23()= default; + OK23(OK23 &&) = default; + OK23 &operator=(OK23 &&) = default; + + T member; +}; + +void testTemplates() { + OK21 value(OK21{}); + value = OK21{}; + + OK22 value2{OK22{}}; + value2 = OK22{}; + + OK23 value3{OK23{}}; + value3 =OK23{}; +} + +struct OK24 : public Empty, OK1 { + OK24(OK24 &&) = default; + OK24 &operator=(OK24 &&) = default; +}; + +struct OK25 : virtual Empty, OK1 { + OK25(OK25 &&) = default; + OK25 &operator=(OK25 &&) = default; +}; + +struct OK26 : public Empty, IntWrapper { + OK26(OK26 &&) = default; + OK26 &operator=(OK26 &&) = default; +}; + +template +struct OK27 : public T +{ + OK27(OK27 &&) = default; + OK27 &operator=(OK27 &&) = default; +}; + +template +struct OK28 : virtual T +{ + OK28(OK28 &&) = default; + OK28 &operator=(OK28 &&) = default; +}; + +template +struct OK29 { + OK29(OK29 &&) = default; + OK29 &operator=(OK29 &&) = default; + + T member; +}; + +struct OK30 { + OK30(OK30 &&) noexcept(TrueT::value) = default; + OK30& operator=(OK30 &&) noexcept(TrueT::value) = default; +}; + +template +struct OK31 { + OK31(OK31 &&) noexcept(TrueT::value) = default; + OK31& operator=(OK31 &&) noexcept(TrueT::value) = default; +};