annotate clang-tools-extra/clang-tidy/readability/ContainerSizeEmptyCheck.cpp @ 150:1d019706d866

LLVM10
author anatofuz
date Thu, 13 Feb 2020 15:10:13 +0900
parents
children 0572611fdcc8
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
150
anatofuz
parents:
diff changeset
1 //===--- ContainerSizeEmptyCheck.cpp - clang-tidy -------------------------===//
anatofuz
parents:
diff changeset
2 //
anatofuz
parents:
diff changeset
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
anatofuz
parents:
diff changeset
4 // See https://llvm.org/LICENSE.txt for license information.
anatofuz
parents:
diff changeset
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
anatofuz
parents:
diff changeset
6 //
anatofuz
parents:
diff changeset
7 //===----------------------------------------------------------------------===//
anatofuz
parents:
diff changeset
8 #include "ContainerSizeEmptyCheck.h"
anatofuz
parents:
diff changeset
9 #include "../utils/ASTUtils.h"
anatofuz
parents:
diff changeset
10 #include "../utils/Matchers.h"
anatofuz
parents:
diff changeset
11 #include "clang/AST/ASTContext.h"
anatofuz
parents:
diff changeset
12 #include "clang/ASTMatchers/ASTMatchers.h"
anatofuz
parents:
diff changeset
13 #include "clang/Lex/Lexer.h"
anatofuz
parents:
diff changeset
14 #include "llvm/ADT/StringRef.h"
anatofuz
parents:
diff changeset
15
anatofuz
parents:
diff changeset
16 using namespace clang::ast_matchers;
anatofuz
parents:
diff changeset
17
anatofuz
parents:
diff changeset
18 namespace clang {
anatofuz
parents:
diff changeset
19 namespace tidy {
anatofuz
parents:
diff changeset
20 namespace readability {
anatofuz
parents:
diff changeset
21
anatofuz
parents:
diff changeset
22 using utils::IsBinaryOrTernary;
anatofuz
parents:
diff changeset
23
anatofuz
parents:
diff changeset
24 ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,
anatofuz
parents:
diff changeset
25 ClangTidyContext *Context)
anatofuz
parents:
diff changeset
26 : ClangTidyCheck(Name, Context) {}
anatofuz
parents:
diff changeset
27
anatofuz
parents:
diff changeset
28 void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) {
anatofuz
parents:
diff changeset
29 // Only register the matchers for C++; the functionality currently does not
anatofuz
parents:
diff changeset
30 // provide any benefit to other languages, despite being benign.
anatofuz
parents:
diff changeset
31 if (!getLangOpts().CPlusPlus)
anatofuz
parents:
diff changeset
32 return;
anatofuz
parents:
diff changeset
33
anatofuz
parents:
diff changeset
34 const auto ValidContainer = qualType(hasUnqualifiedDesugaredType(
anatofuz
parents:
diff changeset
35 recordType(hasDeclaration(cxxRecordDecl(isSameOrDerivedFrom(
anatofuz
parents:
diff changeset
36 namedDecl(
anatofuz
parents:
diff changeset
37 has(cxxMethodDecl(
anatofuz
parents:
diff changeset
38 isConst(), parameterCountIs(0), isPublic(),
anatofuz
parents:
diff changeset
39 hasName("size"),
anatofuz
parents:
diff changeset
40 returns(qualType(isInteger(), unless(booleanType()))))
anatofuz
parents:
diff changeset
41 .bind("size")),
anatofuz
parents:
diff changeset
42 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
anatofuz
parents:
diff changeset
43 hasName("empty"), returns(booleanType()))
anatofuz
parents:
diff changeset
44 .bind("empty")))
anatofuz
parents:
diff changeset
45 .bind("container")))))));
anatofuz
parents:
diff changeset
46
anatofuz
parents:
diff changeset
47 const auto WrongUse = anyOf(
anatofuz
parents:
diff changeset
48 hasParent(binaryOperator(
anatofuz
parents:
diff changeset
49 matchers::isComparisonOperator(),
anatofuz
parents:
diff changeset
50 hasEitherOperand(ignoringImpCasts(anyOf(
anatofuz
parents:
diff changeset
51 integerLiteral(equals(1)), integerLiteral(equals(0))))))
anatofuz
parents:
diff changeset
52 .bind("SizeBinaryOp")),
anatofuz
parents:
diff changeset
53 hasParent(implicitCastExpr(
anatofuz
parents:
diff changeset
54 hasImplicitDestinationType(booleanType()),
anatofuz
parents:
diff changeset
55 anyOf(
anatofuz
parents:
diff changeset
56 hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")),
anatofuz
parents:
diff changeset
57 anything()))),
anatofuz
parents:
diff changeset
58 hasParent(explicitCastExpr(hasDestinationType(booleanType()))));
anatofuz
parents:
diff changeset
59
anatofuz
parents:
diff changeset
60 Finder->addMatcher(
anatofuz
parents:
diff changeset
61 cxxMemberCallExpr(on(expr(anyOf(hasType(ValidContainer),
anatofuz
parents:
diff changeset
62 hasType(pointsTo(ValidContainer)),
anatofuz
parents:
diff changeset
63 hasType(references(ValidContainer))))),
anatofuz
parents:
diff changeset
64 callee(cxxMethodDecl(hasName("size"))), WrongUse,
anatofuz
parents:
diff changeset
65 unless(hasAncestor(cxxMethodDecl(
anatofuz
parents:
diff changeset
66 ofClass(equalsBoundNode("container"))))))
anatofuz
parents:
diff changeset
67 .bind("SizeCallExpr"),
anatofuz
parents:
diff changeset
68 this);
anatofuz
parents:
diff changeset
69
anatofuz
parents:
diff changeset
70 // Empty constructor matcher.
anatofuz
parents:
diff changeset
71 const auto DefaultConstructor = cxxConstructExpr(
anatofuz
parents:
diff changeset
72 hasDeclaration(cxxConstructorDecl(isDefaultConstructor())));
anatofuz
parents:
diff changeset
73 // Comparison to empty string or empty constructor.
anatofuz
parents:
diff changeset
74 const auto WrongComparend = anyOf(
anatofuz
parents:
diff changeset
75 ignoringImpCasts(stringLiteral(hasSize(0))),
anatofuz
parents:
diff changeset
76 ignoringImpCasts(cxxBindTemporaryExpr(has(DefaultConstructor))),
anatofuz
parents:
diff changeset
77 ignoringImplicit(DefaultConstructor),
anatofuz
parents:
diff changeset
78 cxxConstructExpr(
anatofuz
parents:
diff changeset
79 hasDeclaration(cxxConstructorDecl(isCopyConstructor())),
anatofuz
parents:
diff changeset
80 has(expr(ignoringImpCasts(DefaultConstructor)))),
anatofuz
parents:
diff changeset
81 cxxConstructExpr(
anatofuz
parents:
diff changeset
82 hasDeclaration(cxxConstructorDecl(isMoveConstructor())),
anatofuz
parents:
diff changeset
83 has(expr(ignoringImpCasts(DefaultConstructor)))));
anatofuz
parents:
diff changeset
84 // Match the object being compared.
anatofuz
parents:
diff changeset
85 const auto STLArg =
anatofuz
parents:
diff changeset
86 anyOf(unaryOperator(
anatofuz
parents:
diff changeset
87 hasOperatorName("*"),
anatofuz
parents:
diff changeset
88 hasUnaryOperand(
anatofuz
parents:
diff changeset
89 expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))),
anatofuz
parents:
diff changeset
90 expr(hasType(ValidContainer)).bind("STLObject"));
anatofuz
parents:
diff changeset
91 Finder->addMatcher(
anatofuz
parents:
diff changeset
92 cxxOperatorCallExpr(
anatofuz
parents:
diff changeset
93 anyOf(hasOverloadedOperatorName("=="),
anatofuz
parents:
diff changeset
94 hasOverloadedOperatorName("!=")),
anatofuz
parents:
diff changeset
95 anyOf(allOf(hasArgument(0, WrongComparend), hasArgument(1, STLArg)),
anatofuz
parents:
diff changeset
96 allOf(hasArgument(0, STLArg), hasArgument(1, WrongComparend))),
anatofuz
parents:
diff changeset
97 unless(hasAncestor(
anatofuz
parents:
diff changeset
98 cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
anatofuz
parents:
diff changeset
99 .bind("BinCmp"),
anatofuz
parents:
diff changeset
100 this);
anatofuz
parents:
diff changeset
101 }
anatofuz
parents:
diff changeset
102
anatofuz
parents:
diff changeset
103 void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
anatofuz
parents:
diff changeset
104 const auto *MemberCall =
anatofuz
parents:
diff changeset
105 Result.Nodes.getNodeAs<CXXMemberCallExpr>("SizeCallExpr");
anatofuz
parents:
diff changeset
106 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");
anatofuz
parents:
diff changeset
107 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
anatofuz
parents:
diff changeset
108 const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");
anatofuz
parents:
diff changeset
109 const auto *E =
anatofuz
parents:
diff changeset
110 MemberCall
anatofuz
parents:
diff changeset
111 ? MemberCall->getImplicitObjectArgument()
anatofuz
parents:
diff changeset
112 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));
anatofuz
parents:
diff changeset
113 FixItHint Hint;
anatofuz
parents:
diff changeset
114 std::string ReplacementText = std::string(
anatofuz
parents:
diff changeset
115 Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()),
anatofuz
parents:
diff changeset
116 *Result.SourceManager, getLangOpts()));
anatofuz
parents:
diff changeset
117 if (BinCmp && IsBinaryOrTernary(E)) {
anatofuz
parents:
diff changeset
118 // Not just a DeclRefExpr, so parenthesize to be on the safe side.
anatofuz
parents:
diff changeset
119 ReplacementText = "(" + ReplacementText + ")";
anatofuz
parents:
diff changeset
120 }
anatofuz
parents:
diff changeset
121 if (E->getType()->isPointerType())
anatofuz
parents:
diff changeset
122 ReplacementText += "->empty()";
anatofuz
parents:
diff changeset
123 else
anatofuz
parents:
diff changeset
124 ReplacementText += ".empty()";
anatofuz
parents:
diff changeset
125
anatofuz
parents:
diff changeset
126 if (BinCmp) {
anatofuz
parents:
diff changeset
127 if (BinCmp->getOperator() == OO_ExclaimEqual) {
anatofuz
parents:
diff changeset
128 ReplacementText = "!" + ReplacementText;
anatofuz
parents:
diff changeset
129 }
anatofuz
parents:
diff changeset
130 Hint =
anatofuz
parents:
diff changeset
131 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
anatofuz
parents:
diff changeset
132 } else if (BinaryOp) { // Determine the correct transformation.
anatofuz
parents:
diff changeset
133 bool Negation = false;
anatofuz
parents:
diff changeset
134 const bool ContainerIsLHS =
anatofuz
parents:
diff changeset
135 !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
anatofuz
parents:
diff changeset
136 const auto OpCode = BinaryOp->getOpcode();
anatofuz
parents:
diff changeset
137 uint64_t Value = 0;
anatofuz
parents:
diff changeset
138 if (ContainerIsLHS) {
anatofuz
parents:
diff changeset
139 if (const auto *Literal = llvm::dyn_cast<IntegerLiteral>(
anatofuz
parents:
diff changeset
140 BinaryOp->getRHS()->IgnoreImpCasts()))
anatofuz
parents:
diff changeset
141 Value = Literal->getValue().getLimitedValue();
anatofuz
parents:
diff changeset
142 else
anatofuz
parents:
diff changeset
143 return;
anatofuz
parents:
diff changeset
144 } else {
anatofuz
parents:
diff changeset
145 Value =
anatofuz
parents:
diff changeset
146 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts())
anatofuz
parents:
diff changeset
147 ->getValue()
anatofuz
parents:
diff changeset
148 .getLimitedValue();
anatofuz
parents:
diff changeset
149 }
anatofuz
parents:
diff changeset
150
anatofuz
parents:
diff changeset
151 // Constant that is not handled.
anatofuz
parents:
diff changeset
152 if (Value > 1)
anatofuz
parents:
diff changeset
153 return;
anatofuz
parents:
diff changeset
154
anatofuz
parents:
diff changeset
155 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
anatofuz
parents:
diff changeset
156 OpCode == BinaryOperatorKind::BO_NE))
anatofuz
parents:
diff changeset
157 return;
anatofuz
parents:
diff changeset
158
anatofuz
parents:
diff changeset
159 // Always true, no warnings for that.
anatofuz
parents:
diff changeset
160 if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
anatofuz
parents:
diff changeset
161 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
anatofuz
parents:
diff changeset
162 return;
anatofuz
parents:
diff changeset
163
anatofuz
parents:
diff changeset
164 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
anatofuz
parents:
diff changeset
165 if (Value == 1) {
anatofuz
parents:
diff changeset
166 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
anatofuz
parents:
diff changeset
167 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
anatofuz
parents:
diff changeset
168 return;
anatofuz
parents:
diff changeset
169 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
anatofuz
parents:
diff changeset
170 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
anatofuz
parents:
diff changeset
171 return;
anatofuz
parents:
diff changeset
172 }
anatofuz
parents:
diff changeset
173
anatofuz
parents:
diff changeset
174 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
anatofuz
parents:
diff changeset
175 Negation = true;
anatofuz
parents:
diff changeset
176 if ((OpCode == BinaryOperatorKind::BO_GT ||
anatofuz
parents:
diff changeset
177 OpCode == BinaryOperatorKind::BO_GE) &&
anatofuz
parents:
diff changeset
178 ContainerIsLHS)
anatofuz
parents:
diff changeset
179 Negation = true;
anatofuz
parents:
diff changeset
180 if ((OpCode == BinaryOperatorKind::BO_LT ||
anatofuz
parents:
diff changeset
181 OpCode == BinaryOperatorKind::BO_LE) &&
anatofuz
parents:
diff changeset
182 !ContainerIsLHS)
anatofuz
parents:
diff changeset
183 Negation = true;
anatofuz
parents:
diff changeset
184
anatofuz
parents:
diff changeset
185 if (Negation)
anatofuz
parents:
diff changeset
186 ReplacementText = "!" + ReplacementText;
anatofuz
parents:
diff changeset
187 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
anatofuz
parents:
diff changeset
188 ReplacementText);
anatofuz
parents:
diff changeset
189
anatofuz
parents:
diff changeset
190 } else {
anatofuz
parents:
diff changeset
191 // If there is a conversion above the size call to bool, it is safe to just
anatofuz
parents:
diff changeset
192 // replace size with empty.
anatofuz
parents:
diff changeset
193 if (const auto *UnaryOp =
anatofuz
parents:
diff changeset
194 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
anatofuz
parents:
diff changeset
195 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
anatofuz
parents:
diff changeset
196 ReplacementText);
anatofuz
parents:
diff changeset
197 else
anatofuz
parents:
diff changeset
198 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
anatofuz
parents:
diff changeset
199 "!" + ReplacementText);
anatofuz
parents:
diff changeset
200 }
anatofuz
parents:
diff changeset
201
anatofuz
parents:
diff changeset
202 if (MemberCall) {
anatofuz
parents:
diff changeset
203 diag(MemberCall->getBeginLoc(),
anatofuz
parents:
diff changeset
204 "the 'empty' method should be used to check "
anatofuz
parents:
diff changeset
205 "for emptiness instead of 'size'")
anatofuz
parents:
diff changeset
206 << Hint;
anatofuz
parents:
diff changeset
207 } else {
anatofuz
parents:
diff changeset
208 diag(BinCmp->getBeginLoc(),
anatofuz
parents:
diff changeset
209 "the 'empty' method should be used to check "
anatofuz
parents:
diff changeset
210 "for emptiness instead of comparing to an empty object")
anatofuz
parents:
diff changeset
211 << Hint;
anatofuz
parents:
diff changeset
212 }
anatofuz
parents:
diff changeset
213
anatofuz
parents:
diff changeset
214 const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container");
anatofuz
parents:
diff changeset
215 if (const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) {
anatofuz
parents:
diff changeset
216 // The definition of the empty() method is the same for all implicit
anatofuz
parents:
diff changeset
217 // instantiations. In order to avoid duplicate or inconsistent warnings
anatofuz
parents:
diff changeset
218 // (depending on how deduplication is done), we use the same class name
anatofuz
parents:
diff changeset
219 // for all implicit instantiations of a template.
anatofuz
parents:
diff changeset
220 if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation)
anatofuz
parents:
diff changeset
221 Container = CTS->getSpecializedTemplate();
anatofuz
parents:
diff changeset
222 }
anatofuz
parents:
diff changeset
223 const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty");
anatofuz
parents:
diff changeset
224
anatofuz
parents:
diff changeset
225 diag(Empty->getLocation(), "method %0::empty() defined here",
anatofuz
parents:
diff changeset
226 DiagnosticIDs::Note)
anatofuz
parents:
diff changeset
227 << Container;
anatofuz
parents:
diff changeset
228 }
anatofuz
parents:
diff changeset
229
anatofuz
parents:
diff changeset
230 } // namespace readability
anatofuz
parents:
diff changeset
231 } // namespace tidy
anatofuz
parents:
diff changeset
232 } // namespace clang