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 // Only register the matchers for C++; the functionality currently does not 22 // provide any benefit to other languages, despite being benign. 23 if (!getLangOpts().CPlusPlus) 24 return; 25 26 const auto HasGoodReturnType = cxxMethodDecl(returns(lValueReferenceType( 27 pointee(unless(isConstQualified()), 28 anyOf(autoType(), hasDeclaration(equalsBoundNode("class"))))))); 29 30 const auto IsSelf = qualType( 31 anyOf(hasDeclaration(equalsBoundNode("class")), 32 referenceType(pointee(hasDeclaration(equalsBoundNode("class")))))); 33 const auto IsAssign = 34 cxxMethodDecl(unless(anyOf(isDeleted(), isPrivate(), isImplicit())), 35 hasName("operator="), ofClass(recordDecl().bind("class"))) 36 .bind("method"); 37 const auto IsSelfAssign = 38 cxxMethodDecl(IsAssign, hasParameter(0, parmVarDecl(hasType(IsSelf)))) 39 .bind("method"); 40 41 Finder->addMatcher( 42 cxxMethodDecl(IsAssign, unless(HasGoodReturnType)).bind("ReturnType"), 43 this); 44 45 const auto BadSelf = referenceType( 46 anyOf(lValueReferenceType(pointee(unless(isConstQualified()))), 47 rValueReferenceType(pointee(isConstQualified())))); 48 49 Finder->addMatcher( 50 cxxMethodDecl(IsSelfAssign, 51 hasParameter(0, parmVarDecl(hasType(BadSelf)))) 52 .bind("ArgumentType"), 53 this); 54 55 Finder->addMatcher( 56 cxxMethodDecl(IsSelfAssign, anyOf(isConst(), isVirtual())).bind("cv"), 57 this); 58 59 const auto IsBadReturnStatement = returnStmt(unless(has(ignoringParenImpCasts( 60 anyOf(unaryOperator(hasOperatorName("*"), hasUnaryOperand(cxxThisExpr())), 61 cxxOperatorCallExpr(argumentCountIs(1), 62 callee(unresolvedLookupExpr()), 63 hasArgument(0, 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 static const char *const Messages[][2] = { 77 {"ReturnType", "operator=() should return '%0&'"}, 78 {"ArgumentType", "operator=() should take '%0 const&', '%0&&' or '%0'"}, 79 {"cv", "operator=() should not be marked '%1'"}}; 80 81 const auto *Method = Result.Nodes.getNodeAs<CXXMethodDecl>("method"); 82 for (const auto &Message : Messages) { 83 if (Result.Nodes.getNodeAs<Decl>(Message[0])) 84 diag(Method->getBeginLoc(), Message[1]) 85 << Method->getParent()->getName() 86 << (Method->isConst() ? "const" : "virtual"); 87 } 88 } 89 } 90 91 } // namespace misc 92 } // namespace tidy 93 } // namespace clang 94