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 ast_matchers {
AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam,AST_POLYMORPHIC_SUPPORTED_TYPES (CallExpr,CXXConstructExpr),internal::Matcher<Expr>,ArgMatcher,internal::Matcher<ParmVarDecl>,ParamMatcher)20 AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam,
21 AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr,
22 CXXConstructExpr),
23 internal::Matcher<Expr>, ArgMatcher,
24 internal::Matcher<ParmVarDecl>, ParamMatcher) {
25 BoundNodesTreeBuilder Result;
26 // The first argument of an overloaded member operator is the implicit object
27 // argument of the method which should not be matched against a parameter, so
28 // we skip over it here.
29 BoundNodesTreeBuilder Matches;
30 unsigned ArgIndex = cxxOperatorCallExpr(callee(cxxMethodDecl()))
31 .matches(Node, Finder, &Matches)
32 ? 1
33 : 0;
34 int ParamIndex = 0;
35 for (; ArgIndex < Node.getNumArgs(); ++ArgIndex) {
36 BoundNodesTreeBuilder ArgMatches(*Builder);
37 if (ArgMatcher.matches(*(Node.getArg(ArgIndex)->IgnoreParenCasts()), Finder,
38 &ArgMatches)) {
39 BoundNodesTreeBuilder ParamMatches(ArgMatches);
40 if (expr(anyOf(cxxConstructExpr(hasDeclaration(cxxConstructorDecl(
41 hasParameter(ParamIndex, ParamMatcher)))),
42 callExpr(callee(functionDecl(
43 hasParameter(ParamIndex, ParamMatcher))))))
44 .matches(Node, Finder, &ParamMatches)) {
45 Result.addMatch(ParamMatches);
46 *Builder = std::move(Result);
47 return true;
48 }
49 }
50 ++ParamIndex;
51 }
52 return false;
53 }
54
AST_MATCHER(Expr,usedInBooleanContext)55 AST_MATCHER(Expr, usedInBooleanContext) {
56 const char *ExprName = "__booleanContextExpr";
57 auto Result =
58 expr(expr().bind(ExprName),
59 anyOf(hasParent(varDecl(hasType(booleanType()))),
60 hasParent(cxxConstructorDecl(
61 hasAnyConstructorInitializer(cxxCtorInitializer(
62 withInitializer(expr(equalsBoundNode(ExprName))),
63 forField(hasType(booleanType())))))),
64 hasParent(fieldDecl(hasType(booleanType()))),
65 hasParent(stmt(anyOf(
66 explicitCastExpr(hasDestinationType(booleanType())),
67 ifStmt(hasCondition(expr(equalsBoundNode(ExprName)))),
68 doStmt(hasCondition(expr(equalsBoundNode(ExprName)))),
69 whileStmt(hasCondition(expr(equalsBoundNode(ExprName)))),
70 forStmt(hasCondition(expr(equalsBoundNode(ExprName)))),
71 conditionalOperator(
72 hasCondition(expr(equalsBoundNode(ExprName)))),
73 parenListExpr(hasParent(varDecl(hasType(booleanType())))),
74 parenExpr(hasParent(
75 explicitCastExpr(hasDestinationType(booleanType())))),
76 returnStmt(forFunction(returns(booleanType()))),
77 cxxUnresolvedConstructExpr(hasType(booleanType())),
78 callExpr(hasAnyArgumentWithParam(
79 expr(equalsBoundNode(ExprName)),
80 parmVarDecl(hasType(booleanType())))),
81 binaryOperator(hasAnyOperatorName("&&", "||")),
82 unaryOperator(hasOperatorName("!")).bind("NegOnSize"))))))
83 .matches(Node, Finder, Builder);
84 Builder->removeBindings([ExprName](const BoundNodesMap &Nodes) {
85 return Nodes.getNode(ExprName).getNodeKind().isNone();
86 });
87 return Result;
88 }
89 } // namespace ast_matchers
90 namespace tidy {
91 namespace readability {
92
93 using utils::IsBinaryOrTernary;
94
ContainerSizeEmptyCheck(StringRef Name,ClangTidyContext * Context)95 ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,
96 ClangTidyContext *Context)
97 : ClangTidyCheck(Name, Context) {}
98
registerMatchers(MatchFinder * Finder)99 void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) {
100 const auto ValidContainerRecord = cxxRecordDecl(isSameOrDerivedFrom(
101 namedDecl(
102 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
103 hasName("size"),
104 returns(qualType(isInteger(), unless(booleanType()),
105 unless(elaboratedType()))))
106 .bind("size")),
107 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
108 hasName("empty"), returns(booleanType()))
109 .bind("empty")))
110 .bind("container")));
111
112 const auto ValidContainerNonTemplateType =
113 qualType(hasUnqualifiedDesugaredType(
114 recordType(hasDeclaration(ValidContainerRecord))));
115 const auto ValidContainerTemplateType =
116 qualType(hasUnqualifiedDesugaredType(templateSpecializationType(
117 hasDeclaration(classTemplateDecl(has(ValidContainerRecord))))));
118
119 const auto ValidContainer = qualType(
120 anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType));
121
122 const auto WrongUse = traverse(
123 TK_AsIs,
124 anyOf(
125 hasParent(binaryOperator(isComparisonOperator(),
126 hasEitherOperand(ignoringImpCasts(
127 anyOf(integerLiteral(equals(1)),
128 integerLiteral(equals(0))))))
129 .bind("SizeBinaryOp")),
130 hasParent(implicitCastExpr(
131 hasImplicitDestinationType(booleanType()),
132 anyOf(hasParent(
133 unaryOperator(hasOperatorName("!")).bind("NegOnSize")),
134 anything()))),
135 usedInBooleanContext()));
136
137 Finder->addMatcher(
138 cxxMemberCallExpr(unless(isInTemplateInstantiation()),
139 on(expr(anyOf(hasType(ValidContainer),
140 hasType(pointsTo(ValidContainer)),
141 hasType(references(ValidContainer))))
142 .bind("MemberCallObject")),
143 callee(cxxMethodDecl(hasName("size"))), WrongUse,
144 unless(hasAncestor(cxxMethodDecl(
145 ofClass(equalsBoundNode("container"))))))
146 .bind("SizeCallExpr"),
147 this);
148
149 Finder->addMatcher(
150 callExpr(has(cxxDependentScopeMemberExpr(
151 hasObjectExpression(
152 expr(anyOf(hasType(ValidContainer),
153 hasType(pointsTo(ValidContainer)),
154 hasType(references(ValidContainer))))
155 .bind("MemberCallObject")),
156 hasMemberName("size"))),
157 WrongUse,
158 unless(hasAncestor(
159 cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
160 .bind("SizeCallExpr"),
161 this);
162
163 // Empty constructor matcher.
164 const auto DefaultConstructor = cxxConstructExpr(
165 hasDeclaration(cxxConstructorDecl(isDefaultConstructor())));
166 // Comparison to empty string or empty constructor.
167 const auto WrongComparend = anyOf(
168 ignoringImpCasts(stringLiteral(hasSize(0))),
169 ignoringImpCasts(cxxBindTemporaryExpr(has(DefaultConstructor))),
170 ignoringImplicit(DefaultConstructor),
171 cxxConstructExpr(hasDeclaration(cxxConstructorDecl(isCopyConstructor())),
172 has(expr(ignoringImpCasts(DefaultConstructor)))),
173 cxxConstructExpr(hasDeclaration(cxxConstructorDecl(isMoveConstructor())),
174 has(expr(ignoringImpCasts(DefaultConstructor)))),
175 cxxUnresolvedConstructExpr(argumentCountIs(0)));
176 // Match the object being compared.
177 const auto STLArg =
178 anyOf(unaryOperator(
179 hasOperatorName("*"),
180 hasUnaryOperand(
181 expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))),
182 expr(hasType(ValidContainer)).bind("STLObject"));
183 Finder->addMatcher(
184 cxxOperatorCallExpr(
185 unless(isInTemplateInstantiation()),
186 hasAnyOverloadedOperatorName("==", "!="),
187 anyOf(allOf(hasArgument(0, WrongComparend), hasArgument(1, STLArg)),
188 allOf(hasArgument(0, STLArg), hasArgument(1, WrongComparend))),
189 unless(hasAncestor(
190 cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
191 .bind("BinCmp"),
192 this);
193 Finder->addMatcher(
194 binaryOperator(hasAnyOperatorName("==", "!="),
195 anyOf(allOf(hasLHS(WrongComparend), hasRHS(STLArg)),
196 allOf(hasLHS(STLArg), hasRHS(WrongComparend))),
197 unless(hasAncestor(
198 cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
199 .bind("BinCmp"),
200 this);
201 }
202
check(const MatchFinder::MatchResult & Result)203 void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
204 const auto *MemberCall = Result.Nodes.getNodeAs<Expr>("SizeCallExpr");
205 const auto *MemberCallObject =
206 Result.Nodes.getNodeAs<Expr>("MemberCallObject");
207 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");
208 const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>("BinCmp");
209 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
210 const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");
211 const auto *E =
212 MemberCallObject
213 ? MemberCallObject
214 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));
215 FixItHint Hint;
216 std::string ReplacementText = std::string(
217 Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()),
218 *Result.SourceManager, getLangOpts()));
219 if (IsBinaryOrTernary(E) || isa<UnaryOperator>(E)) {
220 ReplacementText = "(" + ReplacementText + ")";
221 }
222 if (E->getType()->isPointerType())
223 ReplacementText += "->empty()";
224 else
225 ReplacementText += ".empty()";
226
227 if (BinCmp) {
228 if (BinCmp->getOperator() == OO_ExclaimEqual) {
229 ReplacementText = "!" + ReplacementText;
230 }
231 Hint =
232 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
233 } else if (BinCmpTempl) {
234 if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE) {
235 ReplacementText = "!" + ReplacementText;
236 }
237 Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
238 ReplacementText);
239 } else if (BinaryOp) { // Determine the correct transformation.
240 bool Negation = false;
241 const bool ContainerIsLHS =
242 !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
243 const auto OpCode = BinaryOp->getOpcode();
244 uint64_t Value = 0;
245 if (ContainerIsLHS) {
246 if (const auto *Literal = llvm::dyn_cast<IntegerLiteral>(
247 BinaryOp->getRHS()->IgnoreImpCasts()))
248 Value = Literal->getValue().getLimitedValue();
249 else
250 return;
251 } else {
252 Value =
253 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts())
254 ->getValue()
255 .getLimitedValue();
256 }
257
258 // Constant that is not handled.
259 if (Value > 1)
260 return;
261
262 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
263 OpCode == BinaryOperatorKind::BO_NE))
264 return;
265
266 // Always true, no warnings for that.
267 if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
268 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
269 return;
270
271 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
272 if (Value == 1) {
273 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
274 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
275 return;
276 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
277 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
278 return;
279 }
280
281 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
282 Negation = true;
283 if ((OpCode == BinaryOperatorKind::BO_GT ||
284 OpCode == BinaryOperatorKind::BO_GE) &&
285 ContainerIsLHS)
286 Negation = true;
287 if ((OpCode == BinaryOperatorKind::BO_LT ||
288 OpCode == BinaryOperatorKind::BO_LE) &&
289 !ContainerIsLHS)
290 Negation = true;
291
292 if (Negation)
293 ReplacementText = "!" + ReplacementText;
294 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
295 ReplacementText);
296
297 } else {
298 // If there is a conversion above the size call to bool, it is safe to just
299 // replace size with empty.
300 if (const auto *UnaryOp =
301 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
302 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
303 ReplacementText);
304 else
305 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
306 "!" + ReplacementText);
307 }
308
309 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
310
311 if (WarnLoc.isValid()) {
312 diag(WarnLoc, "the 'empty' method should be used to check "
313 "for emptiness instead of 'size'")
314 << Hint;
315 } else {
316 WarnLoc = BinCmpTempl ? BinCmpTempl->getBeginLoc()
317 : (BinCmp ? BinCmp->getBeginLoc() : SourceLocation{});
318 diag(WarnLoc, "the 'empty' method should be used to check "
319 "for emptiness instead of comparing to an empty object")
320 << Hint;
321 }
322
323 const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container");
324 if (const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) {
325 // The definition of the empty() method is the same for all implicit
326 // instantiations. In order to avoid duplicate or inconsistent warnings
327 // (depending on how deduplication is done), we use the same class name
328 // for all implicit instantiations of a template.
329 if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation)
330 Container = CTS->getSpecializedTemplate();
331 }
332 const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty");
333
334 diag(Empty->getLocation(), "method %0::empty() defined here",
335 DiagnosticIDs::Note)
336 << Container;
337 }
338
339 } // namespace readability
340 } // namespace tidy
341 } // namespace clang
342