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