1 //===--- ContainerSizeEmptyCheck.cpp - clang-tidy -------------------------===//
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
7 //===----------------------------------------------------------------------===//
8 #include "ContainerSizeEmptyCheck.h"
9 #include "../utils/ASTUtils.h"
10 #include "../utils/Matchers.h"
11 #include "../utils/OptionsUtils.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"
17 using namespace clang::ast_matchers
;
20 namespace ast_matchers
{
22 AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam
,
23 AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr
,
25 internal::Matcher
<Expr
>, ArgMatcher
,
26 internal::Matcher
<ParmVarDecl
>, ParamMatcher
) {
27 BoundNodesTreeBuilder Result
;
28 // The first argument of an overloaded member operator is the implicit object
29 // argument of the method which should not be matched against a parameter, so
30 // we skip over it here.
31 BoundNodesTreeBuilder Matches
;
32 unsigned ArgIndex
= cxxOperatorCallExpr(callee(cxxMethodDecl()))
33 .matches(Node
, Finder
, &Matches
)
37 for (; ArgIndex
< Node
.getNumArgs(); ++ArgIndex
) {
38 BoundNodesTreeBuilder
ArgMatches(*Builder
);
39 if (ArgMatcher
.matches(*(Node
.getArg(ArgIndex
)->IgnoreParenCasts()), Finder
,
41 BoundNodesTreeBuilder
ParamMatches(ArgMatches
);
42 if (expr(anyOf(cxxConstructExpr(hasDeclaration(cxxConstructorDecl(
43 hasParameter(ParamIndex
, ParamMatcher
)))),
44 callExpr(callee(functionDecl(
45 hasParameter(ParamIndex
, ParamMatcher
))))))
46 .matches(Node
, Finder
, &ParamMatches
)) {
47 Result
.addMatch(ParamMatches
);
48 *Builder
= std::move(Result
);
57 AST_MATCHER(Expr
, usedInBooleanContext
) {
58 const char *ExprName
= "__booleanContextExpr";
60 expr(expr().bind(ExprName
),
62 mapAnyOf(varDecl
, fieldDecl
).with(hasType(booleanType()))),
63 hasParent(cxxConstructorDecl(
64 hasAnyConstructorInitializer(cxxCtorInitializer(
65 withInitializer(expr(equalsBoundNode(ExprName
))),
66 forField(hasType(booleanType())))))),
68 explicitCastExpr(hasDestinationType(booleanType())),
69 mapAnyOf(ifStmt
, doStmt
, whileStmt
, forStmt
,
71 .with(hasCondition(expr(equalsBoundNode(ExprName
)))),
72 parenListExpr(hasParent(varDecl(hasType(booleanType())))),
74 explicitCastExpr(hasDestinationType(booleanType())))),
75 returnStmt(forFunction(returns(booleanType()))),
76 cxxUnresolvedConstructExpr(hasType(booleanType())),
77 invocation(hasAnyArgumentWithParam(
78 expr(equalsBoundNode(ExprName
)),
79 parmVarDecl(hasType(booleanType())))),
80 binaryOperator(hasAnyOperatorName("&&", "||")),
81 unaryOperator(hasOperatorName("!")).bind("NegOnSize"))))))
82 .matches(Node
, Finder
, Builder
);
83 Builder
->removeBindings([ExprName
](const BoundNodesMap
&Nodes
) {
84 return Nodes
.getNode(ExprName
).getNodeKind().isNone();
89 AST_MATCHER(CXXConstructExpr
, isDefaultConstruction
) {
90 return Node
.getConstructor()->isDefaultConstructor();
93 AST_MATCHER(QualType
, isIntegralType
) {
94 return Node
->isIntegralType(Finder
->getASTContext());
97 AST_MATCHER_P(UserDefinedLiteral
, hasLiteral
,
98 clang::ast_matchers::internal::Matcher
<Expr
>, InnerMatcher
) {
99 const UserDefinedLiteral::LiteralOperatorKind LOK
=
100 Node
.getLiteralOperatorKind();
101 if (LOK
== UserDefinedLiteral::LOK_Template
||
102 LOK
== UserDefinedLiteral::LOK_Raw
)
105 if (const Expr
*CookedLiteral
= Node
.getCookedLiteral())
106 return InnerMatcher
.matches(*CookedLiteral
, Finder
, Builder
);
110 } // namespace ast_matchers
111 namespace tidy::readability
{
113 using utils::isBinaryOrTernary
;
115 ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name
,
116 ClangTidyContext
*Context
)
117 : ClangTidyCheck(Name
, Context
),
118 ExcludedComparisonTypes(utils::options::parseStringList(
119 Options
.get("ExcludedComparisonTypes", "::std::array"))) {}
121 void ContainerSizeEmptyCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
122 Options
.store(Opts
, "ExcludedComparisonTypes",
123 utils::options::serializeStringList(ExcludedComparisonTypes
));
126 void ContainerSizeEmptyCheck::registerMatchers(MatchFinder
*Finder
) {
127 const auto ValidContainerRecord
= cxxRecordDecl(isSameOrDerivedFrom(
128 namedDecl(has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
129 hasAnyName("size", "length"),
130 returns(qualType(isIntegralType(),
131 unless(booleanType()))))
133 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
134 hasName("empty"), returns(booleanType()))
136 .bind("container")));
138 const auto ValidContainerNonTemplateType
=
139 qualType(hasUnqualifiedDesugaredType(
140 recordType(hasDeclaration(ValidContainerRecord
))));
141 const auto ValidContainerTemplateType
=
142 qualType(hasUnqualifiedDesugaredType(templateSpecializationType(
143 hasDeclaration(classTemplateDecl(has(ValidContainerRecord
))))));
145 const auto ValidContainer
= qualType(
146 anyOf(ValidContainerNonTemplateType
, ValidContainerTemplateType
));
148 const auto WrongUse
=
149 anyOf(hasParent(binaryOperator(
150 isComparisonOperator(),
151 hasEitherOperand(anyOf(integerLiteral(equals(1)),
152 integerLiteral(equals(0)))))
153 .bind("SizeBinaryOp")),
154 usedInBooleanContext());
159 on(expr(anyOf(hasType(ValidContainer
),
160 hasType(pointsTo(ValidContainer
)),
161 hasType(references(ValidContainer
))))
162 .bind("MemberCallObject")),
164 cxxMethodDecl(hasAnyName("size", "length")).bind("SizeMethod")),
167 cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
168 .bind("SizeCallExpr"),
172 callExpr(argumentCountIs(0),
173 has(cxxDependentScopeMemberExpr(
175 expr(anyOf(hasType(ValidContainer
),
176 hasType(pointsTo(ValidContainer
)),
177 hasType(references(ValidContainer
))))
178 .bind("MemberCallObject")),
179 anyOf(hasMemberName("size"), hasMemberName("length")))
180 .bind("DependentExpr")),
183 cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
184 .bind("SizeCallExpr"),
187 // Comparison to empty string or empty constructor.
188 const auto WrongComparend
=
189 anyOf(stringLiteral(hasSize(0)),
190 userDefinedLiteral(hasLiteral(stringLiteral(hasSize(0)))),
191 cxxConstructExpr(isDefaultConstruction()),
192 cxxUnresolvedConstructExpr(argumentCountIs(0)));
193 // Match the object being compared.
196 hasOperatorName("*"),
198 expr(hasType(pointsTo(ValidContainer
))).bind("Pointee"))),
199 expr(hasType(ValidContainer
)).bind("STLObject"));
201 const auto ExcludedComparisonTypesMatcher
= qualType(anyOf(
203 cxxRecordDecl(matchers::matchesAnyListedName(ExcludedComparisonTypes
))
205 hasCanonicalType(hasDeclaration(
206 cxxRecordDecl(matchers::matchesAnyListedName(ExcludedComparisonTypes
))
207 .bind("excluded")))));
208 const auto SameExcludedComparisonTypesMatcher
=
209 qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode("excluded"))),
210 hasCanonicalType(hasDeclaration(
211 cxxRecordDecl(equalsBoundNode("excluded"))))));
215 hasAnyOperatorName("==", "!="), hasOperands(WrongComparend
, STLArg
),
216 unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher
)),
217 hasRHS(hasType(SameExcludedComparisonTypesMatcher
)))),
219 cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
224 void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult
&Result
) {
225 const auto *MemberCall
= Result
.Nodes
.getNodeAs
<Expr
>("SizeCallExpr");
226 const auto *MemberCallObject
=
227 Result
.Nodes
.getNodeAs
<Expr
>("MemberCallObject");
228 const auto *BinCmp
= Result
.Nodes
.getNodeAs
<CXXOperatorCallExpr
>("BinCmp");
229 const auto *BinCmpTempl
= Result
.Nodes
.getNodeAs
<BinaryOperator
>("BinCmp");
230 const auto *BinCmpRewritten
=
231 Result
.Nodes
.getNodeAs
<CXXRewrittenBinaryOperator
>("BinCmp");
232 const auto *BinaryOp
= Result
.Nodes
.getNodeAs
<BinaryOperator
>("SizeBinaryOp");
233 const auto *Pointee
= Result
.Nodes
.getNodeAs
<Expr
>("Pointee");
237 : (Pointee
? Pointee
: Result
.Nodes
.getNodeAs
<Expr
>("STLObject"));
239 std::string ReplacementText
= std::string(
240 Lexer::getSourceText(CharSourceRange::getTokenRange(E
->getSourceRange()),
241 *Result
.SourceManager
, getLangOpts()));
242 const auto *OpCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(E
);
243 if (isBinaryOrTernary(E
) || isa
<UnaryOperator
>(E
) ||
244 (OpCallExpr
&& (OpCallExpr
->getOperator() == OO_Star
))) {
245 ReplacementText
= "(" + ReplacementText
+ ")";
248 OpCallExpr
->getOperator() == OverloadedOperatorKind::OO_Arrow
) {
249 // This can happen if the object is a smart pointer. Don't add anything
250 // because a '->' is already there (PR#51776), just call the method.
251 ReplacementText
+= "empty()";
252 } else if (E
->getType()->isPointerType())
253 ReplacementText
+= "->empty()";
255 ReplacementText
+= ".empty()";
258 if (BinCmp
->getOperator() == OO_ExclaimEqual
) {
259 ReplacementText
= "!" + ReplacementText
;
262 FixItHint::CreateReplacement(BinCmp
->getSourceRange(), ReplacementText
);
263 } else if (BinCmpTempl
) {
264 if (BinCmpTempl
->getOpcode() == BinaryOperatorKind::BO_NE
) {
265 ReplacementText
= "!" + ReplacementText
;
267 Hint
= FixItHint::CreateReplacement(BinCmpTempl
->getSourceRange(),
269 } else if (BinCmpRewritten
) {
270 if (BinCmpRewritten
->getOpcode() == BinaryOperatorKind::BO_NE
) {
271 ReplacementText
= "!" + ReplacementText
;
273 Hint
= FixItHint::CreateReplacement(BinCmpRewritten
->getSourceRange(),
275 } else if (BinaryOp
) { // Determine the correct transformation.
276 const auto *LiteralLHS
=
277 llvm::dyn_cast
<IntegerLiteral
>(BinaryOp
->getLHS()->IgnoreImpCasts());
278 const auto *LiteralRHS
=
279 llvm::dyn_cast
<IntegerLiteral
>(BinaryOp
->getRHS()->IgnoreImpCasts());
280 const bool ContainerIsLHS
= !LiteralLHS
;
284 Value
= LiteralLHS
->getValue().getLimitedValue();
286 Value
= LiteralRHS
->getValue().getLimitedValue();
290 bool Negation
= false;
291 const auto OpCode
= BinaryOp
->getOpcode();
293 // Constant that is not handled.
297 if (Value
== 1 && (OpCode
== BinaryOperatorKind::BO_EQ
||
298 OpCode
== BinaryOperatorKind::BO_NE
))
301 // Always true/false, no warnings for that.
303 if ((OpCode
== BinaryOperatorKind::BO_GT
&& !ContainerIsLHS
) ||
304 (OpCode
== BinaryOperatorKind::BO_LT
&& ContainerIsLHS
) ||
305 (OpCode
== BinaryOperatorKind::BO_LE
&& !ContainerIsLHS
) ||
306 (OpCode
== BinaryOperatorKind::BO_GE
&& ContainerIsLHS
))
310 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
312 if ((OpCode
== BinaryOperatorKind::BO_GT
&& ContainerIsLHS
) ||
313 (OpCode
== BinaryOperatorKind::BO_LT
&& !ContainerIsLHS
))
315 if ((OpCode
== BinaryOperatorKind::BO_LE
&& ContainerIsLHS
) ||
316 (OpCode
== BinaryOperatorKind::BO_GE
&& !ContainerIsLHS
))
320 // Do not warn for size < 1, 1 > size, size <= 0, 0 >= size for non signed
322 if ((OpCode
== BinaryOperatorKind::BO_GT
&& Value
== 1 &&
324 (OpCode
== BinaryOperatorKind::BO_LT
&& Value
== 1 && ContainerIsLHS
) ||
325 (OpCode
== BinaryOperatorKind::BO_GE
&& Value
== 0 &&
327 (OpCode
== BinaryOperatorKind::BO_LE
&& Value
== 0 && ContainerIsLHS
)) {
328 const Expr
*Container
= ContainerIsLHS
329 ? BinaryOp
->getLHS()->IgnoreImpCasts()
330 : BinaryOp
->getRHS()->IgnoreImpCasts();
331 if (Container
->getType()
333 .getNonReferenceType()
334 ->isSignedIntegerType())
338 if (OpCode
== BinaryOperatorKind::BO_NE
&& Value
== 0)
341 if ((OpCode
== BinaryOperatorKind::BO_GT
||
342 OpCode
== BinaryOperatorKind::BO_GE
) &&
346 if ((OpCode
== BinaryOperatorKind::BO_LT
||
347 OpCode
== BinaryOperatorKind::BO_LE
) &&
352 ReplacementText
= "!" + ReplacementText
;
353 Hint
= FixItHint::CreateReplacement(BinaryOp
->getSourceRange(),
357 // If there is a conversion above the size call to bool, it is safe to just
358 // replace size with empty.
359 if (const auto *UnaryOp
=
360 Result
.Nodes
.getNodeAs
<UnaryOperator
>("NegOnSize"))
361 Hint
= FixItHint::CreateReplacement(UnaryOp
->getSourceRange(),
364 Hint
= FixItHint::CreateReplacement(MemberCall
->getSourceRange(),
365 "!" + ReplacementText
);
368 auto WarnLoc
= MemberCall
? MemberCall
->getBeginLoc() : SourceLocation
{};
370 if (WarnLoc
.isValid()) {
371 auto Diag
= diag(WarnLoc
, "the 'empty' method should be used to check "
372 "for emptiness instead of %0");
373 if (const auto *SizeMethod
=
374 Result
.Nodes
.getNodeAs
<NamedDecl
>("SizeMethod"))
376 else if (const auto *DependentExpr
=
377 Result
.Nodes
.getNodeAs
<CXXDependentScopeMemberExpr
>(
379 Diag
<< DependentExpr
->getMember();
381 Diag
<< "unknown method";
384 WarnLoc
= BinCmpTempl
385 ? BinCmpTempl
->getBeginLoc()
386 : (BinCmp
? BinCmp
->getBeginLoc()
387 : (BinCmpRewritten
? BinCmpRewritten
->getBeginLoc()
388 : SourceLocation
{}));
389 diag(WarnLoc
, "the 'empty' method should be used to check "
390 "for emptiness instead of comparing to an empty object")
394 const auto *Container
= Result
.Nodes
.getNodeAs
<NamedDecl
>("container");
395 if (const auto *CTS
= dyn_cast
<ClassTemplateSpecializationDecl
>(Container
)) {
396 // The definition of the empty() method is the same for all implicit
397 // instantiations. In order to avoid duplicate or inconsistent warnings
398 // (depending on how deduplication is done), we use the same class name
399 // for all implicit instantiations of a template.
400 if (CTS
->getSpecializationKind() == TSK_ImplicitInstantiation
)
401 Container
= CTS
->getSpecializedTemplate();
403 const auto *Empty
= Result
.Nodes
.getNodeAs
<FunctionDecl
>("empty");
405 diag(Empty
->getLocation(), "method %0::empty() defined here",
410 } // namespace tidy::readability