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 clang::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 clang::ast_matchers::internal::Matcher<CXXMethodDecl>,
111 InnerMatcher) {
112 return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder);
113}
114
115AST_POLYMORPHIC_MATCHER_P(
116 matchMemberName,
117 AST_POLYMORPHIC_SUPPORTED_TYPES(MemberExpr, CXXDependentScopeMemberExpr),
118 std::string, MemberName) {
119 if (const auto *E = dyn_cast<MemberExpr>(&Node)) {
120 const IdentifierInfo *II = E->getMemberDecl()->getIdentifier();
121 return II && II->getName() == MemberName;
122 }
123
124 if (const auto *E = dyn_cast<CXXDependentScopeMemberExpr>(&Node)) {
125 const IdentifierInfo *II = E->getMember().getAsIdentifierInfo();
126 return II && II->getName() == MemberName;
127 }
128
129 return false;
130}
131
132} // namespace
133
135
137 ClangTidyContext *Context)
138 : ClangTidyCheck(Name, Context),
139 ExcludedComparisonTypes(utils::options::parseStringList(
140 Options.get("ExcludedComparisonTypes", "::std::array"))) {}
141
143 Options.store(Opts, "ExcludedComparisonTypes",
144 utils::options::serializeStringList(ExcludedComparisonTypes));
145}
146
148 const auto ValidContainerRecord = cxxRecordDecl(isSameOrDerivedFrom(
149 namedDecl(has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
150 hasAnyName("size", "length"),
151 returns(qualType(isIntegralType(),
152 unless(booleanType()))))
153 .bind("size")),
154 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
155 hasName("empty"), returns(booleanType()))
156 .bind("empty")))
157 .bind("container")));
158
159 const auto ValidContainerNonTemplateType =
160 qualType(hasUnqualifiedDesugaredType(
161 recordType(hasDeclaration(ValidContainerRecord))));
162 const auto ValidContainerTemplateType = qualType(hasUnqualifiedDesugaredType(
163 anyOf(templateSpecializationType(
164 hasDeclaration(classTemplateDecl(has(ValidContainerRecord)))),
165 injectedClassNameType(hasDeclaration(ValidContainerRecord)))));
166
167 const auto ValidContainer = qualType(
168 anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType));
169
170 const auto WrongUse =
171 anyOf(hasParent(binaryOperator(
172 isComparisonOperator(),
173 hasEitherOperand(anyOf(integerLiteral(equals(1)),
174 integerLiteral(equals(0)))))
175 .bind("SizeBinaryOp")),
176 usedInBooleanContext());
177
178 const auto NotInEmptyMethodOfContainer = unless(
179 forCallable(cxxMethodDecl(hasCanonicalDecl(equalsBoundNode("empty")))));
180
181 Finder->addMatcher(
182 cxxMemberCallExpr(
183 argumentCountIs(0),
184 on(expr(anyOf(hasType(ValidContainer),
185 hasType(pointsTo(ValidContainer)),
186 hasType(references(ValidContainer))))
187 .bind("MemberCallObject")),
188 callee(
189 cxxMethodDecl(hasAnyName("size", "length")).bind("SizeMethod")),
190 WrongUse, NotInEmptyMethodOfContainer)
191 .bind("SizeCallExpr"),
192 this);
193
194 Finder->addMatcher(
195 callExpr(
196 argumentCountIs(0),
197 has(mapAnyOf(memberExpr, cxxDependentScopeMemberExpr)
198 .with(
199 hasObjectExpression(
200 expr(anyOf(hasType(ValidContainer),
201 hasType(pointsTo(ValidContainer)),
202 hasType(references(ValidContainer))))
203 .bind("MemberCallObject")),
204 anyOf(matchMemberName("size"), matchMemberName("length")))
205 .bind("MemberExpr")),
206 WrongUse, NotInEmptyMethodOfContainer)
207 .bind("SizeCallExpr"),
208 this);
209
210 // Comparison to empty string or empty constructor.
211 const auto WrongComparend =
212 anyOf(stringLiteral(hasSize(0)),
213 userDefinedLiteral(hasLiteral(stringLiteral(hasSize(0)))),
214 cxxConstructExpr(argumentCountIs(0)),
215 cxxUnresolvedConstructExpr(argumentCountIs(0)));
216 // Match the object being compared.
217 const auto STLArg =
218 anyOf(unaryOperator(
219 hasOperatorName("*"),
220 hasUnaryOperand(
221 expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))),
222 expr(hasType(ValidContainer)).bind("STLObject"));
223
224 const auto ExcludedComparisonTypesMatcher = qualType(
225 anyOf(hasDeclaration(cxxRecordDecl(matchers::matchesAnyListedRegexName(
226 ExcludedComparisonTypes))
227 .bind("excluded")),
228 hasCanonicalType(hasDeclaration(
230 ExcludedComparisonTypes))
231 .bind("excluded")))));
232 const auto SameExcludedComparisonTypesMatcher =
233 qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode("excluded"))),
234 hasCanonicalType(hasDeclaration(
235 cxxRecordDecl(equalsBoundNode("excluded"))))));
236
237 Finder->addMatcher(
238 binaryOperation(
239 hasAnyOperatorName("==", "!="), hasOperands(WrongComparend, STLArg),
240 unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher)),
241 hasRHS(hasType(SameExcludedComparisonTypesMatcher)))),
242 NotInEmptyMethodOfContainer)
243 .bind("BinCmp"),
244 this);
245}
246
247void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
248 const auto *MemberCall = Result.Nodes.getNodeAs<Expr>("SizeCallExpr");
249 const auto *MemberCallObject =
250 Result.Nodes.getNodeAs<Expr>("MemberCallObject");
251 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");
252 const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>("BinCmp");
253 const auto *BinCmpRewritten =
254 Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>("BinCmp");
255 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
256 const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");
257 const auto *E =
258 MemberCallObject
259 ? MemberCallObject
260 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));
261 FixItHint Hint;
262 std::string ReplacementText =
263 E->isImplicitCXXThis()
264 ? ""
265 : std::string(Lexer::getSourceText(
266 CharSourceRange::getTokenRange(E->getSourceRange()),
267 *Result.SourceManager, getLangOpts()));
268 const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E);
269 if (isBinaryOrTernary(E) || isa<UnaryOperator>(E) ||
270 (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) {
271 ReplacementText = "(" + ReplacementText + ")";
272 }
273 if (OpCallExpr &&
274 OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) {
275 // This can happen if the object is a smart pointer. Don't add anything
276 // because a '->' is already there (PR#51776), just call the method.
277 ReplacementText += "empty()";
278 } else if (E->isImplicitCXXThis()) {
279 ReplacementText += "empty()";
280 } else if (E->getType()->isPointerType()) {
281 ReplacementText += "->empty()";
282 } else {
283 ReplacementText += ".empty()";
284 }
285
286 if (BinCmp) {
287 if (BinCmp->getOperator() == OO_ExclaimEqual)
288 ReplacementText = "!" + ReplacementText;
289 Hint =
290 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
291 } else if (BinCmpTempl) {
292 if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE)
293 ReplacementText = "!" + ReplacementText;
294 Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
295 ReplacementText);
296 } else if (BinCmpRewritten) {
297 if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE)
298 ReplacementText = "!" + ReplacementText;
299 Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),
300 ReplacementText);
301 } else if (BinaryOp) { // Determine the correct transformation.
302 const auto *LiteralLHS =
303 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
304 const auto *LiteralRHS =
305 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts());
306 const bool ContainerIsLHS = !LiteralLHS;
307
308 uint64_t Value = 0;
309 if (LiteralLHS)
310 Value = LiteralLHS->getValue().getLimitedValue();
311 else if (LiteralRHS)
312 Value = LiteralRHS->getValue().getLimitedValue();
313 else
314 return;
315
316 bool Negation = false;
317 const auto OpCode = BinaryOp->getOpcode();
318
319 // Constant that is not handled.
320 if (Value > 1)
321 return;
322
323 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
324 OpCode == BinaryOperatorKind::BO_NE))
325 return;
326
327 // Always true/false, no warnings for that.
328 if (Value == 0) {
329 if ((OpCode == BinaryOperatorKind::BO_GT && !ContainerIsLHS) ||
330 (OpCode == BinaryOperatorKind::BO_LT && ContainerIsLHS) ||
331 (OpCode == BinaryOperatorKind::BO_LE && !ContainerIsLHS) ||
332 (OpCode == BinaryOperatorKind::BO_GE && ContainerIsLHS))
333 return;
334 }
335
336 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
337 if (Value == 1) {
338 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
339 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
340 return;
341 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
342 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
343 return;
344 }
345
346 // Do not warn for size < 1, 1 > size, size <= 0, 0 >= size for non signed
347 // types
348 if ((OpCode == BinaryOperatorKind::BO_GT && Value == 1 &&
349 !ContainerIsLHS) ||
350 (OpCode == BinaryOperatorKind::BO_LT && Value == 1 && ContainerIsLHS) ||
351 (OpCode == BinaryOperatorKind::BO_GE && Value == 0 &&
352 !ContainerIsLHS) ||
353 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && ContainerIsLHS)) {
354 const Expr *Container = ContainerIsLHS
355 ? BinaryOp->getLHS()->IgnoreImpCasts()
356 : BinaryOp->getRHS()->IgnoreImpCasts();
357 if (Container->getType()
358 .getCanonicalType()
359 .getNonReferenceType()
360 ->isSignedIntegerType())
361 return;
362 }
363
364 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
365 Negation = true;
366
367 if ((OpCode == BinaryOperatorKind::BO_GT ||
368 OpCode == BinaryOperatorKind::BO_GE) &&
369 ContainerIsLHS)
370 Negation = true;
371
372 if ((OpCode == BinaryOperatorKind::BO_LT ||
373 OpCode == BinaryOperatorKind::BO_LE) &&
374 !ContainerIsLHS)
375 Negation = true;
376
377 if (Negation)
378 ReplacementText = "!" + ReplacementText;
379 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
380 ReplacementText);
381
382 } else {
383 // If there is a conversion above the size call to bool, it is safe to just
384 // replace size with empty.
385 if (const auto *UnaryOp =
386 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
387 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
388 ReplacementText);
389 else
390 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
391 "!" + ReplacementText);
392 }
393
394 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
395
396 if (WarnLoc.isValid()) {
397 auto Diag = diag(WarnLoc, "the 'empty' method should be used to check "
398 "for emptiness instead of %0");
399 if (const auto *SizeMethod =
400 Result.Nodes.getNodeAs<NamedDecl>("SizeMethod"))
401 Diag << SizeMethod;
402 else if (const auto *DependentExpr =
403 Result.Nodes.getNodeAs<CXXDependentScopeMemberExpr>(
404 "MemberExpr"))
405 Diag << DependentExpr->getMember();
406 else if (const auto *ME = Result.Nodes.getNodeAs<MemberExpr>("MemberExpr"))
407 Diag << ME->getMemberNameInfo().getName();
408 else
409 Diag << "unknown method";
410 Diag << Hint;
411 } else {
412 WarnLoc = BinCmpTempl
413 ? BinCmpTempl->getBeginLoc()
414 : (BinCmp ? BinCmp->getBeginLoc()
415 : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc()
416 : SourceLocation{}));
417 diag(WarnLoc, "the 'empty' method should be used to check "
418 "for emptiness instead of comparing to an empty object")
419 << Hint;
420 }
421
422 const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container");
423 if (const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) {
424 // The definition of the empty() method is the same for all implicit
425 // instantiations. In order to avoid duplicate or inconsistent warnings
426 // (depending on how deduplication is done), we use the same class name
427 // for all implicit instantiations of a template.
428 if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation)
429 Container = CTS->getSpecializedTemplate();
430 }
431 const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty");
432
433 diag(Empty->getLocation(), "method %0::empty() defined here",
434 DiagnosticIDs::Note)
435 << Container;
436}
437
438} // 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