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(anyOf(
224 hasCanonicalType(hasDeclaration(
226 .bind(
"excluded")))));
227 const auto SameExcludedComparisonTypesMatcher =
228 qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode(
"excluded"))),
229 hasCanonicalType(hasDeclaration(
230 cxxRecordDecl(equalsBoundNode(
"excluded"))))));
234 hasAnyOperatorName(
"==",
"!="), hasOperands(WrongComparend, STLArg),
235 unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher)),
236 hasRHS(hasType(SameExcludedComparisonTypesMatcher)))),
237 NotInEmptyMethodOfContainer)
243 const auto *MemberCall = Result.Nodes.getNodeAs<Expr>(
"SizeCallExpr");
244 const auto *MemberCallObject =
245 Result.Nodes.getNodeAs<Expr>(
"MemberCallObject");
246 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>(
"BinCmp");
247 const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>(
"BinCmp");
248 const auto *BinCmpRewritten =
249 Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>(
"BinCmp");
250 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>(
"SizeBinaryOp");
251 const auto *Pointee = Result.Nodes.getNodeAs<Expr>(
"Pointee");
255 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>(
"STLObject"));
257 std::string ReplacementText =
258 E->isImplicitCXXThis()
260 : std::string(Lexer::getSourceText(
261 CharSourceRange::getTokenRange(E->getSourceRange()),
262 *Result.SourceManager, getLangOpts()));
263 const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E);
265 (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) {
266 ReplacementText =
"(" + ReplacementText +
")";
269 OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) {
272 ReplacementText +=
"empty()";
273 }
else if (E->isImplicitCXXThis()) {
274 ReplacementText +=
"empty()";
275 }
else if (E->getType()->isPointerType())
276 ReplacementText +=
"->empty()";
278 ReplacementText +=
".empty()";
281 if (BinCmp->getOperator() == OO_ExclaimEqual) {
282 ReplacementText =
"!" + ReplacementText;
285 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
286 }
else if (BinCmpTempl) {
287 if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE) {
288 ReplacementText =
"!" + ReplacementText;
290 Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
292 }
else if (BinCmpRewritten) {
293 if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE) {
294 ReplacementText =
"!" + ReplacementText;
296 Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),
298 }
else if (BinaryOp) {
299 const auto *LiteralLHS =
300 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
301 const auto *LiteralRHS =
302 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts());
303 const bool ContainerIsLHS = !LiteralLHS;
307 Value = LiteralLHS->getValue().getLimitedValue();
309 Value = LiteralRHS->getValue().getLimitedValue();
313 bool Negation =
false;
314 const auto OpCode = BinaryOp->getOpcode();
320 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
321 OpCode == BinaryOperatorKind::BO_NE))
326 if ((OpCode == BinaryOperatorKind::BO_GT && !ContainerIsLHS) ||
327 (OpCode == BinaryOperatorKind::BO_LT && ContainerIsLHS) ||
328 (OpCode == BinaryOperatorKind::BO_LE && !ContainerIsLHS) ||
329 (OpCode == BinaryOperatorKind::BO_GE && ContainerIsLHS))
335 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
336 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
338 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
339 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
345 if ((OpCode == BinaryOperatorKind::BO_GT && Value == 1 &&
347 (OpCode == BinaryOperatorKind::BO_LT && Value == 1 && ContainerIsLHS) ||
348 (OpCode == BinaryOperatorKind::BO_GE && Value == 0 &&
350 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && ContainerIsLHS)) {
351 const Expr *Container = ContainerIsLHS
352 ? BinaryOp->getLHS()->IgnoreImpCasts()
353 : BinaryOp->getRHS()->IgnoreImpCasts();
354 if (Container->getType()
356 .getNonReferenceType()
357 ->isSignedIntegerType())
361 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
364 if ((OpCode == BinaryOperatorKind::BO_GT ||
365 OpCode == BinaryOperatorKind::BO_GE) &&
369 if ((OpCode == BinaryOperatorKind::BO_LT ||
370 OpCode == BinaryOperatorKind::BO_LE) &&
375 ReplacementText =
"!" + ReplacementText;
376 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
382 if (
const auto *UnaryOp =
383 Result.Nodes.getNodeAs<UnaryOperator>(
"NegOnSize"))
384 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
387 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
388 "!" + ReplacementText);
391 auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
393 if (WarnLoc.isValid()) {
394 auto Diag = diag(WarnLoc,
"the 'empty' method should be used to check "
395 "for emptiness instead of %0");
396 if (
const auto *SizeMethod =
397 Result.Nodes.getNodeAs<NamedDecl>(
"SizeMethod"))
399 else if (
const auto *DependentExpr =
400 Result.Nodes.getNodeAs<CXXDependentScopeMemberExpr>(
402 Diag << DependentExpr->getMember();
403 else if (
const auto *ME =
404 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")
420 const auto *Container = Result.Nodes.getNodeAs<NamedDecl>(
"container");
421 if (
const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) {
426 if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation)
427 Container = CTS->getSpecializedTemplate();
429 const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>(
"empty");
431 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 > matchesAnyListedName(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