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 return E->getMemberDecl()->getName() == MemberName;
122 if (
const auto *E = dyn_cast<CXXDependentScopeMemberExpr>(&Node))
123 return E->getMember().getAsString() == MemberName;
135 ExcludedComparisonTypes(
utils::options::parseStringList(
136 Options.get(
"ExcludedComparisonTypes",
"::std::array"))) {}
139 Options.store(Opts,
"ExcludedComparisonTypes",
144 const auto ValidContainerRecord = cxxRecordDecl(isSameOrDerivedFrom(
145 namedDecl(has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
146 hasAnyName(
"size",
"length"),
147 returns(qualType(isIntegralType(),
148 unless(booleanType()))))
150 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
151 hasName(
"empty"), returns(booleanType()))
153 .bind(
"container")));
155 const auto ValidContainerNonTemplateType =
156 qualType(hasUnqualifiedDesugaredType(
157 recordType(hasDeclaration(ValidContainerRecord))));
158 const auto ValidContainerTemplateType = qualType(hasUnqualifiedDesugaredType(
159 anyOf(templateSpecializationType(
160 hasDeclaration(classTemplateDecl(has(ValidContainerRecord)))),
161 injectedClassNameType(hasDeclaration(ValidContainerRecord)))));
163 const auto ValidContainer = qualType(
164 anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType));
166 const auto WrongUse =
167 anyOf(hasParent(binaryOperator(
168 isComparisonOperator(),
169 hasEitherOperand(anyOf(integerLiteral(equals(1)),
170 integerLiteral(equals(0)))))
171 .bind(
"SizeBinaryOp")),
172 usedInBooleanContext());
174 const auto NotInEmptyMethodOfContainer = unless(
175 forCallable(cxxMethodDecl(hasCanonicalDecl(equalsBoundNode(
"empty")))));
180 on(expr(anyOf(hasType(ValidContainer),
181 hasType(pointsTo(ValidContainer)),
182 hasType(references(ValidContainer))))
183 .bind(
"MemberCallObject")),
185 cxxMethodDecl(hasAnyName(
"size",
"length")).bind(
"SizeMethod")),
186 WrongUse, NotInEmptyMethodOfContainer)
187 .bind(
"SizeCallExpr"),
193 has(mapAnyOf(memberExpr, cxxDependentScopeMemberExpr)
196 expr(anyOf(hasType(ValidContainer),
197 hasType(pointsTo(ValidContainer)),
198 hasType(references(ValidContainer))))
199 .bind(
"MemberCallObject")),
200 anyOf(matchMemberName(
"size"), matchMemberName(
"length")))
201 .bind(
"MemberExpr")),
202 WrongUse, NotInEmptyMethodOfContainer)
203 .bind(
"SizeCallExpr"),
207 const auto WrongComparend =
208 anyOf(stringLiteral(hasSize(0)),
209 userDefinedLiteral(hasLiteral(stringLiteral(hasSize(0)))),
210 cxxConstructExpr(argumentCountIs(0)),
211 cxxUnresolvedConstructExpr(argumentCountIs(0)));
215 hasOperatorName(
"*"),
217 expr(hasType(pointsTo(ValidContainer))).bind(
"Pointee"))),
218 expr(hasType(ValidContainer)).bind(
"STLObject"));
220 const auto ExcludedComparisonTypesMatcher = qualType(
222 ExcludedComparisonTypes))
224 hasCanonicalType(hasDeclaration(
226 ExcludedComparisonTypes))
227 .bind(
"excluded")))));
228 const auto SameExcludedComparisonTypesMatcher =
229 qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode(
"excluded"))),
230 hasCanonicalType(hasDeclaration(
231 cxxRecordDecl(equalsBoundNode(
"excluded"))))));
235 hasAnyOperatorName(
"==",
"!="), hasOperands(WrongComparend, STLArg),
236 unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher)),
237 hasRHS(hasType(SameExcludedComparisonTypesMatcher)))),
238 NotInEmptyMethodOfContainer)
244 const auto *MemberCall = Result.Nodes.getNodeAs<Expr>(
"SizeCallExpr");
245 const auto *MemberCallObject =
246 Result.Nodes.getNodeAs<Expr>(
"MemberCallObject");
247 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>(
"BinCmp");
248 const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>(
"BinCmp");
249 const auto *BinCmpRewritten =
250 Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>(
"BinCmp");
251 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>(
"SizeBinaryOp");
252 const auto *Pointee = Result.Nodes.getNodeAs<Expr>(
"Pointee");
256 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>(
"STLObject"));
258 std::string ReplacementText =
259 E->isImplicitCXXThis()
261 : std::string(Lexer::getSourceText(
262 CharSourceRange::getTokenRange(E->getSourceRange()),
263 *Result.SourceManager, getLangOpts()));
264 const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E);
266 (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) {
267 ReplacementText =
"(" + ReplacementText +
")";
270 OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) {
273 ReplacementText +=
"empty()";
274 }
else if (E->isImplicitCXXThis()) {
275 ReplacementText +=
"empty()";
276 }
else if (E->getType()->isPointerType())
277 ReplacementText +=
"->empty()";
279 ReplacementText +=
".empty()";
282 if (BinCmp->getOperator() == OO_ExclaimEqual)
283 ReplacementText =
"!" + ReplacementText;
285 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
286 }
else if (BinCmpTempl) {
287 if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE)
288 ReplacementText =
"!" + ReplacementText;
289 Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
291 }
else if (BinCmpRewritten) {
292 if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE)
293 ReplacementText =
"!" + ReplacementText;
294 Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),
296 }
else if (BinaryOp) {
297 const auto *LiteralLHS =
298 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
299 const auto *LiteralRHS =
300 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts());
301 const bool ContainerIsLHS = !LiteralLHS;
305 Value = LiteralLHS->getValue().getLimitedValue();
307 Value = LiteralRHS->getValue().getLimitedValue();
311 bool Negation =
false;
312 const auto OpCode = BinaryOp->getOpcode();
318 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
319 OpCode == BinaryOperatorKind::BO_NE))
324 if ((OpCode == BinaryOperatorKind::BO_GT && !ContainerIsLHS) ||
325 (OpCode == BinaryOperatorKind::BO_LT && ContainerIsLHS) ||
326 (OpCode == BinaryOperatorKind::BO_LE && !ContainerIsLHS) ||
327 (OpCode == BinaryOperatorKind::BO_GE && ContainerIsLHS))
333 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
334 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
336 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
337 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
343 if ((OpCode == BinaryOperatorKind::BO_GT && Value == 1 &&
345 (OpCode == BinaryOperatorKind::BO_LT && Value == 1 && ContainerIsLHS) ||
346 (OpCode == BinaryOperatorKind::BO_GE && Value == 0 &&
348 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && ContainerIsLHS)) {
349 const Expr *Container = ContainerIsLHS
350 ? BinaryOp->getLHS()->IgnoreImpCasts()
351 : BinaryOp->getRHS()->IgnoreImpCasts();
352 if (Container->getType()
354 .getNonReferenceType()
355 ->isSignedIntegerType())
359 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
362 if ((OpCode == BinaryOperatorKind::BO_GT ||
363 OpCode == BinaryOperatorKind::BO_GE) &&
367 if ((OpCode == BinaryOperatorKind::BO_LT ||
368 OpCode == BinaryOperatorKind::BO_LE) &&
373 ReplacementText =
"!" + ReplacementText;
374 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
380 if (
const auto *UnaryOp =
381 Result.Nodes.getNodeAs<UnaryOperator>(
"NegOnSize"))
382 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
385 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
386 "!" + ReplacementText);
389 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
391 if (WarnLoc.isValid()) {
392 auto Diag = diag(WarnLoc,
"the 'empty' method should be used to check "
393 "for emptiness instead of %0");
394 if (
const auto *SizeMethod =
395 Result.Nodes.getNodeAs<NamedDecl>(
"SizeMethod"))
397 else if (
const auto *DependentExpr =
398 Result.Nodes.getNodeAs<CXXDependentScopeMemberExpr>(
400 Diag << DependentExpr->getMember();
401 else if (
const auto *ME = Result.Nodes.getNodeAs<MemberExpr>(
"MemberExpr"))
402 Diag << ME->getMemberNameInfo().getName();
404 Diag <<
"unknown method";
407 WarnLoc = BinCmpTempl
408 ? BinCmpTempl->getBeginLoc()
409 : (BinCmp ? BinCmp->getBeginLoc()
410 : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc()
411 : SourceLocation{}));
412 diag(WarnLoc,
"the 'empty' method should be used to check "
413 "for emptiness instead of comparing to an empty object")
417 const auto *Container = Result.Nodes.getNodeAs<NamedDecl>(
"container");
418 if (
const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) {
423 if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation)
424 Container = CTS->getSpecializedTemplate();
426 const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>(
"empty");
428 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