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 @@ -27,6 +27,7 @@ ProTypeVarargCheck.cpp SlicingCheck.cpp SpecialMemberFunctionsCheck.cpp + SymmetricBinaryOperatorCheck.cpp VirtualClassDestructorCheck.cpp LINK_LIBS 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 @@ -36,6 +36,7 @@ #include "ProTypeVarargCheck.h" #include "SlicingCheck.h" #include "SpecialMemberFunctionsCheck.h" +#include "SymmetricBinaryOperatorCheck.h" #include "VirtualClassDestructorCheck.h" namespace clang { @@ -98,6 +99,8 @@ CheckFactories.registerCheck("cppcoreguidelines-slicing"); CheckFactories.registerCheck( "cppcoreguidelines-c-copy-assignment-signature"); + CheckFactories.registerCheck( + "cppcoreguidelines-symmetric-binary-operator"); CheckFactories.registerCheck( "cppcoreguidelines-virtual-class-destructor"); } diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/SymmetricBinaryOperatorCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/SymmetricBinaryOperatorCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/SymmetricBinaryOperatorCheck.h @@ -0,0 +1,38 @@ +//===--- SymmetricBinaryOperatorCheck.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_SYMMETRICBINARYOPERATORCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_SYMMETRICBINARYOPERATORCHECK_H + +#include "../utils/TransformerClangTidyCheck.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// Finds operators that are symmetric with respect to the type of their +/// parameters. Warns that such operators should be friend functions or free +/// functions. Generates fixes to change defaulted comparison operators to +/// defaulted friend operators in c++20 or higher. See also C.161. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines/symmetric-binary-operator.html +class SymmetricBinaryOperatorCheck : public utils::TransformerClangTidyCheck { +public: + SymmetricBinaryOperatorCheck(StringRef Name, ClangTidyContext *Context); + + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_SYMMETRICBINARYOPERATORCHECK_H diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/SymmetricBinaryOperatorCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/SymmetricBinaryOperatorCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/SymmetricBinaryOperatorCheck.cpp @@ -0,0 +1,169 @@ +//===--- SymmetricBinaryOperatorCheck.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 "SymmetricBinaryOperatorCheck.h" +#include "../utils/LexerUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/OperatorKinds.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Transformer/RangeSelector.h" +#include "clang/Tooling/Transformer/RewriteRule.h" +#include "clang/Tooling/Transformer/Stencil.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/ErrorHandling.h" +#include + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { +using namespace transformer; + +bool isPotentialSymmetricBinaryOperator(OverloadedOperatorKind Kind) { + return llvm::is_contained( + std::initializer_list{ +#define OVERLOADED_OPERATOR_IS_BINARY_true(Name) OO_##Name, +#define OVERLOADED_OPERATOR_IS_BINARY_false(Name) + +#define OVERLOADED_OPERATOR_IS_ASSIGNMENT_true(Name, Binary) +#define OVERLOADED_OPERATOR_IS_ASSIGNMENT_false(Name, Binary) \ + OVERLOADED_OPERATOR_IS_BINARY_##Binary(Name) + +#define OVERLOADED_OPERATOR(Name, Spelling, Token, Unary, Binary, MemberOnly) \ + OVERLOADED_OPERATOR_IS_ASSIGNMENT_##MemberOnly(Name, Binary) +#define OVERLOADED_OPERATOR_MULTI(Name, Spelling, Unary, Binary, MemberOnly) + +#include "clang/Basic/OperatorKinds.def" + }, + Kind); +} + +bool isComparisonOperator(OverloadedOperatorKind Kind) { + std::bitset BitSetCompOps{}; + BitSetCompOps.set(OO_Less); + BitSetCompOps.set(OO_Greater); + BitSetCompOps.set(OO_EqualEqual); + BitSetCompOps.set(OO_ExclaimEqual); + BitSetCompOps.set(OO_LessEqual); + BitSetCompOps.set(OO_GreaterEqual); + BitSetCompOps.set(OO_Spaceship); + BitSetCompOps.set(OO_AmpAmp); + BitSetCompOps.set(OO_PipePipe); + return BitSetCompOps[Kind]; +} + +AST_MATCHER(CXXMethodDecl, isPotentialSymmetricBinaryOperator) { + return isPotentialSymmetricBinaryOperator(Node.getOverloadedOperator()); +} + +AST_MATCHER(CXXMethodDecl, isComparisonOperator) { + return isComparisonOperator(Node.getOverloadedOperator()); +} + +static DynTypedNode getNode(const BoundNodes &Nodes, StringRef ID) { + auto &NodesMap = Nodes.getMap(); + auto It = NodesMap.find(ID); + assert(It != NodesMap.end() && "getNode called with unbound ID"); + return It->second; +} + +static RangeSelector constSpec(std::string ID) { + return [ID = std::move(ID)](const MatchFinder::MatchResult &Result) + -> Expected { + DynTypedNode Node = getNode(Result.Nodes, ID); + if (const auto *Method = Node.get()) { + const SourceLocation ParensEndLoc = + Method->getFunctionTypeLoc().getParensRange().getEnd(); + + const auto GetNextToken = [&](const SourceLocation Loc) { + return utils::lexer::findNextTokenSkippingComments( + Loc, *Result.SourceManager, Result.Context->getLangOpts()); + }; + + llvm::Optional OptToken{GetNextToken(ParensEndLoc)}; + + const auto IsConstToken = [](const Token &Tok) { + return Tok.is(tok::TokenKind::kw_const) || + (Tok.is(tok::TokenKind::raw_identifier) && + Tok.getRawIdentifier().equals("const")); + }; + + while (OptToken.has_value() && !IsConstToken(OptToken.value()) && + !OptToken.value().isOneOf(tok::TokenKind::l_brace, + tok::TokenKind::kw_default, + tok::TokenKind::semi)) { + OptToken = GetNextToken(OptToken.value().getLocation()); + } + + if (OptToken.has_value() && IsConstToken(OptToken.value())) { + const auto &Token = OptToken.value(); + return CharSourceRange::getCharRange(Token.getLocation(), + Token.getEndLoc()); + } + // required on defaulted comparison operator members which is the only + // bound node constSpec gets called on + llvm_unreachable( + "constSpec invalid argument: member function is not const qualified"); + } + llvm_unreachable("constSpec invalid argument: expected ID to be bound " + "to a CXXMethodDecl"); + }; +} + +static RewriteRuleWith makeRewriteRule(bool CPlusPlus20) { + const auto ParameterOfOwnType = + parmVarDecl(anyOf(hasType(qualType(references( + cxxRecordDecl(equalsBoundNode("record"))))), + hasType(injectedClassNameType()))) + .bind("param"); + + const auto IsBinaryOperator = + cxxMethodDecl(isPotentialSymmetricBinaryOperator(), + hasParameter(0, ParameterOfOwnType)); + + // Using hasParent instead of ofClass because we want to match declarations + // within the CXXRecord and not an outside definition + const auto BaseMatch = + cxxMethodDecl(hasParent(cxxRecordDecl().bind("record")), IsBinaryOperator, + unless(hasAncestor(functionDecl()))) + .bind("op"); + const auto MatchDefaultedComparisonOperator = + cxxMethodDecl(BaseMatch, isDefaulted(), isComparisonOperator()); + + const auto AddFriendToFunctionDecl = insertBefore(node("op"), cat("friend ")); + const auto RemoveConst = remove(constSpec("op")); + const auto AddSecondParameterToDeclaration = + changeTo(node("param"), + cat("const ", name("record"), "&, const ", name("record"), "&")); + + const auto WarningMessage = cat( + "symmetric binary operator should be a friend or free operator function"); + + // c++20 allows defaulted comparison operators we can provide a fix for, + // otherwise just warn + if (CPlusPlus20) { + return applyFirst( + {makeRule(MatchDefaultedComparisonOperator, + {AddFriendToFunctionDecl, RemoveConst, + AddSecondParameterToDeclaration}, + WarningMessage), + makeRule(BaseMatch, noopEdit(node("op")), WarningMessage)}); + } + return makeRule(BaseMatch, noopEdit(node("op")), WarningMessage); +} + +SymmetricBinaryOperatorCheck::SymmetricBinaryOperatorCheck( + StringRef Name, ClangTidyContext *Context) + : TransformerClangTidyCheck( + makeRewriteRule(Context->getLangOpts().CPlusPlus20), Name, Context) {} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang 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 @@ -104,6 +104,10 @@ Warns when a struct or class uses const or reference (lvalue or rvalue) data members. +- New :doc:`cppcoreguidelines-symmetric-binary-operator ` check. + + Warns that symmetric binary operators should be friend or free functions. + New check aliases ^^^^^^^^^^^^^^^^^ @@ -120,7 +124,7 @@ ` check. Partial support for C++14 signal handler rules was added. Bug report generation was improved. - + - Improved `modernize-use-emplace `_ check. The check now supports detecting inefficient invocations of ``push`` and diff --git a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/symmetric-binary-operator.rst b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/symmetric-binary-operator.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines/symmetric-binary-operator.rst @@ -0,0 +1,35 @@ +.. title:: clang-tidy - cppcoreguidelines-symmetric-binary-operator + +cppcoreguidelines-symmetric-binary-operator +=========================================== + +Warns that symmetric binary operators should be friend or free functions. + +Operators are considered symmetric if the type of lhs and rhs are the same as the record they are declared in. + +Given a simple struct with member operators +(incomplete set of operators, for demonstration purposes only) + +.. code-block:: c++ + + struct A { + int X; + // Member-operator that is symmetric, defaulted and a comparison operator + bool operator==(const A &) const = default; // Warn (+ fix in c++20 or higher) + A operator+(const A &rhs) const { return A{X + rhs.X}; } // Warn + }; + +the check matches both member operators ``operator==`` and ``operator+`` and +warns on both occasions that it is a symmetric binary operator that should be a friend or free function. +Additionally, a fix is created for symmetric comparison operators in c++20 or higher, ``operator==`` in the example, +that rewrites the operator to a friend operator function + +.. code-block:: c++ + + struct A { + int X; + friend bool operator==(const A &, const A &) = default; // Rewritten operator + A operator+(const A &rhs) const { return A{X + rhs.X}; } + }; + +The check is based on `C++ Core Guidelines C.161 ` 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 @@ -198,6 +198,7 @@ `cppcoreguidelines-pro-type-vararg `_, `cppcoreguidelines-slicing `_, `cppcoreguidelines-special-member-functions `_, + `cppcoreguidelines-symmetric-binary-operator `_, "Yes" `cppcoreguidelines-virtual-class-destructor `_, "Yes" `darwin-avoid-spinlock `_, `darwin-dispatch-once-nonstatic `_, "Yes" diff --git a/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/symmetric-binary-operator-cpp11.cpp b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/symmetric-binary-operator-cpp11.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/symmetric-binary-operator-cpp11.cpp @@ -0,0 +1,487 @@ +// RUN: %check_clang_tidy -std=c++11 %s cppcoreguidelines-symmetric-binary-operator %t + +namespace std { +class string { +public: + friend bool operator!=(const string &, const string &) { return true; } + friend bool operator<(const string &, const string &) { return true; } +}; +template +class vector { +public: + int size() const; + const T &operator[](int) const; + friend bool operator==(const vector &, const vector &) = default; +}; +} // namespace std + +using size_t = unsigned long long; + +namespace test_base { +struct A { + int X; + bool operator==(const A &) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +struct A2 { + int X; + bool operator==(const A2 &rhs) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +struct B { + int X; + friend bool operator==(const B &, const B &) = default; +}; + +template +struct C { + int X; + bool operator==(const C &) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +struct D { + int X; + friend bool operator==(const D &, const D &) = default; +}; + +struct E { + int X; + bool operator==(const E &) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +struct F { + int X; + friend bool operator==(const F &, const F &) = default; +}; + +template +struct G { + int X; + bool operator==(const G &) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +struct H { + int X; + friend bool operator==(const H &, const H &) = default; +}; + +template +struct I { + int X; + bool operator==(const I&) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +struct J { + int X; + bool operator==(const J&) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +struct K { + int X; + bool operator==(const K&) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; +} // namespace test_base + +namespace test_constexpr { +struct A { + int X; + constexpr bool operator==(const A &) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + }; + +struct B { + int X; + friend constexpr bool operator==(const B &, const B &) = default; +}; + +template +struct C { + int X; + constexpr bool operator==(const C &) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + }; + +template +struct D { + int X; + friend constexpr bool operator==(const D &, const D &) = default; +}; + +struct E { + int X; + constexpr bool operator==(const E &) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + }; + +struct F { + int X; + friend constexpr bool operator==(const F &, const F &) = default; +}; + +template +struct G { + int X; + constexpr bool operator==(const G &) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +struct H { + int X; + friend constexpr bool operator==(const H &, const H &) = default; +}; +} // namespace test_constexpr + +namespace test_trailing_return_type { +struct A { + int X; + auto operator==(const A &) const -> bool = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +struct B { + int X; + friend auto operator==(const B &, const B &) -> bool = default; +}; + +template +struct C { + int X; + auto operator==(const C &) const -> bool = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +struct D { + int X; + friend auto operator==(const D &, const D &) -> bool = default; +}; + +struct E { + int X; + auto operator==(const E &) const -> bool = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +struct F { + int X; + friend auto operator==(const F &, const F &) -> bool = default; +}; + +template +struct G { + int X; + auto operator==(const G &) const -> bool = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +struct H { + int X; + friend auto operator==(const H &, const H &) -> bool = default; +}; +} // namespace test_trailing_return_type + +namespace non_default { +struct A { + int X; + bool operator==(const A &rhs) const { + return X == rhs.X; + } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +struct C { + int X; + bool operator==(const C &rhs) const { + return X == rhs.X; + } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; +} // namespace non_default + +namespace non_const { +struct A { + int X; + bool operator==(const A &rhs) { + return X == rhs.X; + } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +struct C { + int X; + bool operator==(const C &rhs) { + return X == rhs.X; + } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; +} // namespace non_const + +namespace out_of_class_definition { +struct A { + int X; + bool operator==(const A &rhs) const; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +bool A::operator==(const A &rhs) const { + return X == rhs.X; +} + +struct B { + int X; + bool operator==(const B &rhs) const; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +struct C { + int X; + bool operator==(const C &rhs) const; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +bool C::operator==(const C &rhs) const { + return X == rhs.X; +} +} // namespace out_of_class_definition + +bool out_of_class_definition::B::operator==(const B &rhs) const { + return X == rhs.X; +} + +namespace templates_no_match { +template +struct A { + bool operator==(const T &); +}; + +struct B { + template + bool operator==(const T &); +}; + +template +struct C { + template + bool operator==(const C &); + bool operator!=(const C &); +}; + +} // namespace templates_no_match + +namespace test_base_definition_not_visible { +struct A { + int X; + bool operator==(const A &) const; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +struct A2 { + int X; + bool operator==(const A2 &rhs) const; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +struct B { + int X; + friend bool operator==(const B &, const B &); +}; + +template +struct C { + int X; + bool operator==(const C &) const; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +struct D { + int X; + friend bool operator==(const D &, const D &); +}; + +struct E { + int X; + bool operator==(const E &) const; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +struct F { + int X; + friend bool operator==(const F &, const F &); +}; + +template +struct G { + int X; + bool operator==(const G &) const; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +struct H { + int X; + friend bool operator==(const H &, const H &); +}; +} // namespace test_base_definition_not_visible + +namespace additional_operators { +struct A { + bool operator<(const A &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator+(const A &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator<<(const A &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator&&(const A &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator,(const A &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + + bool operator<(const int &); + bool operator+(const int &); + bool operator<<(const int &); + bool operator&&(const int &); + bool operator,(const int &); +}; + +template +struct B { + bool operator<(const B &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator+(const B &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator<<(const B &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator&&(const B &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator,(const B &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + + bool operator<(const int &); + bool operator+(const int &); + bool operator<<(const int &); + bool operator&&(const int &); + bool operator,(const int &); +}; + +} // namespace additional_operators + +namespace test_noexcept { + +struct A { + int X; + bool operator==(const A &) const noexcept = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +struct A2 { + int X; + bool operator==(const A2 &rhs) const noexcept = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; +} // namespace test_noexcept + +namespace llvm_omp_testcase { +template +using SetVector = ::std::vector; + +enum class ChangeStatus { + CHANGED, + UNCHANGED, +}; + +ChangeStatus operator|(ChangeStatus l, ChangeStatus r); +ChangeStatus &operator|=(ChangeStatus &l, ChangeStatus r); +ChangeStatus operator&(ChangeStatus l, ChangeStatus r); +ChangeStatus &operator&=(ChangeStatus &l, ChangeStatus r); + +struct AbstractState { + virtual ~AbstractState() = default; +}; + +template +struct IntegerStateBase : public AbstractState { + using base_t = base_ty; + + IntegerStateBase() = default; + IntegerStateBase(base_t Assumed) : Assumed(Assumed) {} + + static constexpr base_t getBestState() { return BestState; } + static constexpr base_t getBestState(const IntegerStateBase &) { + return getBestState(); + } + + static constexpr base_t getWorstState() { return WorstState; } + static constexpr base_t getWorstState(const IntegerStateBase &) { + return getWorstState(); + } + + base_t getKnown() const { return Known; } + + base_t getAssumed() const { return Assumed; } + + bool + operator==(const IntegerStateBase &R) const { + return this->getAssumed() == R.getAssumed() && + this->getKnown() == R.getKnown(); + } + // CHECK-MESSAGES: :[[@LINE-5]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + + bool + operator!=(const IntegerStateBase &R) const { + return !(*this == R); + } + // CHECK-MESSAGES: :[[@LINE-4]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + +protected: + virtual void handleNewAssumedValue(base_t Value) = 0; + + virtual void handleNewKnownValue(base_t Value) = 0; + + base_t Known = getWorstState(); + + base_t Assumed = getBestState(); +}; + +struct BooleanState : public IntegerStateBase { + using super = IntegerStateBase; + using base_t = IntegerStateBase::base_t; + + BooleanState() = default; + BooleanState(base_t Assumed) : super(Assumed) {} +}; + +template +struct BooleanStateWithSetVector : public BooleanState { + bool operator==(const BooleanStateWithSetVector &RHS) const { + return BooleanState::operator==(RHS) && Set == RHS.Set; + } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator!=(const BooleanStateWithSetVector &RHS) const { + return !(*this == RHS); + } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + +private: + SetVector Set; +}; + +} // namespace llvm_omp_testcase diff --git a/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/symmetric-binary-operator.cpp b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/symmetric-binary-operator.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/cppcoreguidelines/symmetric-binary-operator.cpp @@ -0,0 +1,505 @@ +// RUN: %check_clang_tidy -std=c++20 %s cppcoreguidelines-symmetric-binary-operator %t + +namespace std { +class string { +public: + friend bool operator!=(const string &, const string &) { return true; } + friend bool operator<(const string &, const string &) { return true; } +}; +template +class vector { +public: + int size() const; + const T &operator[](int) const; + friend bool operator==(const vector &, const vector &) = default; +}; +} // namespace std + +using size_t = unsigned long long; + +namespace test_base { +struct A { + int X; + bool operator==(const A &) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend bool operator==(const A&, const A&) = default; +}; + +struct A2 { + int X; + bool operator==(const A2 &rhs) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend bool operator==(const A2&, const A2&) = default; +}; + +struct B { + int X; + friend bool operator==(const B &, const B &) = default; +}; + +template +struct C { + int X; + bool operator==(const C &) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend bool operator==(const C&, const C&) = default; +}; + +template +struct D { + int X; + friend bool operator==(const D &, const D &) = default; +}; + +struct E { + int X; + bool operator==(const E &) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend bool operator==(const E&, const E&) = default; +}; + +struct F { + int X; + friend bool operator==(const F &, const F &) = default; +}; + +template +struct G { + int X; + bool operator==(const G &) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend bool operator==(const G&, const G&) = default; +}; + +template +struct H { + int X; + friend bool operator==(const H &, const H &) = default; +}; + +template +struct I { + int X; + bool operator==(const I&) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend bool operator==(const I&, const I&) = default; +}; + +template +struct J { + int X; + bool operator==(const J&) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend bool operator==(const J&, const J&) = default; +}; + +template +struct K { + int X; + bool operator==(const K&) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend bool operator==(const K&, const K&) = default; +}; +} // namespace test_base + +namespace test_constexpr { +struct A { + int X; + constexpr bool operator==(const A &) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend constexpr bool operator==(const A&, const A&) = default; +}; + +struct B { + int X; + friend constexpr bool operator==(const B &, const B &) = default; +}; + +template +struct C { + int X; + constexpr bool operator==(const C &) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend constexpr bool operator==(const C&, const C&) = default; +}; + +template +struct D { + int X; + friend constexpr bool operator==(const D &, const D &) = default; +}; + +struct E { + int X; + constexpr bool operator==(const E &) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend constexpr bool operator==(const E&, const E&) = default; +}; + +struct F { + int X; + friend constexpr bool operator==(const F &, const F &) = default; +}; + +template +struct G { + int X; + constexpr bool operator==(const G &) const = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend constexpr bool operator==(const G&, const G&) = default; +}; + +template +struct H { + int X; + friend constexpr bool operator==(const H &, const H &) = default; +}; +} // namespace test_constexpr + +namespace test_trailing_return_type { +struct A { + int X; + auto operator==(const A &) const -> bool = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend auto operator==(const A&, const A&) -> bool = default; +}; + +struct B { + int X; + friend auto operator==(const B &, const B &) -> bool = default; +}; + +template +struct C { + int X; + auto operator==(const C &) const -> bool = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend auto operator==(const C&, const C&) -> bool = default; +}; + +template +struct D { + int X; + friend auto operator==(const D &, const D &) -> bool = default; +}; + +struct E { + int X; + auto operator==(const E &) const -> bool = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend auto operator==(const E&, const E&) -> bool = default; +}; + +struct F { + int X; + friend auto operator==(const F &, const F &) -> bool = default; +}; + +template +struct G { + int X; + auto operator==(const G &) const -> bool = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend auto operator==(const G&, const G&) -> bool = default; +}; + +template +struct H { + int X; + friend auto operator==(const H &, const H &) -> bool = default; +}; +} // namespace test_trailing_return_type + +namespace non_default { +struct A { + int X; + bool operator==(const A &rhs) const { + return X == rhs.X; + } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +struct C { + int X; + bool operator==(const C &rhs) const { + return X == rhs.X; + } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; +} // namespace non_default + +namespace non_const { +struct A { + int X; + bool operator==(const A &rhs) { + return X == rhs.X; + } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +struct C { + int X; + bool operator==(const C &rhs) { + return X == rhs.X; + } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; +} // namespace non_const + +namespace out_of_class_definition { +struct A { + int X; + bool operator==(const A &rhs) const; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +bool A::operator==(const A &rhs) const { + return X == rhs.X; +} + +struct B { + int X; + bool operator==(const B &rhs) const; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +struct C { + int X; + bool operator==(const C &rhs) const; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +bool C::operator==(const C &rhs) const { + return X == rhs.X; +} +} // namespace out_of_class_definition + +bool out_of_class_definition::B::operator==(const B &rhs) const { + return X == rhs.X; +} + +namespace templates_no_match { +template +struct A { + bool operator==(const T &); +}; + +struct B { + template + bool operator==(const T &); +}; + +template +struct C { + template + bool operator==(const C &); + bool operator!=(const C &); +}; + +} // namespace templates_no_match + +namespace test_base_definition_not_visible { +struct A { + int X; + bool operator==(const A &) const; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +struct A2 { + int X; + bool operator==(const A2 &rhs) const; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +struct B { + int X; + friend bool operator==(const B &, const B &); +}; + +template +struct C { + int X; + bool operator==(const C &) const; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +struct D { + int X; + friend bool operator==(const D &, const D &); +}; + +struct E { + int X; + bool operator==(const E &) const; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +struct F { + int X; + friend bool operator==(const F &, const F &); +}; + +template +struct G { + int X; + bool operator==(const G &) const; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] +}; + +template +struct H { + int X; + friend bool operator==(const H &, const H &); +}; +} // namespace test_base_definition_not_visible + +namespace additional_operators { +struct A { + bool operator<(const A &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator+(const A &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator<<(const A &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator&&(const A &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator,(const A &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + + bool operator<(const int &); + bool operator+(const int &); + bool operator<<(const int &); + bool operator&&(const int &); + bool operator,(const int &); +}; + +template +struct B { + bool operator<(const B &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator+(const B &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator<<(const B &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator&&(const B &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator,(const B &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + + bool operator<(const int &); + bool operator+(const int &); + bool operator<<(const int &); + bool operator&&(const int &); + bool operator,(const int &); +}; + +} // namespace additional_operators + +namespace test_noexcept { + +struct A { + int X; + bool operator==(const A &) const noexcept = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend bool operator==(const A&, const A&) noexcept = default; +}; + +struct A2 { + int X; + bool operator==(const A2 &rhs) const noexcept = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + // CHECK-FIXES: friend bool operator==(const A2&, const A2&) noexcept = default; +}; +} // namespace test_noexcept + +namespace llvm_omp_testcase { +template +using SetVector = ::std::vector; + +enum class ChangeStatus { + CHANGED, + UNCHANGED, +}; + +ChangeStatus operator|(ChangeStatus l, ChangeStatus r); +ChangeStatus &operator|=(ChangeStatus &l, ChangeStatus r); +ChangeStatus operator&(ChangeStatus l, ChangeStatus r); +ChangeStatus &operator&=(ChangeStatus &l, ChangeStatus r); + +struct AbstractState { + virtual ~AbstractState() = default; +}; + +template +struct IntegerStateBase : public AbstractState { + using base_t = base_ty; + + IntegerStateBase() = default; + IntegerStateBase(base_t Assumed) : Assumed(Assumed) {} + + static constexpr base_t getBestState() { return BestState; } + static constexpr base_t getBestState(const IntegerStateBase &) { + return getBestState(); + } + + static constexpr base_t getWorstState() { return WorstState; } + static constexpr base_t getWorstState(const IntegerStateBase &) { + return getWorstState(); + } + + base_t getKnown() const { return Known; } + + base_t getAssumed() const { return Assumed; } + + bool + operator==(const IntegerStateBase &R) const { + return this->getAssumed() == R.getAssumed() && + this->getKnown() == R.getKnown(); + } + // CHECK-MESSAGES: :[[@LINE-5]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + + bool + operator!=(const IntegerStateBase &R) const { + return !(*this == R); + } + // CHECK-MESSAGES: :[[@LINE-4]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + +protected: + virtual void handleNewAssumedValue(base_t Value) = 0; + + virtual void handleNewKnownValue(base_t Value) = 0; + + base_t Known = getWorstState(); + + base_t Assumed = getBestState(); +}; + +struct BooleanState : public IntegerStateBase { + using super = IntegerStateBase; + using base_t = IntegerStateBase::base_t; + + BooleanState() = default; + BooleanState(base_t Assumed) : super(Assumed) {} +}; + +template +struct BooleanStateWithSetVector : public BooleanState { + bool operator==(const BooleanStateWithSetVector &RHS) const { + return BooleanState::operator==(RHS) && Set == RHS.Set; + } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + bool operator!=(const BooleanStateWithSetVector &RHS) const { + return !(*this == RHS); + } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: symmetric binary operator should be a friend or free operator function [cppcoreguidelines-symmetric-binary-operator] + +private: + SetVector Set; +}; + +} // namespace llvm_omp_testcase