blob: 1086340a30a644403c1aafbf5cb53f12dcb04b54 [file] [log] [blame]
Alexander Kornienko1b677db2015-03-09 12:18:39 +00001//===--- ContainerSizeEmptyCheck.cpp - clang-tidy -------------------------===//
Alexander Kornienko4babd682015-01-15 15:46:58 +00002//
Chandler Carruth2946cd72019-01-19 08:50:56 +00003// 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
Alexander Kornienko4babd682015-01-15 15:46:58 +00006//
7//===----------------------------------------------------------------------===//
Alexander Kornienko1b677db2015-03-09 12:18:39 +00008#include "ContainerSizeEmptyCheck.h"
Aaron Ballman72163a92017-04-24 14:57:09 +00009#include "../utils/ASTUtils.h"
Kirill Bobyrevacb6b352016-09-13 08:58:11 +000010#include "../utils/Matchers.h"
Alexander Kornienko4babd682015-01-15 15:46:58 +000011#include "clang/AST/ASTContext.h"
12#include "clang/ASTMatchers/ASTMatchers.h"
Alexander Kornienko4babd682015-01-15 15:46:58 +000013#include "clang/Lex/Lexer.h"
Chandler Carruthf7662782015-02-13 09:07:58 +000014#include "llvm/ADT/StringRef.h"
Alexander Kornienko4babd682015-01-15 15:46:58 +000015
16using namespace clang::ast_matchers;
17
Alexander Kornienko4babd682015-01-15 15:46:58 +000018namespace clang {
Alexander Kornienko4babd682015-01-15 15:46:58 +000019namespace tidy {
20namespace readability {
21
Aaron Ballman72163a92017-04-24 14:57:09 +000022using utils::IsBinaryOrTernary;
23
Alexander Kornienko4babd682015-01-15 15:46:58 +000024ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,
25 ClangTidyContext *Context)
26 : ClangTidyCheck(Name, Context) {}
27
28void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) {
Aaron Ballman1f1b0672015-09-02 16:05:21 +000029 // 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
Manuel Klimek7b9c1172017-08-02 13:13:11 +000034 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")))))));
Etienne Bergeron9d265992016-04-21 16:57:56 +000046
Alexander Kornienko4babd682015-01-15 15:46:58 +000047 const auto WrongUse = anyOf(
Gabor Horvath533c01d2016-04-19 13:29:05 +000048 hasParent(binaryOperator(
Etienne Bergeron9d265992016-04-21 16:57:56 +000049 matchers::isComparisonOperator(),
Gabor Horvath533c01d2016-04-19 13:29:05 +000050 hasEitherOperand(ignoringImpCasts(anyOf(
51 integerLiteral(equals(1)), integerLiteral(equals(0))))))
52 .bind("SizeBinaryOp")),
Alexander Kornienko4babd682015-01-15 15:46:58 +000053 hasParent(implicitCastExpr(
Gabor Horvatha4fd3be2016-02-09 09:26:11 +000054 hasImplicitDestinationType(booleanType()),
Alexander Kornienko4babd682015-01-15 15:46:58 +000055 anyOf(
56 hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")),
57 anything()))),
Gabor Horvatha4fd3be2016-02-09 09:26:11 +000058 hasParent(explicitCastExpr(hasDestinationType(booleanType()))));
Alexander Kornienko4babd682015-01-15 15:46:58 +000059
60 Finder->addMatcher(
Kirill Bobyrev0d0bbfd2016-09-13 10:19:13 +000061 cxxMemberCallExpr(on(expr(anyOf(hasType(ValidContainer),
62 hasType(pointsTo(ValidContainer)),
Alexander Kornienkof6cd3672017-03-20 22:15:27 +000063 hasType(references(ValidContainer))))),
Alexander Kornienkob5ca17f2016-12-21 23:44:23 +000064 callee(cxxMethodDecl(hasName("size"))), WrongUse,
65 unless(hasAncestor(cxxMethodDecl(
66 ofClass(equalsBoundNode("container"))))))
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +000067 .bind("SizeCallExpr"),
Alexander Kornienko4babd682015-01-15 15:46:58 +000068 this);
Aaron Ballman72163a92017-04-24 14:57:09 +000069
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);
Alexander Kornienko4babd682015-01-15 15:46:58 +0000101}
102
103void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
104 const auto *MemberCall =
105 Result.Nodes.getNodeAs<CXXMemberCallExpr>("SizeCallExpr");
Aaron Ballman72163a92017-04-24 14:57:09 +0000106 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");
Alexander Kornienko4babd682015-01-15 15:46:58 +0000107 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
Aaron Ballman72163a92017-04-24 14:57:09 +0000108 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"));
Alexander Kornienko4babd682015-01-15 15:46:58 +0000113 FixItHint Hint;
Benjamin Krameradcd0262020-01-28 20:23:46 +0100114 std::string ReplacementText = std::string(
Gabor Horvathafad84c2016-09-24 02:13:45 +0000115 Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()),
Benjamin Krameradcd0262020-01-28 20:23:46 +0100116 *Result.SourceManager, getLangOpts()));
Aaron Ballman72163a92017-04-24 14:57:09 +0000117 if (BinCmp && IsBinaryOrTernary(E)) {
118 // Not just a DeclRefExpr, so parenthesize to be on the safe side.
119 ReplacementText = "(" + ReplacementText + ")";
120 }
Alexander Kornienko4babd682015-01-15 15:46:58 +0000121 if (E->getType()->isPointerType())
122 ReplacementText += "->empty()";
123 else
124 ReplacementText += ".empty()";
125
Aaron Ballman72163a92017-04-24 14:57:09 +0000126 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.
Alexander Kornienko4babd682015-01-15 15:46:58 +0000133 bool Negation = false;
Gabor Horvatha4e35ec2015-12-12 11:31:25 +0000134 const bool ContainerIsLHS =
135 !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
Alexander Kornienko4babd682015-01-15 15:46:58 +0000136 const auto OpCode = BinaryOp->getOpcode();
137 uint64_t Value = 0;
138 if (ContainerIsLHS) {
Gabor Horvatha4e35ec2015-12-12 11:31:25 +0000139 if (const auto *Literal = llvm::dyn_cast<IntegerLiteral>(
140 BinaryOp->getRHS()->IgnoreImpCasts()))
Alexander Kornienko4babd682015-01-15 15:46:58 +0000141 Value = Literal->getValue().getLimitedValue();
142 else
143 return;
144 } else {
Gabor Horvatha4e35ec2015-12-12 11:31:25 +0000145 Value =
146 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts())
147 ->getValue()
148 .getLimitedValue();
Alexander Kornienko4babd682015-01-15 15:46:58 +0000149 }
150
151 // Constant that is not handled.
152 if (Value > 1)
153 return;
154
Gabor Horvath533c01d2016-04-19 13:29:05 +0000155 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
156 OpCode == BinaryOperatorKind::BO_NE))
157 return;
158
Alexander Kornienko4babd682015-01-15 15:46:58 +0000159 // 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
Gabor Horvath1f30cf62015-12-28 17:20:33 +0000164 // 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 }
Gabor Horvathc6ff9c32015-12-21 09:43:52 +0000173
Alexander Kornienko4babd682015-01-15 15:46:58 +0000174 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 }
Kirill Bobyrevacb6b352016-09-13 08:58:11 +0000201
Aaron Ballman72163a92017-04-24 14:57:09 +0000202 if (MemberCall) {
Stephen Kelly43465bf2018-08-09 22:42:26 +0000203 diag(MemberCall->getBeginLoc(),
Aaron Ballman72163a92017-04-24 14:57:09 +0000204 "the 'empty' method should be used to check "
205 "for emptiness instead of 'size'")
206 << Hint;
207 } else {
Stephen Kelly43465bf2018-08-09 22:42:26 +0000208 diag(BinCmp->getBeginLoc(),
Aaron Ballman72163a92017-04-24 14:57:09 +0000209 "the 'empty' method should be used to check "
210 "for emptiness instead of comparing to an empty object")
211 << Hint;
212 }
Kirill Bobyrevacb6b352016-09-13 08:58:11 +0000213
214 const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container");
Alexander Kornienko8577dcc2018-11-27 10:53:44 +0000215 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 }
Kirill Bobyrevacb6b352016-09-13 08:58:11 +0000223 const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty");
224
225 diag(Empty->getLocation(), "method %0::empty() defined here",
226 DiagnosticIDs::Note)
227 << Container;
Alexander Kornienko4babd682015-01-15 15:46:58 +0000228}
229
230} // namespace readability
231} // namespace tidy
232} // namespace clang