diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt b/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt @@ -6,6 +6,7 @@ add_clang_library(clangTidyCppCoreGuidelinesModule AvoidGotoCheck.cpp AvoidNonConstGlobalVariablesCheck.cpp + ComparisonOperatorCheck.cpp CppCoreGuidelinesTidyModule.cpp InitVariablesCheck.cpp InterfacesGlobalInitCheck.cpp diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ComparisonOperatorCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/ComparisonOperatorCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ComparisonOperatorCheck.h @@ -0,0 +1,47 @@ +//===--- ComparisonOperatorCheck.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_CPPCOREGUIDELINES_COMPARISONOPERATORCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_COMPARISONOPERATORCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// Finds comparison operators which are not noexcept, are defined as member +/// functions, have parameters of different type or pass parameters not by value +/// for cheap to copy types and not by reference to const for expensive to copy +/// types. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-comparison-operator.html +class ComparisonOperatorCheck : public ClangTidyCheck { +public: + ComparisonOperatorCheck(StringRef Name, ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void checkParamPassing(const FunctionDecl *FD, + const ast_matchers::MatchFinder::MatchResult &Result); + void diagCanThrow(const FunctionDecl *FD); + const bool CheckParamPassing; + const size_t ExpensiveToCopySize; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_COMPARISONOPERATORCHECK_H diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/ComparisonOperatorCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/ComparisonOperatorCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/ComparisonOperatorCheck.cpp @@ -0,0 +1,176 @@ +//===--- ComparisonOperatorCheck.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 "ComparisonOperatorCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { +constexpr size_t DefaultExpensiveToCopySize = 0; + +ComparisonOperatorCheck::ComparisonOperatorCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + CheckParamPassing(Options.get("CheckParamPassing", true)), + ExpensiveToCopySize( + Options.get("ExpensiveToCopySize", DefaultExpensiveToCopySize)) {} + +void ComparisonOperatorCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "CheckParamPassing", CheckParamPassing); + Options.store(Opts, "ExpensiveToCopySize", ExpensiveToCopySize); +} + +void ComparisonOperatorCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(functionDecl(hasAnyOverloadedOperatorName("==", "!=", "<", + "<=", ">", ">=")) + .bind("operator"), + this); +} + +static bool canThrow(const FunctionDecl *FD) { + const auto *Proto = FD->getType()->getAs(); + if (isUnresolvedExceptionSpec(Proto->getExceptionSpecType())) + return false; + + return Proto->canThrow() == CT_Can; +} + +static bool hasSameParam(const FunctionDecl *FD) { + constexpr unsigned int ExpectedNumParams = 2; + if (FD->getNumParams() != ExpectedNumParams) + return false; + + QualType LH = FD->getParamDecl(0)->getOriginalType(); + QualType RH = FD->getParamDecl(1)->getOriginalType(); + + return LH == RH; +} + +void ComparisonOperatorCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Operator = Result.Nodes.getNodeAs("operator"); + + if (isa(Operator)) { + diag(Operator->getLocation(), "%0 should not be member function") + << Operator; + return; + } + + if (!hasSameParam(Operator)) + diag(Operator->getLocation(), + "%0 should have 2 parameters of the same type") + << Operator; + + if (CheckParamPassing) + checkParamPassing(Operator, Result); + + if (getLangOpts().CPlusPlus11 && canThrow(Operator)) + diagCanThrow(Operator); +} + +/// An 'in' parameter of type 'T' should be: +/// - const T& --- if `T` is expensive to copy. +/// - T --- if `T` is cheap to copy. +enum InParamPassingKind { + IPP_Eligible, // 'in' parameter obeys the above rule + IPP_ExpensiveToCopy, // expensive to copy but not 'const T&' + IPP_CheapToCopy, // cheap to copy but not 'T' + IPP_Unknown +}; + +static llvm::Optional isExpensiveToCopy(QualType Type, + const ASTContext &Context, + size_t ExpensiveToCopySize) { + llvm::Optional Size = + Context.getTypeSizeInCharsIfKnown(Type.getNonReferenceType()); + if (!Size.hasValue() || Size.getValue().isNegative()) + return llvm::None; + + if (ExpensiveToCopySize != DefaultExpensiveToCopySize) + return static_cast(Size.getValue().getQuantity()) > + ExpensiveToCopySize; + + llvm::Optional VoidPtrSize = + Context.getTypeSizeInCharsIfKnown(Context.VoidPtrTy); + if (!VoidPtrSize.hasValue() || VoidPtrSize.getValue().isNegative()) + return llvm::None; + + // Default to treat types which have a size greater than sizeof(void*)*2 as + // expensive to copy types + return Size.getValue() > VoidPtrSize.getValue() * 2; +} + +static InParamPassingKind getInParamPassingKind(const ParmVarDecl *PVD, + const ASTContext &Context, + size_t ExpensiveToCopySize) { + QualType Type = PVD->getOriginalType(); + QualType NonRefType = Type.getNonReferenceType(); + llvm::Optional IsExpensive = + isExpensiveToCopy(NonRefType, Context, ExpensiveToCopySize); + if (!IsExpensive.hasValue()) + return IPP_Unknown; + + // expensive to copy, not 'const T&' + if (IsExpensive.getValue() && + !(NonRefType.isConstQualified() && Type->isLValueReferenceType())) + return IPP_ExpensiveToCopy; + + // cheap to copy, not 'T' + if (!IsExpensive.getValue() && + !(!NonRefType.isConstQualified() && !Type->isReferenceType())) + return IPP_CheapToCopy; + + return IPP_Eligible; +} + +void ComparisonOperatorCheck::checkParamPassing( + const FunctionDecl *FD, const MatchFinder::MatchResult &Result) { + for (const ParmVarDecl *PVD : FD->parameters()) { + InParamPassingKind IPPK = + getInParamPassingKind(PVD, *Result.Context, ExpensiveToCopySize); + + if (IPPK == IPP_ExpensiveToCopy || IPPK == IPP_CheapToCopy) { + QualType Type = + PVD->getOriginalType().getNonReferenceType().getUnqualifiedType(); + diag(PVD->getLocation(), "%0 of type %1 is %select{cheap|expensive}2 to " + "copy, should be passed by " + "%select{value|reference to const}2") + << PVD << Type << (IPPK == IPP_ExpensiveToCopy); + } + } +} + +static SourceLocation getInsertionLoc(const FunctionDecl *FD) { + FunctionTypeLoc FTL = FD->getFunctionTypeLoc(); + if (!FTL) + return SourceLocation(); + + SourceLocation RParenLoc = FTL.getRParenLoc(); + return RParenLoc.getLocWithOffset(1); +} + +void ComparisonOperatorCheck::diagCanThrow(const FunctionDecl *FD) { + diag(FD->getLocation(), "%0 should be marked noexcept") << FD; + + DiagnosticBuilder FixDiag = + diag(FD->getLocation(), "mark 'noexcept'", DiagnosticIDs::Note); + SourceRange ExceptionSpecRange = FD->getExceptionSpecSourceRange(); + SourceLocation InsertionLoc = getInsertionLoc(FD); + + if (ExceptionSpecRange.isValid()) + FixDiag << FixItHint::CreateReplacement(ExceptionSpecRange, "noexcept"); + else if (InsertionLoc.isValid()) + FixDiag << FixItHint::CreateInsertion(InsertionLoc, " noexcept"); +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp @@ -16,6 +16,7 @@ #include "../readability/MagicNumbersCheck.h" #include "AvoidGotoCheck.h" #include "AvoidNonConstGlobalVariablesCheck.h" +#include "ComparisonOperatorCheck.h" #include "InitVariablesCheck.h" #include "InterfacesGlobalInitCheck.h" #include "MacroUsageCheck.h" @@ -52,6 +53,8 @@ "cppcoreguidelines-avoid-magic-numbers"); CheckFactories.registerCheck( "cppcoreguidelines-avoid-non-const-global-variables"); + CheckFactories.registerCheck( + "cppcoreguidelines-comparison-operator"); CheckFactories.registerCheck( "cppcoreguidelines-explicit-virtual-functions"); CheckFactories.registerCheck( 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 @@ -83,6 +83,14 @@ Finds ``pthread_setcanceltype`` function calls where a thread's cancellation type is set to asynchronous. +- New :doc:`cppcoreguidelines-comparison-operator + ` check. + + Finds comparison operators(``==``, ``!=``, ``<``, ``<=``, ``>``, ``>=``) + which are not ``noexcept``, are defined as member functions, have parameters + of different type or pass parameters not by value for cheap to copy types and + not by reference to const for expensive to copy types. + - New :doc:`cppcoreguidelines-prefer-member-initializer ` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines-comparison-operator.rst b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines-comparison-operator.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines-comparison-operator.rst @@ -0,0 +1,74 @@ +.. title:: clang-tidy - cppcoreguidelines-comparison-operator + +cppcoreguidelines-comparison-operator +===================================== + +Finds comparison operators(``==``, ``!=``, ``<``, ``<=``, ``>``, ``>=``) +which are not ``noexcept``, are defined as member functions, have parameters of +different type or pass parameters not by value for cheap to copy types and not +by reference to const for expensive to copy types. + +Examples: + +.. code-block:: c++ + + struct A { + int Var; + bool operator==(const A &a) const; // 'operator==' should not be member function + }; + +.. code-block:: c++ + + struct C; + bool operator==(const C &lh, const C &rh); // 'operator==' should be marked noexcept + +.. code-block:: c++ + + struct B; + bool operator==(const B &lh, int rh) noexcept; // 'operator==' should have 2 parameters of the same type + +.. code-block:: c++ + + struct C { // cheap to copy + int Var; + }; + + bool operator==(const C &lh, const C &rh) noexcept; + // 'lh' of type 'C' is cheap to copy, should be passed by value + // 'rh' of type 'C' is cheap to copy, should be passed by value + +.. code-block:: c++ + + struct D { // expensive to copy + int Array[1024]; + }; + + bool operator==(D lh, D rh) noexcept; + // 'lh' of type 'D' is expensive to copy, should be passed by reference to const + // 'rh' of type 'D' is expensive to copy, should be passed by reference to const + +Options +------- +.. option:: CheckParamPassing + + Boolean flag to warn when operators pass parameters not by value for cheap + to copy types and not by reference to const for expensive to copy types. + Default value is `true`. + +.. option:: ExpensiveToCopySize + + When parameters have a size greater than `ExpensiveToCopySize` `bytes`, + treat them as expensive to copy types. + Default value is `0`, which means to use `2 * sizeof(void*)` as the + threshold. + +The relevant rules in the C++ Core Guidelines are: + +- `C.86: Make == symmetric with respect to operand types and noexcept + `_ + +- `C.161: Use non-member functions for symmetric operators + `_ + +- `F.16: For "in" parameters, pass cheaply-copied types by value and others by reference to const + `_ diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -142,6 +142,7 @@ `concurrency-thread-canceltype-asynchronous `_, `cppcoreguidelines-avoid-goto `_, `cppcoreguidelines-avoid-non-const-global-variables `_, + `cppcoreguidelines-comparison-operator `_, "Yes" `cppcoreguidelines-init-variables `_, "Yes" `cppcoreguidelines-interfaces-global-init `_, `cppcoreguidelines-macro-usage `_, diff --git a/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-comparison-operator-check-param-passing.cpp b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-comparison-operator-check-param-passing.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-comparison-operator-check-param-passing.cpp @@ -0,0 +1,47 @@ +// RUN: %check_clang_tidy %s cppcoreguidelines-comparison-operator %t -- \ +// RUN: -config="{CheckOptions: \ +// RUN: [{key: cppcoreguidelines-comparison-operator.CheckParamPassing, \ +// RUN: value: false}]}" + +/// cheap to copy +struct E1 { + int Var; +}; + +bool operator==(E1 lh, E1 rh) noexcept; // ok +bool operator==(E1 lh, const E1 &rh) noexcept; // not warn on parameter passing +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] + +bool operator==(const E1 &lh, E1 rh) noexcept; // not warn on parameter passing +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] + +bool operator==(const E1 &lh, const E1 &rh) noexcept; // not warn on parameter passing + +/// expensive to copy +struct E2 { + int Array[1024]; +}; + +bool operator==(const E2 &lh, const E2 &rh) noexcept; // ok +bool operator==(E2 lh, const E2 &rh) noexcept; // not warn on parameter passing +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] + +bool operator==(const E2 &lh, E2 rh) noexcept; // not warn on parameter passing +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] + +bool operator==(E2 lh, E2 rh) noexcept; // not warn on parameter passing + +/// incomplete type +struct E3; +bool operator==(const E3 &lh, const E3 &rh) noexcept; // ok +bool operator==(E3 lh, E3 rh) noexcept; // ok + +/// definition +struct E4 { // expensive to copy + int Array[1024]; +}; + +bool operator==(E4 lh, E4 rh) noexcept; // not warn on parameter passing +bool operator==(E4 lh, E4 rh) noexcept { // not warn on parameter passing + return true; +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-comparison-operator-expensive-to-copy-size.cpp b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-comparison-operator-expensive-to-copy-size.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-comparison-operator-expensive-to-copy-size.cpp @@ -0,0 +1,61 @@ +// RUN: %check_clang_tidy %s cppcoreguidelines-comparison-operator %t -- \ +// RUN: -config="{CheckOptions: \ +// RUN: [{key: cppcoreguidelines-comparison-operator.ExpensiveToCopySize, \ +// RUN: value: 64}]}" + +/// cheap to copy +struct E1 { + char Array[64]; +}; + +bool operator==(E1 lh, E1 rh) noexcept; // ok +bool operator==(E1 lh, const E1 &rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:34: warning: 'rh' of type 'E1' is cheap to copy, should be passed by value [cppcoreguidelines-comparison-operator] + +bool operator==(const E1 &lh, E1 rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:27: warning: 'lh' of type 'E1' is cheap to copy, should be passed by value [cppcoreguidelines-comparison-operator] + +bool operator==(const E1 &lh, const E1 &rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:27: warning: 'lh' of type 'E1' is cheap to copy, should be passed by value [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:41: warning: 'rh' of type 'E1' is cheap to copy, should be passed by value [cppcoreguidelines-comparison-operator] + +/// expensive to copy +struct E2 { + int Array[65]; +}; + +bool operator==(const E2 &lh, const E2 &rh) noexcept; // ok +bool operator==(E2 lh, const E2 &rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:20: warning: 'lh' of type 'E2' is expensive to copy, should be passed by reference to const [cppcoreguidelines-comparison-operator] + +bool operator==(const E2 &lh, E2 rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:34: warning: 'rh' of type 'E2' is expensive to copy, should be passed by reference to const [cppcoreguidelines-comparison-operator] + +bool operator==(E2 lh, E2 rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: 'lh' of type 'E2' is expensive to copy, should be passed by reference to const [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:27: warning: 'rh' of type 'E2' is expensive to copy, should be passed by reference to const [cppcoreguidelines-comparison-operator] + +/// incomplete type +struct E3; +bool operator==(const E3 &lh, const E3 &rh) noexcept; // ok +bool operator==(E3 lh, E3 rh) noexcept; // ok + +/// definition +struct E4 { // expensive to copy + int Array[65]; +}; + +bool operator==(E4 lh, E4 rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: 'lh' of type 'E4' is expensive to copy, should be passed by reference to const [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:27: warning: 'rh' of type 'E4' is expensive to copy, should be passed by reference to const [cppcoreguidelines-comparison-operator] + +bool operator==(E4 lh, E4 rh) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: 'lh' of type 'E4' is expensive to copy, should be passed by reference to const [cppcoreguidelines-comparison-operator] + // CHECK-MESSAGES: :[[@LINE-2]]:27: warning: 'rh' of type 'E4' is expensive to copy, should be passed by reference to const [cppcoreguidelines-comparison-operator] + + return true; +} diff --git a/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-comparison-operator.cpp b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-comparison-operator.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines-comparison-operator.cpp @@ -0,0 +1,194 @@ +// RUN: %check_clang_tidy %s cppcoreguidelines-comparison-operator %t -- --fix-notes + +/// member function +namespace test_member_function { +struct A { + int Var; + + bool operator==(const A &a) const; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: 'operator==' should not be member function [cppcoreguidelines-comparison-operator] + + bool operator!=(const A &a) const; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: 'operator!=' should not be member function [cppcoreguidelines-comparison-operator] + + bool operator<(const A &a) const; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: 'operator<' should not be member function [cppcoreguidelines-comparison-operator] + + bool operator<=(const A &a) const; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: 'operator<=' should not be member function [cppcoreguidelines-comparison-operator] + + bool operator>(const A &a) const; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: 'operator>' should not be member function [cppcoreguidelines-comparison-operator] + + bool operator>=(const A &a) const; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: 'operator>=' should not be member function [cppcoreguidelines-comparison-operator] +}; +} // namespace test_member_function + +/// parameters types differ +namespace test_params_type_differ { +struct B; +bool operator==(const B &lh, int rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] + +bool operator!=(const B &lh, int rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator!=' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] + +bool operator<(const B &lh, int rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator<' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] + +bool operator<=(const B &lh, int rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator<=' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] + +bool operator>(const B &lh, int rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator>' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] + +bool operator>=(const B &lh, int rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator>=' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] +} // namespace test_params_type_differ + +/// can throw +namespace test_can_throw { +struct C; +bool operator==(const C &lh, const C &rh); +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should be marked noexcept [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:6: note: mark 'noexcept' +// CHECK-FIXES: bool operator==(const C &lh, const C &rh) noexcept + +bool operator!=(const C &lh, const C &rh); +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator!=' should be marked noexcept [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:6: note: mark 'noexcept' +// CHECK-FIXES: bool operator!=(const C &lh, const C &rh) noexcept; + +bool operator<(const C &lh, const C &rh); +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator<' should be marked noexcept [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:6: note: mark 'noexcept' +// CHECK-FIXES: bool operator<(const C &lh, const C &rh) noexcept; + +bool operator<=(const C &lh, const C &rh); +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator<=' should be marked noexcept [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:6: note: mark 'noexcept' +// CHECK-FIXES: bool operator<=(const C &lh, const C &rh) noexcept; + +bool operator>(const C &lh, const C &rh); +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator>' should be marked noexcept [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:6: note: mark 'noexcept' +// CHECK-FIXES: bool operator>(const C &lh, const C &rh) noexcept; + +bool operator>=(const C &lh, const C &rh); +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator>=' should be marked noexcept [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:6: note: mark 'noexcept' +// CHECK-FIXES: bool operator>=(const C &lh, const C &rh) noexcept; +} // namespace test_can_throw + +/// ok +namespace test_ok { +struct D; +bool operator==(const D &lh, const D &rh) noexcept; +bool operator!=(const D &lh, const D &rh) noexcept; +bool operator<(const D &lh, const D &rh) noexcept; +bool operator<=(const D &lh, const D &rh) noexcept; +bool operator>(const D &lh, const D &rh) noexcept; +bool operator>=(const D &lh, const D &rh) noexcept; +} // namespace test_ok + +/// should be 'in' parameters +namespace test_parameter_passing { +/// cheap to copy +struct E1 { + int Var; +}; + +bool operator==(E1 lh, E1 rh) noexcept; // ok + +bool operator==(E1 lh, const E1 &rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:34: warning: 'rh' of type 'test_parameter_passing::E1' is cheap to copy, should be passed by value [cppcoreguidelines-comparison-operator] + +bool operator==(const E1 &lh, E1 rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:27: warning: 'lh' of type 'test_parameter_passing::E1' is cheap to copy, should be passed by value [cppcoreguidelines-comparison-operator] + +bool operator==(const E1 &lh, const E1 &rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:27: warning: 'lh' of type 'test_parameter_passing::E1' is cheap to copy, should be passed by value [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:41: warning: 'rh' of type 'test_parameter_passing::E1' is cheap to copy, should be passed by value [cppcoreguidelines-comparison-operator] + +/// expensive to copy +struct E2 { + int Array[1024]; +}; + +bool operator==(const E2 &lh, const E2 &rh) noexcept; // ok + +bool operator==(E2 lh, const E2 &rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:20: warning: 'lh' of type 'test_parameter_passing::E2' is expensive to copy, should be passed by reference to const [cppcoreguidelines-comparison-operator] + +bool operator==(const E2 &lh, E2 rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:34: warning: 'rh' of type 'test_parameter_passing::E2' is expensive to copy, should be passed by reference to const [cppcoreguidelines-comparison-operator] + +bool operator==(E2 lh, E2 rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: 'lh' of type 'test_parameter_passing::E2' is expensive to copy, should be passed by reference to const [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:27: warning: 'rh' of type 'test_parameter_passing::E2' is expensive to copy, should be passed by reference to const [cppcoreguidelines-comparison-operator] + +/// incomplete type +struct E3; +bool operator==(const E3 &lh, const E3 &rh) noexcept; // ok +bool operator==(E3 lh, E3 rh) noexcept; // ok +} // namespace test_parameter_passing + +/// warn on both declaration and definition +namespace test_warn_declaration_definition { +/// member function +struct A { + int Var; + bool operator==(const A &a) const; + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: 'operator==' should not be member function [cppcoreguidelines-comparison-operator] +}; + +bool A::operator==(const A &) const { + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: 'operator==' should not be member function [cppcoreguidelines-comparison-operator] + return true; +} + +/// parameters types differ +struct B; +bool operator==(const B &lh, int rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] + +bool operator==(const B &lh, int rh) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should have 2 parameters of the same type [cppcoreguidelines-comparison-operator] + return true; +} + +/// can throw +struct C; +bool operator==(const C &lh, const C &rh); +// CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should be marked noexcept [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:6: note: mark 'noexcept' +// CHECK-FIXES: bool operator==(const C &lh, const C &rh) noexcept + +bool operator==(const C &lh, const C &rh) { + // CHECK-MESSAGES: :[[@LINE-1]]:6: warning: 'operator==' should be marked noexcept [cppcoreguidelines-comparison-operator] + // CHECK-MESSAGES: :[[@LINE-2]]:6: note: mark 'noexcept' + // CHECK-FIXES: bool operator==(const C &lh, const C &rh) noexcept { + return true; +} + +/// should be 'in' parameters +struct E { // expensive to copy + int Array[1024]; +}; + +bool operator==(E lh, E rh) noexcept; +// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 'lh' of type 'test_warn_declaration_definition::E' is expensive to copy, should be passed by reference to const [cppcoreguidelines-comparison-operator] +// CHECK-MESSAGES: :[[@LINE-2]]:25: warning: 'rh' of type 'test_warn_declaration_definition::E' is expensive to copy, should be passed by reference to const [cppcoreguidelines-comparison-operator] + +bool operator==(E lh, E rh) noexcept { + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 'lh' of type 'test_warn_declaration_definition::E' is expensive to copy, should be passed by reference to const [cppcoreguidelines-comparison-operator] + // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: 'rh' of type 'test_warn_declaration_definition::E' is expensive to copy, should be passed by reference to const [cppcoreguidelines-comparison-operator] + + return true; +} +} // namespace test_warn_declaration_definition