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