clang-tools 23.0.0git
ContainerSizeEmptyCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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//===----------------------------------------------------------------------===//
9#include "../utils/ASTUtils.h"
10#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
17using namespace clang::ast_matchers;
18
20
21namespace {
22
23AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam,
24 AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr,
25 CXXConstructExpr),
26 ast_matchers::internal::Matcher<Expr>, ArgMatcher,
27 ast_matchers::internal::Matcher<ParmVarDecl>,
28 ParamMatcher) {
29 ast_matchers::internal::BoundNodesTreeBuilder Result;
30 // The first argument of an overloaded member operator is the implicit object
31 // argument of the method which should not be matched against a parameter, so
32 // we skip over it here.
33 ast_matchers::internal::BoundNodesTreeBuilder Matches;
34 unsigned ArgIndex = cxxOperatorCallExpr(callee(cxxMethodDecl()))
35 .matches(Node, Finder, &Matches)
36 ? 1
37 : 0;
38 int ParamIndex = 0;
39 for (; ArgIndex < Node.getNumArgs(); ++ArgIndex) {
40 ast_matchers::internal::BoundNodesTreeBuilder ArgMatches(*Builder);
41 if (ArgMatcher.matches(*(Node.getArg(ArgIndex)->IgnoreParenCasts()), Finder,
42 &ArgMatches)) {
43 ast_matchers::internal::BoundNodesTreeBuilder ParamMatches(ArgMatches);
44 if (expr(anyOf(cxxConstructExpr(hasDeclaration(cxxConstructorDecl(
45 hasParameter(ParamIndex, ParamMatcher)))),
46 callExpr(callee(functionDecl(
47 hasParameter(ParamIndex, ParamMatcher))))))
48 .matches(Node, Finder, &ParamMatches)) {
49 Result.addMatch(ParamMatches);
50 *Builder = std::move(Result);
51 return true;
52 }
53 }
54 ++ParamIndex;
55 }
56 return false;
57}
58
59AST_MATCHER(Expr, usedInBooleanContext) {
60 const char *ExprName = "__booleanContextExpr";
61 auto Result =
62 expr(expr().bind(ExprName),
63 anyOf(hasParent(
64 mapAnyOf(varDecl, fieldDecl).with(hasType(booleanType()))),
65 hasParent(cxxConstructorDecl(
66 hasAnyConstructorInitializer(cxxCtorInitializer(
67 withInitializer(expr(equalsBoundNode(ExprName))),
68 forField(hasType(booleanType())))))),
69 hasParent(stmt(anyOf(
70 explicitCastExpr(hasDestinationType(booleanType())),
71 mapAnyOf(ifStmt, doStmt, whileStmt, forStmt,
72 conditionalOperator)
73 .with(hasCondition(expr(equalsBoundNode(ExprName)))),
74 parenListExpr(hasParent(varDecl(hasType(booleanType())))),
75 parenExpr(hasParent(
76 explicitCastExpr(hasDestinationType(booleanType())))),
77 returnStmt(forFunction(returns(booleanType()))),
78 cxxUnresolvedConstructExpr(hasType(booleanType())),
79 invocation(hasAnyArgumentWithParam(
80 expr(equalsBoundNode(ExprName)),
81 parmVarDecl(hasType(booleanType())))),
82 binaryOperator(hasAnyOperatorName("&&", "||")),
83 unaryOperator(hasOperatorName("!")).bind("NegOnSize"))))))
84 .matches(Node, Finder, Builder);
85 Builder->removeBindings(
86 [ExprName](const ast_matchers::internal::BoundNodesMap &Nodes) {
87 return Nodes.getNode(ExprName).getNodeKind().isNone();
88 });
89 return Result;
90}
91
92AST_MATCHER(QualType, isIntegralType) {
93 return Node->isIntegralType(Finder->getASTContext());
94}
95
96AST_MATCHER_P(UserDefinedLiteral, hasLiteral,
97 ast_matchers::internal::Matcher<Expr>, InnerMatcher) {
98 const UserDefinedLiteral::LiteralOperatorKind LOK =
99 Node.getLiteralOperatorKind();
100 if (LOK == UserDefinedLiteral::LOK_Template ||
101 LOK == UserDefinedLiteral::LOK_Raw)
102 return false;
103
104 if (const Expr *CookedLiteral = Node.getCookedLiteral())
105 return InnerMatcher.matches(*CookedLiteral, Finder, Builder);
106 return false;
107}
108
109AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl,
110 ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) {
111 return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder);
112}
113
114AST_POLYMORPHIC_MATCHER_P(
115 matchMemberName,
116 AST_POLYMORPHIC_SUPPORTED_TYPES(MemberExpr, CXXDependentScopeMemberExpr),
117 std::string, MemberName) {
118 if (const auto *E = dyn_cast<MemberExpr>(&Node)) {
119 const IdentifierInfo *II = E->getMemberDecl()->getIdentifier();
120 return II && II->getName() == MemberName;
121 }
122
123 if (const auto *E = dyn_cast<CXXDependentScopeMemberExpr>(&Node)) {
124 const IdentifierInfo *II = E->getMember().getAsIdentifierInfo();
125 return II && II->getName() == MemberName;
126 }
127
128 return false;
129}
130
131} // namespace
132
134
136 ClangTidyContext *Context)
137 : ClangTidyCheck(Name, Context),
138 ExcludedComparisonTypes(utils::options::parseStringList(
139 Options.get("ExcludedComparisonTypes", "::std::array"))) {}
140
142 Options.store(Opts, "ExcludedComparisonTypes",
143 utils::options::serializeStringList(ExcludedComparisonTypes));
144}
145
147 const auto ValidContainerRecord =
148 cxxRecordDecl(
149 isSameOrDerivedFrom(namedDecl(
150 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
151 hasAnyName("size", "length"),
152 returns(qualType(isIntegralType(),
153 unless(booleanType()))))
154 .bind("size")),
155 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
156 hasName("empty"), returns(booleanType()))
157 .bind("empty")))))
158 .bind("ContainerDecl");
159
160 const auto ValidContainerNonTemplateType =
161 qualType(hasUnqualifiedDesugaredType(
162 recordType(hasDeclaration(ValidContainerRecord))));
163 const auto ValidContainerTemplateType = qualType(hasUnqualifiedDesugaredType(
164 anyOf(templateSpecializationType(
165 hasDeclaration(classTemplateDecl(has(ValidContainerRecord)))),
166 injectedClassNameType(hasDeclaration(ValidContainerRecord)))));
167
168 const auto ValidContainer = qualType(
169 anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType));
170
171 const auto WrongUse =
172 anyOf(hasParent(binaryOperator(
173 isComparisonOperator(),
174 hasEitherOperand(anyOf(integerLiteral(equals(1)),
175 integerLiteral(equals(0)))))
176 .bind("SizeBinaryOp")),
177 usedInBooleanContext());
178
179 const auto NotInEmptyMethodOfContainer = unless(
180 forCallable(cxxMethodDecl(hasCanonicalDecl(equalsBoundNode("empty")))));
181
182 Finder->addMatcher(
183 cxxMemberCallExpr(
184 argumentCountIs(0),
185 on(expr(anyOf(hasType(ValidContainer),
186 hasType(pointsTo(ValidContainer)),
187 hasType(references(ValidContainer))))
188 .bind("MemberCallObject")),
189 callee(
190 cxxMethodDecl(hasAnyName("size", "length")).bind("SizeMethod")),
191 WrongUse, NotInEmptyMethodOfContainer)
192 .bind("SizeCallExpr"),
193 this);
194
195 Finder->addMatcher(
196 callExpr(
197 argumentCountIs(0),
198 has(mapAnyOf(memberExpr, cxxDependentScopeMemberExpr)
199 .with(
200 hasObjectExpression(
201 expr(anyOf(hasType(ValidContainer),
202 hasType(pointsTo(ValidContainer)),
203 hasType(references(ValidContainer))))
204 .bind("MemberCallObject")),
205 anyOf(matchMemberName("size"), matchMemberName("length")))
206 .bind("MemberExpr")),
207 WrongUse, NotInEmptyMethodOfContainer)
208 .bind("SizeCallExpr"),
209 this);
210
211 // Comparison to empty string or empty constructor.
212 const auto WrongComparend =
213 anyOf(stringLiteral(hasSize(0)),
214 userDefinedLiteral(hasLiteral(stringLiteral(hasSize(0)))),
215 cxxConstructExpr(argumentCountIs(0)),
216 cxxUnresolvedConstructExpr(argumentCountIs(0)));
217 // Match the object being compared.
218 const auto STLArg =
219 anyOf(unaryOperator(
220 hasOperatorName("*"),
221 hasUnaryOperand(
222 expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))),
223 expr(hasType(ValidContainer)).bind("STLObject"));
224
225 const auto ExcludedComparisonTypesMatcher = qualType(
226 anyOf(hasDeclaration(cxxRecordDecl(matchers::matchesAnyListedRegexName(
227 ExcludedComparisonTypes))
228 .bind("excluded")),
229 hasCanonicalType(hasDeclaration(
231 ExcludedComparisonTypes))
232 .bind("excluded")))));
233 const auto SameExcludedComparisonTypesMatcher =
234 qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode("excluded"))),
235 hasCanonicalType(hasDeclaration(
236 cxxRecordDecl(equalsBoundNode("excluded"))))));
237
238 Finder->addMatcher(
239 binaryOperation(
240 hasAnyOperatorName("==", "!="), hasOperands(WrongComparend, STLArg),
241 unless(hasEitherOperand(cxxConstructExpr(
242 argumentCountIs(0),
243 unless(hasType(qualType(hasCanonicalType(hasDeclaration(
244 // 'equalsBoundNode' needs the 'ContainerDecl' binding
245 // from 'STLArg' to already exist, so this constraint must
246 // appear after 'hasOperands' matcher
247 namedDecl(equalsBoundNode("ContainerDecl")))))))))),
248 unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher)),
249 hasRHS(hasType(SameExcludedComparisonTypesMatcher)))),
250 NotInEmptyMethodOfContainer)
251 .bind("BinCmp"),
252 this);
253}
254
255void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
256 const auto *MemberCall = Result.Nodes.getNodeAs<Expr>("SizeCallExpr");
257 const auto *MemberCallObject =
258 Result.Nodes.getNodeAs<Expr>("MemberCallObject");
259 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");
260 const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>("BinCmp");
261 const auto *BinCmpRewritten =
262 Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>("BinCmp");
263 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
264 const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");
265 const auto *E =
266 MemberCallObject
267 ? MemberCallObject
268 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));
269 FixItHint Hint;
270 std::string ReplacementText =
271 E->isImplicitCXXThis()
272 ? ""
273 : std::string(Lexer::getSourceText(
274 CharSourceRange::getTokenRange(E->getSourceRange()),
275 *Result.SourceManager, getLangOpts()));
276 const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E);
277 if (isBinaryOrTernary(E) || isa<UnaryOperator>(E) ||
278 (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) {
279 ReplacementText = "(" + ReplacementText + ")";
280 }
281 if (OpCallExpr &&
282 OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) {
283 // This can happen if the object is a smart pointer. Don't add anything
284 // because a '->' is already there (PR#51776), just call the method.
285 ReplacementText += "empty()";
286 } else if (E->isImplicitCXXThis()) {
287 ReplacementText += "empty()";
288 } else if (E->getType()->isPointerType()) {
289 ReplacementText += "->empty()";
290 } else {
291 ReplacementText += ".empty()";
292 }
293
294 if (BinCmp) {
295 if (BinCmp->getOperator() == OO_ExclaimEqual)
296 ReplacementText = "!" + ReplacementText;
297 Hint =
298 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
299 } else if (BinCmpTempl) {
300 if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE)
301 ReplacementText = "!" + ReplacementText;
302 Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
303 ReplacementText);
304 } else if (BinCmpRewritten) {
305 if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE)
306 ReplacementText = "!" + ReplacementText;
307 Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),
308 ReplacementText);
309 } else if (BinaryOp) { // Determine the correct transformation.
310 const auto *LiteralLHS =
311 dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
312 const auto *LiteralRHS =
313 dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts());
314 const bool ContainerIsLHS = !LiteralLHS;
315
316 uint64_t Value = 0;
317 if (LiteralLHS)
318 Value = LiteralLHS->getValue().getLimitedValue();
319 else if (LiteralRHS)
320 Value = LiteralRHS->getValue().getLimitedValue();
321 else
322 return;
323
324 bool Negation = false;
325 const auto OpCode = BinaryOp->getOpcode();
326
327 // Constant that is not handled.
328 if (Value > 1)
329 return;
330
331 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
332 OpCode == BinaryOperatorKind::BO_NE))
333 return;
334
335 // Always true/false, no warnings for that.
336 if (Value == 0) {
337 if ((OpCode == BinaryOperatorKind::BO_GT && !ContainerIsLHS) ||
338 (OpCode == BinaryOperatorKind::BO_LT && ContainerIsLHS) ||
339 (OpCode == BinaryOperatorKind::BO_LE && !ContainerIsLHS) ||
340 (OpCode == BinaryOperatorKind::BO_GE && ContainerIsLHS))
341 return;
342 }
343
344 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
345 if (Value == 1) {
346 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
347 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
348 return;
349 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
350 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
351 return;
352 }
353
354 // Do not warn for size < 1, 1 > size, size <= 0, 0 >= size for non signed
355 // types
356 if ((OpCode == BinaryOperatorKind::BO_GT && Value == 1 &&
357 !ContainerIsLHS) ||
358 (OpCode == BinaryOperatorKind::BO_LT && Value == 1 && ContainerIsLHS) ||
359 (OpCode == BinaryOperatorKind::BO_GE && Value == 0 &&
360 !ContainerIsLHS) ||
361 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && ContainerIsLHS)) {
362 const Expr *Container = ContainerIsLHS
363 ? BinaryOp->getLHS()->IgnoreImpCasts()
364 : BinaryOp->getRHS()->IgnoreImpCasts();
365 if (Container->getType()
366 .getCanonicalType()
367 .getNonReferenceType()
368 ->isSignedIntegerType())
369 return;
370 }
371
372 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
373 Negation = true;
374
375 if ((OpCode == BinaryOperatorKind::BO_GT ||
376 OpCode == BinaryOperatorKind::BO_GE) &&
377 ContainerIsLHS)
378 Negation = true;
379
380 if ((OpCode == BinaryOperatorKind::BO_LT ||
381 OpCode == BinaryOperatorKind::BO_LE) &&
382 !ContainerIsLHS)
383 Negation = true;
384
385 if (Negation)
386 ReplacementText = "!" + ReplacementText;
387 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
388 ReplacementText);
389
390 } else {
391 // If there is a conversion above the size call to bool, it is safe to just
392 // replace size with empty.
393 if (const auto *UnaryOp =
394 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
395 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
396 ReplacementText);
397 else
398 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
399 "!" + ReplacementText);
400 }
401
402 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
403
404 if (WarnLoc.isValid()) {
405 auto Diag = diag(WarnLoc, "the 'empty' method should be used to check "
406 "for emptiness instead of %0");
407 if (const auto *SizeMethod =
408 Result.Nodes.getNodeAs<NamedDecl>("SizeMethod"))
409 Diag << SizeMethod;
410 else if (const auto *DependentExpr =
411 Result.Nodes.getNodeAs<CXXDependentScopeMemberExpr>(
412 "MemberExpr"))
413 Diag << DependentExpr->getMember();
414 else if (const auto *ME = Result.Nodes.getNodeAs<MemberExpr>("MemberExpr"))
415 Diag << ME->getMemberNameInfo().getName();
416 else
417 Diag << "unknown method";
418 Diag << Hint;
419 } else {
420 WarnLoc = BinCmpTempl
421 ? BinCmpTempl->getBeginLoc()
422 : (BinCmp ? BinCmp->getBeginLoc()
423 : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc()
424 : SourceLocation{}));
425 diag(WarnLoc, "the 'empty' method should be used to check "
426 "for emptiness instead of comparing to an empty object")
427 << Hint;
428 }
429}
430
431} // namespace clang::tidy::readability
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
ContainerSizeEmptyCheck(StringRef Name, ClangTidyContext *Context)
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
AST_MATCHER_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID)
AST_MATCHER(BinaryOperator, isRelationalOperator)
inline ::clang::ast_matchers::internal::Matcher< NamedDecl > matchesAnyListedRegexName(llvm::ArrayRef< StringRef > NameList)
bool isBinaryOrTernary(const Expr *E)
Definition ASTUtils.cpp:25
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
bool isBinaryOrTernary(const Expr *E)
Definition ASTUtils.cpp:25
llvm::StringMap< ClangTidyValue > OptionMap