blob: 24680eb629cd5edf82c503c0f58b49d359e067c6 [file] [log] [blame]
Alexander Kornienko1b677db2015-03-09 12:18:39 +00001//===--- ContainerSizeEmptyCheck.cpp - clang-tidy -------------------------===//
Alexander Kornienko4babd682015-01-15 15:46:58 +00002//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
Alexander Kornienko1b677db2015-03-09 12:18:39 +00009#include "ContainerSizeEmptyCheck.h"
Aaron Ballman72163a92017-04-24 14:57:09 +000010#include "../utils/ASTUtils.h"
Kirill Bobyrevacb6b352016-09-13 08:58:11 +000011#include "../utils/Matchers.h"
Alexander Kornienko4babd682015-01-15 15:46:58 +000012#include "clang/AST/ASTContext.h"
13#include "clang/ASTMatchers/ASTMatchers.h"
Alexander Kornienko4babd682015-01-15 15:46:58 +000014#include "clang/Lex/Lexer.h"
Chandler Carruthf7662782015-02-13 09:07:58 +000015#include "llvm/ADT/StringRef.h"
Alexander Kornienko4babd682015-01-15 15:46:58 +000016
17using namespace clang::ast_matchers;
18
Alexander Kornienko4babd682015-01-15 15:46:58 +000019namespace clang {
Alexander Kornienko4babd682015-01-15 15:46:58 +000020namespace tidy {
21namespace readability {
22
Aaron Ballman72163a92017-04-24 14:57:09 +000023using utils::IsBinaryOrTernary;
24
Alexander Kornienko4babd682015-01-15 15:46:58 +000025ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,
26 ClangTidyContext *Context)
27 : ClangTidyCheck(Name, Context) {}
28
29void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) {
Aaron Ballman1f1b0672015-09-02 16:05:21 +000030 // Only register the matchers for C++; the functionality currently does not
31 // provide any benefit to other languages, despite being benign.
32 if (!getLangOpts().CPlusPlus)
33 return;
34
Kirill Bobyrev0d0bbfd2016-09-13 10:19:13 +000035 const auto ValidContainer = cxxRecordDecl(isSameOrDerivedFrom(
Kirill Bobyrevacb6b352016-09-13 08:58:11 +000036 namedDecl(
37 has(cxxMethodDecl(
38 isConst(), parameterCountIs(0), isPublic(), hasName("size"),
39 returns(qualType(isInteger(), unless(booleanType()))))
40 .bind("size")),
41 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
42 hasName("empty"), returns(booleanType()))
43 .bind("empty")))
44 .bind("container")));
Etienne Bergeron9d265992016-04-21 16:57:56 +000045
Alexander Kornienko4babd682015-01-15 15:46:58 +000046 const auto WrongUse = anyOf(
Gabor Horvath533c01d2016-04-19 13:29:05 +000047 hasParent(binaryOperator(
Etienne Bergeron9d265992016-04-21 16:57:56 +000048 matchers::isComparisonOperator(),
Gabor Horvath533c01d2016-04-19 13:29:05 +000049 hasEitherOperand(ignoringImpCasts(anyOf(
50 integerLiteral(equals(1)), integerLiteral(equals(0))))))
51 .bind("SizeBinaryOp")),
Alexander Kornienko4babd682015-01-15 15:46:58 +000052 hasParent(implicitCastExpr(
Gabor Horvatha4fd3be2016-02-09 09:26:11 +000053 hasImplicitDestinationType(booleanType()),
Alexander Kornienko4babd682015-01-15 15:46:58 +000054 anyOf(
55 hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")),
56 anything()))),
Gabor Horvatha4fd3be2016-02-09 09:26:11 +000057 hasParent(explicitCastExpr(hasDestinationType(booleanType()))));
Alexander Kornienko4babd682015-01-15 15:46:58 +000058
59 Finder->addMatcher(
Kirill Bobyrev0d0bbfd2016-09-13 10:19:13 +000060 cxxMemberCallExpr(on(expr(anyOf(hasType(ValidContainer),
61 hasType(pointsTo(ValidContainer)),
Alexander Kornienkof6cd3672017-03-20 22:15:27 +000062 hasType(references(ValidContainer))))),
Alexander Kornienkob5ca17f2016-12-21 23:44:23 +000063 callee(cxxMethodDecl(hasName("size"))), WrongUse,
64 unless(hasAncestor(cxxMethodDecl(
65 ofClass(equalsBoundNode("container"))))))
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +000066 .bind("SizeCallExpr"),
Alexander Kornienko4babd682015-01-15 15:46:58 +000067 this);
Aaron Ballman72163a92017-04-24 14:57:09 +000068
69 // Empty constructor matcher.
70 const auto DefaultConstructor = cxxConstructExpr(
71 hasDeclaration(cxxConstructorDecl(isDefaultConstructor())));
72 // Comparison to empty string or empty constructor.
73 const auto WrongComparend = anyOf(
74 ignoringImpCasts(stringLiteral(hasSize(0))),
75 ignoringImpCasts(cxxBindTemporaryExpr(has(DefaultConstructor))),
76 ignoringImplicit(DefaultConstructor),
77 cxxConstructExpr(
78 hasDeclaration(cxxConstructorDecl(isCopyConstructor())),
79 has(expr(ignoringImpCasts(DefaultConstructor)))),
80 cxxConstructExpr(
81 hasDeclaration(cxxConstructorDecl(isMoveConstructor())),
82 has(expr(ignoringImpCasts(DefaultConstructor)))));
83 // Match the object being compared.
84 const auto STLArg =
85 anyOf(unaryOperator(
86 hasOperatorName("*"),
87 hasUnaryOperand(
88 expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))),
89 expr(hasType(ValidContainer)).bind("STLObject"));
90 Finder->addMatcher(
91 cxxOperatorCallExpr(
92 anyOf(hasOverloadedOperatorName("=="),
93 hasOverloadedOperatorName("!=")),
94 anyOf(allOf(hasArgument(0, WrongComparend), hasArgument(1, STLArg)),
95 allOf(hasArgument(0, STLArg), hasArgument(1, WrongComparend))),
96 unless(hasAncestor(
97 cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
98 .bind("BinCmp"),
99 this);
Alexander Kornienko4babd682015-01-15 15:46:58 +0000100}
101
102void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
103 const auto *MemberCall =
104 Result.Nodes.getNodeAs<CXXMemberCallExpr>("SizeCallExpr");
Aaron Ballman72163a92017-04-24 14:57:09 +0000105 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");
Alexander Kornienko4babd682015-01-15 15:46:58 +0000106 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
Aaron Ballman72163a92017-04-24 14:57:09 +0000107 const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");
108 const auto *E =
109 MemberCall
110 ? MemberCall->getImplicitObjectArgument()
111 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));
Alexander Kornienko4babd682015-01-15 15:46:58 +0000112 FixItHint Hint;
Gabor Horvathafad84c2016-09-24 02:13:45 +0000113 std::string ReplacementText =
114 Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()),
115 *Result.SourceManager, getLangOpts());
Aaron Ballman72163a92017-04-24 14:57:09 +0000116 if (BinCmp && IsBinaryOrTernary(E)) {
117 // Not just a DeclRefExpr, so parenthesize to be on the safe side.
118 ReplacementText = "(" + ReplacementText + ")";
119 }
Alexander Kornienko4babd682015-01-15 15:46:58 +0000120 if (E->getType()->isPointerType())
121 ReplacementText += "->empty()";
122 else
123 ReplacementText += ".empty()";
124
Aaron Ballman72163a92017-04-24 14:57:09 +0000125 if (BinCmp) {
126 if (BinCmp->getOperator() == OO_ExclaimEqual) {
127 ReplacementText = "!" + ReplacementText;
128 }
129 Hint =
130 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
131 } else if (BinaryOp) { // Determine the correct transformation.
Alexander Kornienko4babd682015-01-15 15:46:58 +0000132 bool Negation = false;
Gabor Horvatha4e35ec2015-12-12 11:31:25 +0000133 const bool ContainerIsLHS =
134 !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
Alexander Kornienko4babd682015-01-15 15:46:58 +0000135 const auto OpCode = BinaryOp->getOpcode();
136 uint64_t Value = 0;
137 if (ContainerIsLHS) {
Gabor Horvatha4e35ec2015-12-12 11:31:25 +0000138 if (const auto *Literal = llvm::dyn_cast<IntegerLiteral>(
139 BinaryOp->getRHS()->IgnoreImpCasts()))
Alexander Kornienko4babd682015-01-15 15:46:58 +0000140 Value = Literal->getValue().getLimitedValue();
141 else
142 return;
143 } else {
Gabor Horvatha4e35ec2015-12-12 11:31:25 +0000144 Value =
145 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts())
146 ->getValue()
147 .getLimitedValue();
Alexander Kornienko4babd682015-01-15 15:46:58 +0000148 }
149
150 // Constant that is not handled.
151 if (Value > 1)
152 return;
153
Gabor Horvath533c01d2016-04-19 13:29:05 +0000154 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
155 OpCode == BinaryOperatorKind::BO_NE))
156 return;
157
Alexander Kornienko4babd682015-01-15 15:46:58 +0000158 // Always true, no warnings for that.
159 if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
160 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
161 return;
162
Gabor Horvath1f30cf62015-12-28 17:20:33 +0000163 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
164 if (Value == 1) {
165 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
166 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
167 return;
168 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
169 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
170 return;
171 }
Gabor Horvathc6ff9c32015-12-21 09:43:52 +0000172
Alexander Kornienko4babd682015-01-15 15:46:58 +0000173 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
174 Negation = true;
175 if ((OpCode == BinaryOperatorKind::BO_GT ||
176 OpCode == BinaryOperatorKind::BO_GE) &&
177 ContainerIsLHS)
178 Negation = true;
179 if ((OpCode == BinaryOperatorKind::BO_LT ||
180 OpCode == BinaryOperatorKind::BO_LE) &&
181 !ContainerIsLHS)
182 Negation = true;
183
184 if (Negation)
185 ReplacementText = "!" + ReplacementText;
186 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
187 ReplacementText);
188
189 } else {
190 // If there is a conversion above the size call to bool, it is safe to just
191 // replace size with empty.
192 if (const auto *UnaryOp =
193 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
194 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
195 ReplacementText);
196 else
197 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
198 "!" + ReplacementText);
199 }
Kirill Bobyrevacb6b352016-09-13 08:58:11 +0000200
Aaron Ballman72163a92017-04-24 14:57:09 +0000201 if (MemberCall) {
202 diag(MemberCall->getLocStart(),
203 "the 'empty' method should be used to check "
204 "for emptiness instead of 'size'")
205 << Hint;
206 } else {
207 diag(BinCmp->getLocStart(),
208 "the 'empty' method should be used to check "
209 "for emptiness instead of comparing to an empty object")
210 << Hint;
211 }
Kirill Bobyrevacb6b352016-09-13 08:58:11 +0000212
213 const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container");
214 const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty");
215
216 diag(Empty->getLocation(), "method %0::empty() defined here",
217 DiagnosticIDs::Note)
218 << Container;
Alexander Kornienko4babd682015-01-15 15:46:58 +0000219}
220
221} // namespace readability
222} // namespace tidy
223} // namespace clang