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::tidy::misc { |
16 | |
17 | void UnconventionalAssignOperatorCheck::registerMatchers( |
18 | ast_matchers::MatchFinder *Finder) { |
19 | const auto HasGoodReturnType = |
20 | cxxMethodDecl(returns(InnerMatcher: hasCanonicalType(InnerMatcher: lValueReferenceType(pointee( |
21 | unless(isConstQualified()), |
22 | anyOf(autoType(), hasDeclaration(InnerMatcher: equalsBoundNode(ID: "class" )))))))); |
23 | |
24 | const auto IsSelf = qualType(hasCanonicalType( |
25 | InnerMatcher: anyOf(hasDeclaration(InnerMatcher: equalsBoundNode(ID: "class" )), |
26 | referenceType(pointee(hasDeclaration(InnerMatcher: equalsBoundNode(ID: "class" ))))))); |
27 | const auto IsAssign = |
28 | cxxMethodDecl(unless(anyOf(isDeleted(), isPrivate(), isImplicit())), |
29 | hasName(Name: "operator=" ), ofClass(InnerMatcher: recordDecl().bind(ID: "class" ))) |
30 | .bind(ID: "method" ); |
31 | const auto IsSelfAssign = |
32 | cxxMethodDecl(IsAssign, hasParameter(N: 0, InnerMatcher: parmVarDecl(hasType(InnerMatcher: IsSelf)))) |
33 | .bind(ID: "method" ); |
34 | |
35 | Finder->addMatcher( |
36 | NodeMatch: cxxMethodDecl(IsAssign, unless(HasGoodReturnType)).bind(ID: "ReturnType" ), |
37 | Action: this); |
38 | |
39 | const auto BadSelf = qualType(hasCanonicalType(InnerMatcher: referenceType( |
40 | anyOf(lValueReferenceType(pointee(unless(isConstQualified()))), |
41 | rValueReferenceType(pointee(isConstQualified())))))); |
42 | |
43 | Finder->addMatcher( |
44 | NodeMatch: cxxMethodDecl(IsSelfAssign, |
45 | hasParameter(N: 0, InnerMatcher: parmVarDecl(hasType(InnerMatcher: BadSelf)))) |
46 | .bind(ID: "ArgumentType" ), |
47 | Action: this); |
48 | |
49 | Finder->addMatcher( |
50 | NodeMatch: cxxMethodDecl(IsSelfAssign, anyOf(isConst(), isVirtual())).bind(ID: "cv" ), |
51 | Action: this); |
52 | |
53 | const auto IsBadReturnStatement = returnStmt(unless(has(ignoringParenImpCasts( |
54 | InnerMatcher: anyOf(unaryOperator(hasOperatorName(Name: "*" ), hasUnaryOperand(InnerMatcher: cxxThisExpr())), |
55 | cxxOperatorCallExpr(argumentCountIs(N: 1), |
56 | callee(InnerMatcher: unresolvedLookupExpr()), |
57 | hasArgument(N: 0, InnerMatcher: cxxThisExpr())), |
58 | cxxOperatorCallExpr( |
59 | hasOverloadedOperatorName(Name: "=" ), |
60 | hasArgument( |
61 | N: 0, InnerMatcher: unaryOperator(hasOperatorName(Name: "*" ), |
62 | hasUnaryOperand(InnerMatcher: cxxThisExpr()))))))))); |
63 | const auto IsGoodAssign = cxxMethodDecl(IsAssign, HasGoodReturnType); |
64 | |
65 | Finder->addMatcher(NodeMatch: returnStmt(IsBadReturnStatement, forFunction(InnerMatcher: IsGoodAssign)) |
66 | .bind(ID: "returnStmt" ), |
67 | Action: this); |
68 | } |
69 | |
70 | void UnconventionalAssignOperatorCheck::check( |
71 | const MatchFinder::MatchResult &Result) { |
72 | if (const auto *RetStmt = Result.Nodes.getNodeAs<ReturnStmt>(ID: "returnStmt" )) { |
73 | diag(Loc: RetStmt->getBeginLoc(), Description: "operator=() should always return '*this'" ); |
74 | } else { |
75 | const auto *Method = Result.Nodes.getNodeAs<CXXMethodDecl>(ID: "method" ); |
76 | if (Result.Nodes.getNodeAs<CXXMethodDecl>(ID: "ReturnType" )) |
77 | diag(Method->getBeginLoc(), "operator=() should return '%0&'" ) |
78 | << Method->getParent()->getName(); |
79 | if (Result.Nodes.getNodeAs<CXXMethodDecl>(ID: "ArgumentType" )) |
80 | diag(Method->getBeginLoc(), |
81 | "operator=() should take '%0 const&'%select{|, '%0&&'}1 or '%0'" ) |
82 | << Method->getParent()->getName() << getLangOpts().CPlusPlus11; |
83 | if (Result.Nodes.getNodeAs<CXXMethodDecl>(ID: "cv" )) |
84 | diag(Method->getBeginLoc(), |
85 | "operator=() should not be marked '%select{const|virtual}0'" ) |
86 | << !Method->isConst(); |
87 | } |
88 | } |
89 | |
90 | } // namespace clang::tidy::misc |
91 | |