clang-tools 6.0.0

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