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