1 //===--- UnconventionalAssignOperatorCheck.cpp - clang-tidy -----*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "UnconventionalAssignOperatorCheck.h" 10 #include "clang/ASTMatchers/ASTMatchFinder.h" 11 #include "clang/ASTMatchers/ASTMatchers.h" 12 13 using namespace clang::ast_matchers; 14 15 namespace clang { 16 namespace tidy { 17 namespace misc { 18 19 void UnconventionalAssignOperatorCheck::registerMatchers( 20 ast_matchers::MatchFinder *Finder) { 21 const auto HasGoodReturnType = cxxMethodDecl(returns(lValueReferenceType( 22 pointee(unless(isConstQualified()), 23 anyOf(autoType(), hasDeclaration(equalsBoundNode("class"))))))); 24 25 const auto IsSelf = qualType( 26 anyOf(hasDeclaration(equalsBoundNode("class")), 27 referenceType(pointee(hasDeclaration(equalsBoundNode("class")))))); 28 const auto IsAssign = 29 cxxMethodDecl(unless(anyOf(isDeleted(), isPrivate(), isImplicit())), 30 hasName("operator="), ofClass(recordDecl().bind("class"))) 31 .bind("method"); 32 const auto IsSelfAssign = 33 cxxMethodDecl(IsAssign, hasParameter(0, parmVarDecl(hasType(IsSelf)))) 34 .bind("method"); 35 36 Finder->addMatcher( 37 cxxMethodDecl(IsAssign, unless(HasGoodReturnType)).bind("ReturnType"), 38 this); 39 40 const auto BadSelf = referenceType( 41 anyOf(lValueReferenceType(pointee(unless(isConstQualified()))), 42 rValueReferenceType(pointee(isConstQualified())))); 43 44 Finder->addMatcher( 45 cxxMethodDecl(IsSelfAssign, 46 hasParameter(0, parmVarDecl(hasType(BadSelf)))) 47 .bind("ArgumentType"), 48 this); 49 50 Finder->addMatcher( 51 cxxMethodDecl(IsSelfAssign, anyOf(isConst(), isVirtual())).bind("cv"), 52 this); 53 54 const auto IsBadReturnStatement = returnStmt(unless(has(ignoringParenImpCasts( 55 anyOf(unaryOperator(hasOperatorName("*"), hasUnaryOperand(cxxThisExpr())), 56 cxxOperatorCallExpr(argumentCountIs(1), 57 callee(unresolvedLookupExpr()), 58 hasArgument(0, cxxThisExpr())), 59 cxxOperatorCallExpr( 60 hasOverloadedOperatorName("="), 61 hasArgument( 62 0, unaryOperator(hasOperatorName("*"), 63 hasUnaryOperand(cxxThisExpr()))))))))); 64 const auto IsGoodAssign = cxxMethodDecl(IsAssign, HasGoodReturnType); 65 66 Finder->addMatcher(returnStmt(IsBadReturnStatement, forFunction(IsGoodAssign)) 67 .bind("returnStmt"), 68 this); 69 } 70 71 void UnconventionalAssignOperatorCheck::check( 72 const MatchFinder::MatchResult &Result) { 73 if (const auto *RetStmt = Result.Nodes.getNodeAs<ReturnStmt>("returnStmt")) { 74 diag(RetStmt->getBeginLoc(), "operator=() should always return '*this'"); 75 } else { 76 const auto *Method = Result.Nodes.getNodeAs<CXXMethodDecl>("method"); 77 if (Result.Nodes.getNodeAs<CXXMethodDecl>("ReturnType")) 78 diag(Method->getBeginLoc(), "operator=() should return '%0&'") 79 << Method->getParent()->getName(); 80 if (Result.Nodes.getNodeAs<CXXMethodDecl>("ArgumentType")) 81 diag(Method->getBeginLoc(), 82 "operator=() should take '%0 const&'%select{|, '%0&&'}1 or '%0'") 83 << Method->getParent()->getName() << getLangOpts().CPlusPlus11; 84 if (Result.Nodes.getNodeAs<CXXMethodDecl>("cv")) 85 diag(Method->getBeginLoc(), 86 "operator=() should not be marked '%select{const|virtual}0'") 87 << !Method->isConst(); 88 } 89 } 90 91 } // namespace misc 92 } // namespace tidy 93 } // namespace clang 94