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")))));
182 const auto ValidContainerExpr =
183 expr(anyOf(hasType(ValidContainer), hasType(pointsTo(ValidContainer)),
184 hasType(references(ValidContainer))))
185 .bind(
"MemberCallObject");
189 argumentCountIs(0), on(ValidContainerExpr),
191 cxxMethodDecl(hasAnyName(
"size",
"length")).bind(
"SizeMethod")),
192 WrongUse, NotInEmptyMethodOfContainer)
193 .bind(
"SizeCallExpr"),
197 callExpr(argumentCountIs(0),
198 has(mapAnyOf(memberExpr, cxxDependentScopeMemberExpr)
199 .with(hasObjectExpression(ValidContainerExpr),
200 anyOf(matchMemberName(
"size"),
201 matchMemberName(
"length")))
202 .bind(
"MemberExpr")),
203 WrongUse, NotInEmptyMethodOfContainer)
204 .bind(
"SizeCallExpr"),
210 callExpr(argumentCountIs(1),
211 callee(functionDecl(hasName(
"::std::size")).bind(
"SizeMethod")),
212 hasArgument(0, ValidContainerExpr), WrongUse,
213 NotInEmptyMethodOfContainer)
214 .bind(
"SizeCallExpr"),
218 const auto WrongComparend =
219 anyOf(stringLiteral(hasSize(0)),
220 userDefinedLiteral(hasLiteral(stringLiteral(hasSize(0)))),
221 cxxConstructExpr(argumentCountIs(0)),
222 cxxUnresolvedConstructExpr(argumentCountIs(0)));
226 hasOperatorName(
"*"),
228 expr(hasType(pointsTo(ValidContainer))).bind(
"Pointee"))),
229 expr(hasType(ValidContainer)).bind(
"STLObject"));
231 const auto ExcludedComparisonTypesMatcher = qualType(
233 ExcludedComparisonTypes))
235 hasCanonicalType(hasDeclaration(
237 ExcludedComparisonTypes))
238 .bind(
"excluded")))));
239 const auto SameExcludedComparisonTypesMatcher =
240 qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode(
"excluded"))),
241 hasCanonicalType(hasDeclaration(
242 cxxRecordDecl(equalsBoundNode(
"excluded"))))));
246 hasAnyOperatorName(
"==",
"!="), hasOperands(WrongComparend, STLArg),
247 unless(hasEitherOperand(cxxConstructExpr(
249 unless(hasType(qualType(hasCanonicalType(hasDeclaration(
253 namedDecl(equalsBoundNode(
"ContainerDecl")))))))))),
254 unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher)),
255 hasRHS(hasType(SameExcludedComparisonTypesMatcher)))),
256 NotInEmptyMethodOfContainer)
262 const auto *MemberCall = Result.Nodes.getNodeAs<Expr>(
"SizeCallExpr");
263 const auto *MemberCallObject =
264 Result.Nodes.getNodeAs<Expr>(
"MemberCallObject");
265 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>(
"BinCmp");
266 const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>(
"BinCmp");
267 const auto *BinCmpRewritten =
268 Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>(
"BinCmp");
269 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>(
"SizeBinaryOp");
270 const auto *Pointee = Result.Nodes.getNodeAs<Expr>(
"Pointee");
274 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>(
"STLObject"));
276 std::string ReplacementText =
277 E->isImplicitCXXThis()
279 : std::string(Lexer::getSourceText(
280 CharSourceRange::getTokenRange(E->getSourceRange()),
281 *Result.SourceManager, getLangOpts()));
282 const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E);
284 (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) {
285 ReplacementText =
"(" + ReplacementText +
")";
288 OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) {
291 ReplacementText +=
"empty()";
292 }
else if (E->isImplicitCXXThis()) {
293 ReplacementText +=
"empty()";
294 }
else if (E->getType()->isPointerType()) {
295 ReplacementText +=
"->empty()";
297 ReplacementText +=
".empty()";
301 if (BinCmp->getOperator() == OO_ExclaimEqual)
302 ReplacementText =
"!" + ReplacementText;
304 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
305 }
else if (BinCmpTempl) {
306 if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE)
307 ReplacementText =
"!" + ReplacementText;
308 Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
310 }
else if (BinCmpRewritten) {
311 if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE)
312 ReplacementText =
"!" + ReplacementText;
313 Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),
315 }
else if (BinaryOp) {
316 const auto *LiteralLHS =
317 dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
318 const auto *LiteralRHS =
319 dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts());
320 const bool ContainerIsLHS = !LiteralLHS;
324 Value = LiteralLHS->getValue().getLimitedValue();
326 Value = LiteralRHS->getValue().getLimitedValue();
330 bool Negation =
false;
331 const auto OpCode = BinaryOp->getOpcode();
337 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
338 OpCode == BinaryOperatorKind::BO_NE))
343 if ((OpCode == BinaryOperatorKind::BO_GT && !ContainerIsLHS) ||
344 (OpCode == BinaryOperatorKind::BO_LT && ContainerIsLHS) ||
345 (OpCode == BinaryOperatorKind::BO_LE && !ContainerIsLHS) ||
346 (OpCode == BinaryOperatorKind::BO_GE && ContainerIsLHS))
352 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
353 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
355 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
356 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
362 if ((OpCode == BinaryOperatorKind::BO_GT && Value == 1 &&
364 (OpCode == BinaryOperatorKind::BO_LT && Value == 1 && ContainerIsLHS) ||
365 (OpCode == BinaryOperatorKind::BO_GE && Value == 0 &&
367 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && ContainerIsLHS)) {
368 const Expr *Container = ContainerIsLHS
369 ? BinaryOp->getLHS()->IgnoreImpCasts()
370 : BinaryOp->getRHS()->IgnoreImpCasts();
371 if (Container->getType()
373 .getNonReferenceType()
374 ->isSignedIntegerType())
378 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
381 if ((OpCode == BinaryOperatorKind::BO_GT ||
382 OpCode == BinaryOperatorKind::BO_GE) &&
386 if ((OpCode == BinaryOperatorKind::BO_LT ||
387 OpCode == BinaryOperatorKind::BO_LE) &&
392 ReplacementText =
"!" + ReplacementText;
393 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
399 if (
const auto *UnaryOp =
400 Result.Nodes.getNodeAs<UnaryOperator>(
"NegOnSize"))
401 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
404 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
405 "!" + ReplacementText);
408 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
410 if (WarnLoc.isValid()) {
411 auto Diag = diag(WarnLoc,
"the 'empty' method should be used to check "
412 "for emptiness instead of %0");
413 if (
const auto *SizeMethod =
414 Result.Nodes.getNodeAs<NamedDecl>(
"SizeMethod"))
415 Diag << SizeMethod->getDeclName();
416 else if (
const auto *DependentExpr =
417 Result.Nodes.getNodeAs<CXXDependentScopeMemberExpr>(
419 Diag << DependentExpr->getMember();
420 else if (
const auto *ME = Result.Nodes.getNodeAs<MemberExpr>(
"MemberExpr"))
421 Diag << ME->getMemberNameInfo().getName();
423 Diag <<
"unknown method";
426 WarnLoc = BinCmpTempl
427 ? BinCmpTempl->getBeginLoc()
428 : (BinCmp ? BinCmp->getBeginLoc()
429 : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc()
430 : SourceLocation{}));
431 diag(WarnLoc,
"the 'empty' method should be used to check "
432 "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