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 if (const Expr
*CookedLiteral
= Node
.getCookedLiteral()) {
100 return InnerMatcher
.matches(*CookedLiteral
, Finder
, Builder
);
105 } // namespace ast_matchers
106 namespace tidy::readability
{
108 using utils::isBinaryOrTernary
;
110 ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name
,
111 ClangTidyContext
*Context
)
112 : ClangTidyCheck(Name
, Context
),
113 ExcludedComparisonTypes(utils::options::parseStringList(
114 Options
.get("ExcludedComparisonTypes", "::std::array"))) {}
116 void ContainerSizeEmptyCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
117 Options
.store(Opts
, "ExcludedComparisonTypes",
118 utils::options::serializeStringList(ExcludedComparisonTypes
));
121 void ContainerSizeEmptyCheck::registerMatchers(MatchFinder
*Finder
) {
122 const auto ValidContainerRecord
= cxxRecordDecl(isSameOrDerivedFrom(
123 namedDecl(has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
124 hasAnyName("size", "length"),
125 returns(qualType(isIntegralType(),
126 unless(booleanType()))))
128 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
129 hasName("empty"), returns(booleanType()))
131 .bind("container")));
133 const auto ValidContainerNonTemplateType
=
134 qualType(hasUnqualifiedDesugaredType(
135 recordType(hasDeclaration(ValidContainerRecord
))));
136 const auto ValidContainerTemplateType
=
137 qualType(hasUnqualifiedDesugaredType(templateSpecializationType(
138 hasDeclaration(classTemplateDecl(has(ValidContainerRecord
))))));
140 const auto ValidContainer
= qualType(
141 anyOf(ValidContainerNonTemplateType
, ValidContainerTemplateType
));
143 const auto WrongUse
=
144 anyOf(hasParent(binaryOperator(
145 isComparisonOperator(),
146 hasEitherOperand(anyOf(integerLiteral(equals(1)),
147 integerLiteral(equals(0)))))
148 .bind("SizeBinaryOp")),
149 usedInBooleanContext());
153 on(expr(anyOf(hasType(ValidContainer
),
154 hasType(pointsTo(ValidContainer
)),
155 hasType(references(ValidContainer
))))
156 .bind("MemberCallObject")),
158 cxxMethodDecl(hasAnyName("size", "length")).bind("SizeMethod")),
161 cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
162 .bind("SizeCallExpr"),
166 callExpr(has(cxxDependentScopeMemberExpr(
168 expr(anyOf(hasType(ValidContainer
),
169 hasType(pointsTo(ValidContainer
)),
170 hasType(references(ValidContainer
))))
171 .bind("MemberCallObject")),
172 anyOf(hasMemberName("size"), hasMemberName("length")))
173 .bind("DependentExpr")),
176 cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
177 .bind("SizeCallExpr"),
180 // Comparison to empty string or empty constructor.
181 const auto WrongComparend
=
182 anyOf(stringLiteral(hasSize(0)),
183 userDefinedLiteral(hasLiteral(stringLiteral(hasSize(0)))),
184 cxxConstructExpr(isDefaultConstruction()),
185 cxxUnresolvedConstructExpr(argumentCountIs(0)));
186 // Match the object being compared.
189 hasOperatorName("*"),
191 expr(hasType(pointsTo(ValidContainer
))).bind("Pointee"))),
192 expr(hasType(ValidContainer
)).bind("STLObject"));
194 const auto ExcludedComparisonTypesMatcher
= qualType(anyOf(
196 cxxRecordDecl(matchers::matchesAnyListedName(ExcludedComparisonTypes
))
198 hasCanonicalType(hasDeclaration(
199 cxxRecordDecl(matchers::matchesAnyListedName(ExcludedComparisonTypes
))
200 .bind("excluded")))));
201 const auto SameExcludedComparisonTypesMatcher
=
202 qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode("excluded"))),
203 hasCanonicalType(hasDeclaration(
204 cxxRecordDecl(equalsBoundNode("excluded"))))));
208 hasAnyOperatorName("==", "!="), hasOperands(WrongComparend
, STLArg
),
209 unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher
)),
210 hasRHS(hasType(SameExcludedComparisonTypesMatcher
)))),
212 cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
217 void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult
&Result
) {
218 const auto *MemberCall
= Result
.Nodes
.getNodeAs
<Expr
>("SizeCallExpr");
219 const auto *MemberCallObject
=
220 Result
.Nodes
.getNodeAs
<Expr
>("MemberCallObject");
221 const auto *BinCmp
= Result
.Nodes
.getNodeAs
<CXXOperatorCallExpr
>("BinCmp");
222 const auto *BinCmpTempl
= Result
.Nodes
.getNodeAs
<BinaryOperator
>("BinCmp");
223 const auto *BinCmpRewritten
=
224 Result
.Nodes
.getNodeAs
<CXXRewrittenBinaryOperator
>("BinCmp");
225 const auto *BinaryOp
= Result
.Nodes
.getNodeAs
<BinaryOperator
>("SizeBinaryOp");
226 const auto *Pointee
= Result
.Nodes
.getNodeAs
<Expr
>("Pointee");
230 : (Pointee
? Pointee
: Result
.Nodes
.getNodeAs
<Expr
>("STLObject"));
232 std::string ReplacementText
= std::string(
233 Lexer::getSourceText(CharSourceRange::getTokenRange(E
->getSourceRange()),
234 *Result
.SourceManager
, getLangOpts()));
235 const auto *OpCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(E
);
236 if (isBinaryOrTernary(E
) || isa
<UnaryOperator
>(E
) ||
237 (OpCallExpr
&& (OpCallExpr
->getOperator() == OO_Star
))) {
238 ReplacementText
= "(" + ReplacementText
+ ")";
241 OpCallExpr
->getOperator() == OverloadedOperatorKind::OO_Arrow
) {
242 // This can happen if the object is a smart pointer. Don't add anything
243 // because a '->' is already there (PR#51776), just call the method.
244 ReplacementText
+= "empty()";
245 } else if (E
->getType()->isPointerType())
246 ReplacementText
+= "->empty()";
248 ReplacementText
+= ".empty()";
251 if (BinCmp
->getOperator() == OO_ExclaimEqual
) {
252 ReplacementText
= "!" + ReplacementText
;
255 FixItHint::CreateReplacement(BinCmp
->getSourceRange(), ReplacementText
);
256 } else if (BinCmpTempl
) {
257 if (BinCmpTempl
->getOpcode() == BinaryOperatorKind::BO_NE
) {
258 ReplacementText
= "!" + ReplacementText
;
260 Hint
= FixItHint::CreateReplacement(BinCmpTempl
->getSourceRange(),
262 } else if (BinCmpRewritten
) {
263 if (BinCmpRewritten
->getOpcode() == BinaryOperatorKind::BO_NE
) {
264 ReplacementText
= "!" + ReplacementText
;
266 Hint
= FixItHint::CreateReplacement(BinCmpRewritten
->getSourceRange(),
268 } else if (BinaryOp
) { // Determine the correct transformation.
269 const auto *LiteralLHS
=
270 llvm::dyn_cast
<IntegerLiteral
>(BinaryOp
->getLHS()->IgnoreImpCasts());
271 const auto *LiteralRHS
=
272 llvm::dyn_cast
<IntegerLiteral
>(BinaryOp
->getRHS()->IgnoreImpCasts());
273 const bool ContainerIsLHS
= !LiteralLHS
;
277 Value
= LiteralLHS
->getValue().getLimitedValue();
279 Value
= LiteralRHS
->getValue().getLimitedValue();
283 bool Negation
= false;
284 const auto OpCode
= BinaryOp
->getOpcode();
286 // Constant that is not handled.
290 if (Value
== 1 && (OpCode
== BinaryOperatorKind::BO_EQ
||
291 OpCode
== BinaryOperatorKind::BO_NE
))
294 // Always true, no warnings for that.
295 if ((OpCode
== BinaryOperatorKind::BO_GE
&& Value
== 0 && ContainerIsLHS
) ||
296 (OpCode
== BinaryOperatorKind::BO_LE
&& Value
== 0 && !ContainerIsLHS
))
299 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
301 if ((OpCode
== BinaryOperatorKind::BO_GT
&& ContainerIsLHS
) ||
302 (OpCode
== BinaryOperatorKind::BO_LT
&& !ContainerIsLHS
))
304 if ((OpCode
== BinaryOperatorKind::BO_LE
&& ContainerIsLHS
) ||
305 (OpCode
== BinaryOperatorKind::BO_GE
&& !ContainerIsLHS
))
309 if (OpCode
== BinaryOperatorKind::BO_NE
&& Value
== 0)
311 if ((OpCode
== BinaryOperatorKind::BO_GT
||
312 OpCode
== BinaryOperatorKind::BO_GE
) &&
315 if ((OpCode
== BinaryOperatorKind::BO_LT
||
316 OpCode
== BinaryOperatorKind::BO_LE
) &&
321 ReplacementText
= "!" + ReplacementText
;
322 Hint
= FixItHint::CreateReplacement(BinaryOp
->getSourceRange(),
326 // If there is a conversion above the size call to bool, it is safe to just
327 // replace size with empty.
328 if (const auto *UnaryOp
=
329 Result
.Nodes
.getNodeAs
<UnaryOperator
>("NegOnSize"))
330 Hint
= FixItHint::CreateReplacement(UnaryOp
->getSourceRange(),
333 Hint
= FixItHint::CreateReplacement(MemberCall
->getSourceRange(),
334 "!" + ReplacementText
);
337 auto WarnLoc
= MemberCall
? MemberCall
->getBeginLoc() : SourceLocation
{};
339 if (WarnLoc
.isValid()) {
340 auto Diag
= diag(WarnLoc
, "the 'empty' method should be used to check "
341 "for emptiness instead of %0");
342 if (const auto *SizeMethod
=
343 Result
.Nodes
.getNodeAs
<NamedDecl
>("SizeMethod"))
345 else if (const auto *DependentExpr
=
346 Result
.Nodes
.getNodeAs
<CXXDependentScopeMemberExpr
>(
348 Diag
<< DependentExpr
->getMember();
350 Diag
<< "unknown method";
353 WarnLoc
= BinCmpTempl
354 ? BinCmpTempl
->getBeginLoc()
355 : (BinCmp
? BinCmp
->getBeginLoc()
356 : (BinCmpRewritten
? BinCmpRewritten
->getBeginLoc()
357 : SourceLocation
{}));
358 diag(WarnLoc
, "the 'empty' method should be used to check "
359 "for emptiness instead of comparing to an empty object")
363 const auto *Container
= Result
.Nodes
.getNodeAs
<NamedDecl
>("container");
364 if (const auto *CTS
= dyn_cast
<ClassTemplateSpecializationDecl
>(Container
)) {
365 // The definition of the empty() method is the same for all implicit
366 // instantiations. In order to avoid duplicate or inconsistent warnings
367 // (depending on how deduplication is done), we use the same class name
368 // for all implicit instantiations of a template.
369 if (CTS
->getSpecializationKind() == TSK_ImplicitInstantiation
)
370 Container
= CTS
->getSpecializedTemplate();
372 const auto *Empty
= Result
.Nodes
.getNodeAs
<FunctionDecl
>("empty");
374 diag(Empty
->getLocation(), "method %0::empty() defined here",
379 } // namespace tidy::readability