12#include "clang/AST/ASTContext.h"
13#include "clang/ASTMatchers/ASTMatchers.h"
14#include "clang/Lex/Lexer.h"
15#include "llvm/ADT/StringRef.h"
23AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam,
24 AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr,
26 ast_matchers::internal::Matcher<Expr>, ArgMatcher,
27 ast_matchers::internal::Matcher<ParmVarDecl>,
29 ast_matchers::internal::BoundNodesTreeBuilder Result;
33 ast_matchers::internal::BoundNodesTreeBuilder Matches;
34 unsigned ArgIndex = cxxOperatorCallExpr(callee(cxxMethodDecl()))
35 .matches(Node, Finder, &Matches)
39 for (; ArgIndex < Node.getNumArgs(); ++ArgIndex) {
40 ast_matchers::internal::BoundNodesTreeBuilder ArgMatches(*Builder);
41 if (ArgMatcher.matches(*(Node.getArg(ArgIndex)->IgnoreParenCasts()), Finder,
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);
60 const char *ExprName =
"__booleanContextExpr";
62 expr(expr().bind(ExprName),
64 mapAnyOf(varDecl, fieldDecl).with(hasType(booleanType()))),
65 hasParent(cxxConstructorDecl(
66 hasAnyConstructorInitializer(cxxCtorInitializer(
67 withInitializer(expr(equalsBoundNode(ExprName))),
68 forField(hasType(booleanType())))))),
70 explicitCastExpr(hasDestinationType(booleanType())),
71 mapAnyOf(ifStmt, doStmt, whileStmt, forStmt,
73 .with(hasCondition(expr(equalsBoundNode(ExprName)))),
74 parenListExpr(hasParent(varDecl(hasType(booleanType())))),
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();
93 return Node->isIntegralType(Finder->getASTContext());
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)
104 if (
const Expr *CookedLiteral = Node.getCookedLiteral())
105 return InnerMatcher.matches(*CookedLiteral, Finder, Builder);
110 ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) {
111 return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder);
114AST_POLYMORPHIC_MATCHER_P(
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;
123 if (
const auto *E = dyn_cast<CXXDependentScopeMemberExpr>(&Node)) {
124 const IdentifierInfo *II = E->getMember().getAsIdentifierInfo();
125 return II && II->getName() == MemberName;
138 ExcludedComparisonTypes(
utils::options::parseStringList(
139 Options.get(
"ExcludedComparisonTypes",
"::std::array"))) {}
142 Options.store(Opts,
"ExcludedComparisonTypes",
147 const auto ValidContainerRecord =
149 isSameOrDerivedFrom(namedDecl(
150 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
151 hasAnyName(
"size",
"length"),
152 returns(qualType(isIntegralType(),
153 unless(booleanType()))))
155 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
156 hasName(
"empty"), returns(booleanType()))
158 .bind(
"ContainerDecl");
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)))));
168 const auto ValidContainer = qualType(
169 anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType));
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());
179 const auto NotInEmptyMethodOfContainer = unless(
180 forCallable(cxxMethodDecl(hasCanonicalDecl(equalsBoundNode(
"empty")))));
185 on(expr(anyOf(hasType(ValidContainer),
186 hasType(pointsTo(ValidContainer)),
187 hasType(references(ValidContainer))))
188 .bind(
"MemberCallObject")),
190 cxxMethodDecl(hasAnyName(
"size",
"length")).bind(
"SizeMethod")),
191 WrongUse, NotInEmptyMethodOfContainer)
192 .bind(
"SizeCallExpr"),
198 has(mapAnyOf(memberExpr, cxxDependentScopeMemberExpr)
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"),
212 const auto WrongComparend =
213 anyOf(stringLiteral(hasSize(0)),
214 userDefinedLiteral(hasLiteral(stringLiteral(hasSize(0)))),
215 cxxConstructExpr(argumentCountIs(0)),
216 cxxUnresolvedConstructExpr(argumentCountIs(0)));
220 hasOperatorName(
"*"),
222 expr(hasType(pointsTo(ValidContainer))).bind(
"Pointee"))),
223 expr(hasType(ValidContainer)).bind(
"STLObject"));
225 const auto ExcludedComparisonTypesMatcher = qualType(
227 ExcludedComparisonTypes))
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"))))));
240 hasAnyOperatorName(
"==",
"!="), hasOperands(WrongComparend, STLArg),
241 unless(hasEitherOperand(cxxConstructExpr(
243 unless(hasType(qualType(hasCanonicalType(hasDeclaration(
247 namedDecl(equalsBoundNode(
"ContainerDecl")))))))))),
248 unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher)),
249 hasRHS(hasType(SameExcludedComparisonTypesMatcher)))),
250 NotInEmptyMethodOfContainer)
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");
268 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>(
"STLObject"));
270 std::string ReplacementText =
271 E->isImplicitCXXThis()
273 : std::string(Lexer::getSourceText(
274 CharSourceRange::getTokenRange(E->getSourceRange()),
275 *Result.SourceManager, getLangOpts()));
276 const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E);
278 (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) {
279 ReplacementText =
"(" + ReplacementText +
")";
282 OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) {
285 ReplacementText +=
"empty()";
286 }
else if (E->isImplicitCXXThis()) {
287 ReplacementText +=
"empty()";
288 }
else if (E->getType()->isPointerType()) {
289 ReplacementText +=
"->empty()";
291 ReplacementText +=
".empty()";
295 if (BinCmp->getOperator() == OO_ExclaimEqual)
296 ReplacementText =
"!" + ReplacementText;
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(),
304 }
else if (BinCmpRewritten) {
305 if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE)
306 ReplacementText =
"!" + ReplacementText;
307 Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),
309 }
else if (BinaryOp) {
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;
318 Value = LiteralLHS->getValue().getLimitedValue();
320 Value = LiteralRHS->getValue().getLimitedValue();
324 bool Negation =
false;
325 const auto OpCode = BinaryOp->getOpcode();
331 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
332 OpCode == BinaryOperatorKind::BO_NE))
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))
346 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
347 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
349 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
350 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
356 if ((OpCode == BinaryOperatorKind::BO_GT && Value == 1 &&
358 (OpCode == BinaryOperatorKind::BO_LT && Value == 1 && ContainerIsLHS) ||
359 (OpCode == BinaryOperatorKind::BO_GE && Value == 0 &&
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()
367 .getNonReferenceType()
368 ->isSignedIntegerType())
372 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
375 if ((OpCode == BinaryOperatorKind::BO_GT ||
376 OpCode == BinaryOperatorKind::BO_GE) &&
380 if ((OpCode == BinaryOperatorKind::BO_LT ||
381 OpCode == BinaryOperatorKind::BO_LE) &&
386 ReplacementText =
"!" + ReplacementText;
387 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
393 if (
const auto *UnaryOp =
394 Result.Nodes.getNodeAs<UnaryOperator>(
"NegOnSize"))
395 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
398 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
399 "!" + ReplacementText);
402 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
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"))
410 else if (
const auto *DependentExpr =
411 Result.Nodes.getNodeAs<CXXDependentScopeMemberExpr>(
413 Diag << DependentExpr->getMember();
414 else if (
const auto *ME = Result.Nodes.getNodeAs<MemberExpr>(
"MemberExpr"))
415 Diag << ME->getMemberNameInfo().getName();
417 Diag <<
"unknown method";
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")
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)
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
bool isBinaryOrTernary(const Expr *E)
llvm::StringMap< ClangTidyValue > OptionMap