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 = cxxRecordDecl(isSameOrDerivedFrom(namedDecl(
149 isConst(), parameterCountIs(0), isPublic(),
150 hasAnyName(
"size",
"length"),
151 returns(qualType(isIntegralType(), unless(booleanType()))))
153 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
154 hasName(
"empty"), returns(booleanType()))
157 const auto ValidContainerNonTemplateType =
158 qualType(hasUnqualifiedDesugaredType(
159 recordType(hasDeclaration(ValidContainerRecord))));
160 const auto ValidContainerTemplateType = qualType(hasUnqualifiedDesugaredType(
161 anyOf(templateSpecializationType(
162 hasDeclaration(classTemplateDecl(has(ValidContainerRecord)))),
163 injectedClassNameType(hasDeclaration(ValidContainerRecord)))));
165 const auto ValidContainer = qualType(
166 anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType));
168 const auto WrongUse =
169 anyOf(hasParent(binaryOperator(
170 isComparisonOperator(),
171 hasEitherOperand(anyOf(integerLiteral(equals(1)),
172 integerLiteral(equals(0)))))
173 .bind(
"SizeBinaryOp")),
174 usedInBooleanContext());
176 const auto NotInEmptyMethodOfContainer = unless(
177 forCallable(cxxMethodDecl(hasCanonicalDecl(equalsBoundNode(
"empty")))));
182 on(expr(anyOf(hasType(ValidContainer),
183 hasType(pointsTo(ValidContainer)),
184 hasType(references(ValidContainer))))
185 .bind(
"MemberCallObject")),
187 cxxMethodDecl(hasAnyName(
"size",
"length")).bind(
"SizeMethod")),
188 WrongUse, NotInEmptyMethodOfContainer)
189 .bind(
"SizeCallExpr"),
195 has(mapAnyOf(memberExpr, cxxDependentScopeMemberExpr)
198 expr(anyOf(hasType(ValidContainer),
199 hasType(pointsTo(ValidContainer)),
200 hasType(references(ValidContainer))))
201 .bind(
"MemberCallObject")),
202 anyOf(matchMemberName(
"size"), matchMemberName(
"length")))
203 .bind(
"MemberExpr")),
204 WrongUse, NotInEmptyMethodOfContainer)
205 .bind(
"SizeCallExpr"),
209 const auto WrongComparend =
210 anyOf(stringLiteral(hasSize(0)),
211 userDefinedLiteral(hasLiteral(stringLiteral(hasSize(0)))),
212 cxxConstructExpr(argumentCountIs(0)),
213 cxxUnresolvedConstructExpr(argumentCountIs(0)));
217 hasOperatorName(
"*"),
219 expr(hasType(pointsTo(ValidContainer))).bind(
"Pointee"))),
220 expr(hasType(ValidContainer)).bind(
"STLObject"));
222 const auto ExcludedComparisonTypesMatcher = qualType(
224 ExcludedComparisonTypes))
226 hasCanonicalType(hasDeclaration(
228 ExcludedComparisonTypes))
229 .bind(
"excluded")))));
230 const auto SameExcludedComparisonTypesMatcher =
231 qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode(
"excluded"))),
232 hasCanonicalType(hasDeclaration(
233 cxxRecordDecl(equalsBoundNode(
"excluded"))))));
237 hasAnyOperatorName(
"==",
"!="), hasOperands(WrongComparend, STLArg),
238 unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher)),
239 hasRHS(hasType(SameExcludedComparisonTypesMatcher)))),
240 NotInEmptyMethodOfContainer)
246 const auto *MemberCall = Result.Nodes.getNodeAs<Expr>(
"SizeCallExpr");
247 const auto *MemberCallObject =
248 Result.Nodes.getNodeAs<Expr>(
"MemberCallObject");
249 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>(
"BinCmp");
250 const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>(
"BinCmp");
251 const auto *BinCmpRewritten =
252 Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>(
"BinCmp");
253 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>(
"SizeBinaryOp");
254 const auto *Pointee = Result.Nodes.getNodeAs<Expr>(
"Pointee");
258 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>(
"STLObject"));
260 std::string ReplacementText =
261 E->isImplicitCXXThis()
263 : std::string(Lexer::getSourceText(
264 CharSourceRange::getTokenRange(E->getSourceRange()),
265 *Result.SourceManager, getLangOpts()));
266 const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E);
268 (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) {
269 ReplacementText =
"(" + ReplacementText +
")";
272 OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) {
275 ReplacementText +=
"empty()";
276 }
else if (E->isImplicitCXXThis()) {
277 ReplacementText +=
"empty()";
278 }
else if (E->getType()->isPointerType()) {
279 ReplacementText +=
"->empty()";
281 ReplacementText +=
".empty()";
285 if (BinCmp->getOperator() == OO_ExclaimEqual)
286 ReplacementText =
"!" + ReplacementText;
288 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
289 }
else if (BinCmpTempl) {
290 if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE)
291 ReplacementText =
"!" + ReplacementText;
292 Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
294 }
else if (BinCmpRewritten) {
295 if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE)
296 ReplacementText =
"!" + ReplacementText;
297 Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),
299 }
else if (BinaryOp) {
300 const auto *LiteralLHS =
301 dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
302 const auto *LiteralRHS =
303 dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts());
304 const bool ContainerIsLHS = !LiteralLHS;
308 Value = LiteralLHS->getValue().getLimitedValue();
310 Value = LiteralRHS->getValue().getLimitedValue();
314 bool Negation =
false;
315 const auto OpCode = BinaryOp->getOpcode();
321 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
322 OpCode == BinaryOperatorKind::BO_NE))
327 if ((OpCode == BinaryOperatorKind::BO_GT && !ContainerIsLHS) ||
328 (OpCode == BinaryOperatorKind::BO_LT && ContainerIsLHS) ||
329 (OpCode == BinaryOperatorKind::BO_LE && !ContainerIsLHS) ||
330 (OpCode == BinaryOperatorKind::BO_GE && ContainerIsLHS))
336 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
337 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
339 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
340 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
346 if ((OpCode == BinaryOperatorKind::BO_GT && Value == 1 &&
348 (OpCode == BinaryOperatorKind::BO_LT && Value == 1 && ContainerIsLHS) ||
349 (OpCode == BinaryOperatorKind::BO_GE && Value == 0 &&
351 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && ContainerIsLHS)) {
352 const Expr *Container = ContainerIsLHS
353 ? BinaryOp->getLHS()->IgnoreImpCasts()
354 : BinaryOp->getRHS()->IgnoreImpCasts();
355 if (Container->getType()
357 .getNonReferenceType()
358 ->isSignedIntegerType())
362 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
365 if ((OpCode == BinaryOperatorKind::BO_GT ||
366 OpCode == BinaryOperatorKind::BO_GE) &&
370 if ((OpCode == BinaryOperatorKind::BO_LT ||
371 OpCode == BinaryOperatorKind::BO_LE) &&
376 ReplacementText =
"!" + ReplacementText;
377 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
383 if (
const auto *UnaryOp =
384 Result.Nodes.getNodeAs<UnaryOperator>(
"NegOnSize"))
385 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
388 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
389 "!" + ReplacementText);
392 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
394 if (WarnLoc.isValid()) {
395 auto Diag = diag(WarnLoc,
"the 'empty' method should be used to check "
396 "for emptiness instead of %0");
397 if (
const auto *SizeMethod =
398 Result.Nodes.getNodeAs<NamedDecl>(
"SizeMethod"))
400 else if (
const auto *DependentExpr =
401 Result.Nodes.getNodeAs<CXXDependentScopeMemberExpr>(
403 Diag << DependentExpr->getMember();
404 else if (
const auto *ME = Result.Nodes.getNodeAs<MemberExpr>(
"MemberExpr"))
405 Diag << ME->getMemberNameInfo().getName();
407 Diag <<
"unknown method";
410 WarnLoc = BinCmpTempl
411 ? BinCmpTempl->getBeginLoc()
412 : (BinCmp ? BinCmp->getBeginLoc()
413 : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc()
414 : SourceLocation{}));
415 diag(WarnLoc,
"the 'empty' method should be used to check "
416 "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