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 const auto ValidContainerExpr =
183 expr(anyOf(hasType(ValidContainer), hasType(pointsTo(ValidContainer)),
184 hasType(references(ValidContainer))))
185 .bind("MemberCallObject");
186
187 Finder->addMatcher(
188 cxxMemberCallExpr(
189 argumentCountIs(0), on(ValidContainerExpr),
190 callee(
191 cxxMethodDecl(hasAnyName("size", "length")).bind("SizeMethod")),
192 WrongUse, NotInEmptyMethodOfContainer)
193 .bind("SizeCallExpr"),
194 this);
195
196 Finder->addMatcher(
197 callExpr(argumentCountIs(0),
198 has(mapAnyOf(memberExpr, cxxDependentScopeMemberExpr)
199 .with(hasObjectExpression(ValidContainerExpr),
200 anyOf(matchMemberName("size"),
201 matchMemberName("length")))
202 .bind("MemberExpr")),
203 WrongUse, NotInEmptyMethodOfContainer)
204 .bind("SizeCallExpr"),
205 this);
206
207 // Match non-member std::size(container) used in boolean context or compared
208 // with 0/1.
209 Finder->addMatcher(
210 callExpr(argumentCountIs(1),
211 callee(functionDecl(hasName("::std::size")).bind("SizeMethod")),
212 hasArgument(0, ValidContainerExpr), WrongUse,
213 NotInEmptyMethodOfContainer)
214 .bind("SizeCallExpr"),
215 this);
216
217 // Comparison to empty string or empty constructor.
218 const auto WrongComparend =
219 anyOf(stringLiteral(hasSize(0)),
220 userDefinedLiteral(hasLiteral(stringLiteral(hasSize(0)))),
221 cxxConstructExpr(argumentCountIs(0)),
222 cxxUnresolvedConstructExpr(argumentCountIs(0)));
223 // Match the object being compared.
224 const auto STLArg =
225 anyOf(unaryOperator(
226 hasOperatorName("*"),
227 hasUnaryOperand(
228 expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))),
229 expr(hasType(ValidContainer)).bind("STLObject"));
230
231 const auto ExcludedComparisonTypesMatcher = qualType(
232 anyOf(hasDeclaration(cxxRecordDecl(matchers::matchesAnyListedRegexName(
233 ExcludedComparisonTypes))
234 .bind("excluded")),
235 hasCanonicalType(hasDeclaration(
237 ExcludedComparisonTypes))
238 .bind("excluded")))));
239 const auto SameExcludedComparisonTypesMatcher =
240 qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode("excluded"))),
241 hasCanonicalType(hasDeclaration(
242 cxxRecordDecl(equalsBoundNode("excluded"))))));
243
244 Finder->addMatcher(
245 binaryOperation(
246 hasAnyOperatorName("==", "!="), hasOperands(WrongComparend, STLArg),
247 unless(hasEitherOperand(cxxConstructExpr(
248 argumentCountIs(0),
249 unless(hasType(qualType(hasCanonicalType(hasDeclaration(
250 // 'equalsBoundNode' needs the 'ContainerDecl' binding
251 // from 'STLArg' to already exist, so this constraint must
252 // appear after 'hasOperands' matcher
253 namedDecl(equalsBoundNode("ContainerDecl")))))))))),
254 unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher)),
255 hasRHS(hasType(SameExcludedComparisonTypesMatcher)))),
256 NotInEmptyMethodOfContainer)
257 .bind("BinCmp"),
258 this);
259}
260
261void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
262 const auto *MemberCall = Result.Nodes.getNodeAs<Expr>("SizeCallExpr");
263 const auto *MemberCallObject =
264 Result.Nodes.getNodeAs<Expr>("MemberCallObject");
265 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");
266 const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>("BinCmp");
267 const auto *BinCmpRewritten =
268 Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>("BinCmp");
269 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
270 const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");
271 const auto *E =
272 MemberCallObject
273 ? MemberCallObject
274 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));
275 FixItHint Hint;
276 std::string ReplacementText =
277 E->isImplicitCXXThis()
278 ? ""
279 : std::string(Lexer::getSourceText(
280 CharSourceRange::getTokenRange(E->getSourceRange()),
281 *Result.SourceManager, getLangOpts()));
282 const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E);
283 if (isBinaryOrTernary(E) || isa<UnaryOperator>(E) ||
284 (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) {
285 ReplacementText = "(" + ReplacementText + ")";
286 }
287 if (OpCallExpr &&
288 OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) {
289 // This can happen if the object is a smart pointer. Don't add anything
290 // because a '->' is already there (PR#51776), just call the method.
291 ReplacementText += "empty()";
292 } else if (E->isImplicitCXXThis()) {
293 ReplacementText += "empty()";
294 } else if (E->getType()->isPointerType()) {
295 ReplacementText += "->empty()";
296 } else {
297 ReplacementText += ".empty()";
298 }
299
300 if (BinCmp) {
301 if (BinCmp->getOperator() == OO_ExclaimEqual)
302 ReplacementText = "!" + ReplacementText;
303 Hint =
304 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
305 } else if (BinCmpTempl) {
306 if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE)
307 ReplacementText = "!" + ReplacementText;
308 Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
309 ReplacementText);
310 } else if (BinCmpRewritten) {
311 if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE)
312 ReplacementText = "!" + ReplacementText;
313 Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),
314 ReplacementText);
315 } else if (BinaryOp) { // Determine the correct transformation.
316 const auto *LiteralLHS =
317 dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
318 const auto *LiteralRHS =
319 dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts());
320 const bool ContainerIsLHS = !LiteralLHS;
321
322 uint64_t Value = 0;
323 if (LiteralLHS)
324 Value = LiteralLHS->getValue().getLimitedValue();
325 else if (LiteralRHS)
326 Value = LiteralRHS->getValue().getLimitedValue();
327 else
328 return;
329
330 bool Negation = false;
331 const auto OpCode = BinaryOp->getOpcode();
332
333 // Constant that is not handled.
334 if (Value > 1)
335 return;
336
337 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
338 OpCode == BinaryOperatorKind::BO_NE))
339 return;
340
341 // Always true/false, no warnings for that.
342 if (Value == 0) {
343 if ((OpCode == BinaryOperatorKind::BO_GT && !ContainerIsLHS) ||
344 (OpCode == BinaryOperatorKind::BO_LT && ContainerIsLHS) ||
345 (OpCode == BinaryOperatorKind::BO_LE && !ContainerIsLHS) ||
346 (OpCode == BinaryOperatorKind::BO_GE && ContainerIsLHS))
347 return;
348 }
349
350 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
351 if (Value == 1) {
352 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
353 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
354 return;
355 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
356 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
357 return;
358 }
359
360 // Do not warn for size < 1, 1 > size, size <= 0, 0 >= size for non signed
361 // types
362 if ((OpCode == BinaryOperatorKind::BO_GT && Value == 1 &&
363 !ContainerIsLHS) ||
364 (OpCode == BinaryOperatorKind::BO_LT && Value == 1 && ContainerIsLHS) ||
365 (OpCode == BinaryOperatorKind::BO_GE && Value == 0 &&
366 !ContainerIsLHS) ||
367 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && ContainerIsLHS)) {
368 const Expr *Container = ContainerIsLHS
369 ? BinaryOp->getLHS()->IgnoreImpCasts()
370 : BinaryOp->getRHS()->IgnoreImpCasts();
371 if (Container->getType()
372 .getCanonicalType()
373 .getNonReferenceType()
374 ->isSignedIntegerType())
375 return;
376 }
377
378 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
379 Negation = true;
380
381 if ((OpCode == BinaryOperatorKind::BO_GT ||
382 OpCode == BinaryOperatorKind::BO_GE) &&
383 ContainerIsLHS)
384 Negation = true;
385
386 if ((OpCode == BinaryOperatorKind::BO_LT ||
387 OpCode == BinaryOperatorKind::BO_LE) &&
388 !ContainerIsLHS)
389 Negation = true;
390
391 if (Negation)
392 ReplacementText = "!" + ReplacementText;
393 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
394 ReplacementText);
395
396 } else {
397 // If there is a conversion above the size call to bool, it is safe to just
398 // replace size with empty.
399 if (const auto *UnaryOp =
400 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
401 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
402 ReplacementText);
403 else
404 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
405 "!" + ReplacementText);
406 }
407
408 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
409
410 if (WarnLoc.isValid()) {
411 auto Diag = diag(WarnLoc, "the 'empty' method should be used to check "
412 "for emptiness instead of %0");
413 if (const auto *SizeMethod =
414 Result.Nodes.getNodeAs<NamedDecl>("SizeMethod"))
415 Diag << SizeMethod->getDeclName();
416 else if (const auto *DependentExpr =
417 Result.Nodes.getNodeAs<CXXDependentScopeMemberExpr>(
418 "MemberExpr"))
419 Diag << DependentExpr->getMember();
420 else if (const auto *ME = Result.Nodes.getNodeAs<MemberExpr>("MemberExpr"))
421 Diag << ME->getMemberNameInfo().getName();
422 else
423 Diag << "unknown method";
424 Diag << Hint;
425 } else {
426 WarnLoc = BinCmpTempl
427 ? BinCmpTempl->getBeginLoc()
428 : (BinCmp ? BinCmp->getBeginLoc()
429 : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc()
430 : SourceLocation{}));
431 diag(WarnLoc, "the 'empty' method should be used to check "
432 "for emptiness instead of comparing to an empty object")
433 << Hint;
434 }
435}
436
437} // 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