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 clang::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 clang::ast_matchers::internal::Matcher<CXXMethodDecl>,
112 return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder);
115AST_POLYMORPHIC_MATCHER_P(
117 AST_POLYMORPHIC_SUPPORTED_TYPES(MemberExpr, CXXDependentScopeMemberExpr),
118 std::string, MemberName) {
119 if (
const auto *E = dyn_cast<MemberExpr>(&Node)) {
120 const IdentifierInfo *II = E->getMemberDecl()->getIdentifier();
121 return II && II->getName() == MemberName;
124 if (
const auto *E = dyn_cast<CXXDependentScopeMemberExpr>(&Node)) {
125 const IdentifierInfo *II = E->getMember().getAsIdentifierInfo();
126 return II && II->getName() == MemberName;
139 ExcludedComparisonTypes(
utils::options::parseStringList(
140 Options.get(
"ExcludedComparisonTypes",
"::std::array"))) {}
143 Options.store(Opts,
"ExcludedComparisonTypes",
148 const auto ValidContainerRecord = cxxRecordDecl(isSameOrDerivedFrom(
149 namedDecl(has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
150 hasAnyName(
"size",
"length"),
151 returns(qualType(isIntegralType(),
152 unless(booleanType()))))
154 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
155 hasName(
"empty"), returns(booleanType()))
157 .bind(
"container")));
159 const auto ValidContainerNonTemplateType =
160 qualType(hasUnqualifiedDesugaredType(
161 recordType(hasDeclaration(ValidContainerRecord))));
162 const auto ValidContainerTemplateType = qualType(hasUnqualifiedDesugaredType(
163 anyOf(templateSpecializationType(
164 hasDeclaration(classTemplateDecl(has(ValidContainerRecord)))),
165 injectedClassNameType(hasDeclaration(ValidContainerRecord)))));
167 const auto ValidContainer = qualType(
168 anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType));
170 const auto WrongUse =
171 anyOf(hasParent(binaryOperator(
172 isComparisonOperator(),
173 hasEitherOperand(anyOf(integerLiteral(equals(1)),
174 integerLiteral(equals(0)))))
175 .bind(
"SizeBinaryOp")),
176 usedInBooleanContext());
178 const auto NotInEmptyMethodOfContainer = unless(
179 forCallable(cxxMethodDecl(hasCanonicalDecl(equalsBoundNode(
"empty")))));
184 on(expr(anyOf(hasType(ValidContainer),
185 hasType(pointsTo(ValidContainer)),
186 hasType(references(ValidContainer))))
187 .bind(
"MemberCallObject")),
189 cxxMethodDecl(hasAnyName(
"size",
"length")).bind(
"SizeMethod")),
190 WrongUse, NotInEmptyMethodOfContainer)
191 .bind(
"SizeCallExpr"),
197 has(mapAnyOf(memberExpr, cxxDependentScopeMemberExpr)
200 expr(anyOf(hasType(ValidContainer),
201 hasType(pointsTo(ValidContainer)),
202 hasType(references(ValidContainer))))
203 .bind(
"MemberCallObject")),
204 anyOf(matchMemberName(
"size"), matchMemberName(
"length")))
205 .bind(
"MemberExpr")),
206 WrongUse, NotInEmptyMethodOfContainer)
207 .bind(
"SizeCallExpr"),
211 const auto WrongComparend =
212 anyOf(stringLiteral(hasSize(0)),
213 userDefinedLiteral(hasLiteral(stringLiteral(hasSize(0)))),
214 cxxConstructExpr(argumentCountIs(0)),
215 cxxUnresolvedConstructExpr(argumentCountIs(0)));
219 hasOperatorName(
"*"),
221 expr(hasType(pointsTo(ValidContainer))).bind(
"Pointee"))),
222 expr(hasType(ValidContainer)).bind(
"STLObject"));
224 const auto ExcludedComparisonTypesMatcher = qualType(
226 ExcludedComparisonTypes))
228 hasCanonicalType(hasDeclaration(
230 ExcludedComparisonTypes))
231 .bind(
"excluded")))));
232 const auto SameExcludedComparisonTypesMatcher =
233 qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode(
"excluded"))),
234 hasCanonicalType(hasDeclaration(
235 cxxRecordDecl(equalsBoundNode(
"excluded"))))));
239 hasAnyOperatorName(
"==",
"!="), hasOperands(WrongComparend, STLArg),
240 unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher)),
241 hasRHS(hasType(SameExcludedComparisonTypesMatcher)))),
242 NotInEmptyMethodOfContainer)
248 const auto *MemberCall = Result.Nodes.getNodeAs<Expr>(
"SizeCallExpr");
249 const auto *MemberCallObject =
250 Result.Nodes.getNodeAs<Expr>(
"MemberCallObject");
251 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>(
"BinCmp");
252 const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>(
"BinCmp");
253 const auto *BinCmpRewritten =
254 Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>(
"BinCmp");
255 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>(
"SizeBinaryOp");
256 const auto *Pointee = Result.Nodes.getNodeAs<Expr>(
"Pointee");
260 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>(
"STLObject"));
262 std::string ReplacementText =
263 E->isImplicitCXXThis()
265 : std::string(Lexer::getSourceText(
266 CharSourceRange::getTokenRange(E->getSourceRange()),
267 *Result.SourceManager, getLangOpts()));
268 const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E);
270 (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) {
271 ReplacementText =
"(" + ReplacementText +
")";
274 OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) {
277 ReplacementText +=
"empty()";
278 }
else if (E->isImplicitCXXThis()) {
279 ReplacementText +=
"empty()";
280 }
else if (E->getType()->isPointerType()) {
281 ReplacementText +=
"->empty()";
283 ReplacementText +=
".empty()";
287 if (BinCmp->getOperator() == OO_ExclaimEqual)
288 ReplacementText =
"!" + ReplacementText;
290 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
291 }
else if (BinCmpTempl) {
292 if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE)
293 ReplacementText =
"!" + ReplacementText;
294 Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
296 }
else if (BinCmpRewritten) {
297 if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE)
298 ReplacementText =
"!" + ReplacementText;
299 Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),
301 }
else if (BinaryOp) {
302 const auto *LiteralLHS =
303 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
304 const auto *LiteralRHS =
305 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts());
306 const bool ContainerIsLHS = !LiteralLHS;
310 Value = LiteralLHS->getValue().getLimitedValue();
312 Value = LiteralRHS->getValue().getLimitedValue();
316 bool Negation =
false;
317 const auto OpCode = BinaryOp->getOpcode();
323 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
324 OpCode == BinaryOperatorKind::BO_NE))
329 if ((OpCode == BinaryOperatorKind::BO_GT && !ContainerIsLHS) ||
330 (OpCode == BinaryOperatorKind::BO_LT && ContainerIsLHS) ||
331 (OpCode == BinaryOperatorKind::BO_LE && !ContainerIsLHS) ||
332 (OpCode == BinaryOperatorKind::BO_GE && ContainerIsLHS))
338 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
339 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
341 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
342 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
348 if ((OpCode == BinaryOperatorKind::BO_GT && Value == 1 &&
350 (OpCode == BinaryOperatorKind::BO_LT && Value == 1 && ContainerIsLHS) ||
351 (OpCode == BinaryOperatorKind::BO_GE && Value == 0 &&
353 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && ContainerIsLHS)) {
354 const Expr *Container = ContainerIsLHS
355 ? BinaryOp->getLHS()->IgnoreImpCasts()
356 : BinaryOp->getRHS()->IgnoreImpCasts();
357 if (Container->getType()
359 .getNonReferenceType()
360 ->isSignedIntegerType())
364 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
367 if ((OpCode == BinaryOperatorKind::BO_GT ||
368 OpCode == BinaryOperatorKind::BO_GE) &&
372 if ((OpCode == BinaryOperatorKind::BO_LT ||
373 OpCode == BinaryOperatorKind::BO_LE) &&
378 ReplacementText =
"!" + ReplacementText;
379 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
385 if (
const auto *UnaryOp =
386 Result.Nodes.getNodeAs<UnaryOperator>(
"NegOnSize"))
387 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
390 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
391 "!" + ReplacementText);
394 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
396 if (WarnLoc.isValid()) {
397 auto Diag = diag(WarnLoc,
"the 'empty' method should be used to check "
398 "for emptiness instead of %0");
399 if (
const auto *SizeMethod =
400 Result.Nodes.getNodeAs<NamedDecl>(
"SizeMethod"))
402 else if (
const auto *DependentExpr =
403 Result.Nodes.getNodeAs<CXXDependentScopeMemberExpr>(
405 Diag << DependentExpr->getMember();
406 else if (
const auto *ME = Result.Nodes.getNodeAs<MemberExpr>(
"MemberExpr"))
407 Diag << ME->getMemberNameInfo().getName();
409 Diag <<
"unknown method";
412 WarnLoc = BinCmpTempl
413 ? BinCmpTempl->getBeginLoc()
414 : (BinCmp ? BinCmp->getBeginLoc()
415 : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc()
416 : SourceLocation{}));
417 diag(WarnLoc,
"the 'empty' method should be used to check "
418 "for emptiness instead of comparing to an empty object")
422 const auto *Container = Result.Nodes.getNodeAs<NamedDecl>(
"container");
423 if (
const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) {
428 if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation)
429 Container = CTS->getSpecializedTemplate();
431 const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>(
"empty");
433 diag(Empty->getLocation(),
"method %0::empty() defined here",
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