10 #include "../utils/ASTUtils.h"
11 #include "../utils/Matchers.h"
12 #include "clang/AST/ASTContext.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/Lex/Lexer.h"
15 #include "llvm/ADT/StringRef.h"
17 using namespace clang::ast_matchers;
21 namespace readability {
25 ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef
Name,
35 const auto ValidContainer = cxxRecordDecl(isSameOrDerivedFrom(
38 isConst(), parameterCountIs(0), isPublic(), hasName(
"size"),
39 returns(qualType(isInteger(), unless(booleanType()))))
41 has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
42 hasName(
"empty"), returns(booleanType()))
46 const auto WrongUse = anyOf(
47 hasParent(binaryOperator(
48 matchers::isComparisonOperator(),
49 hasEitherOperand(ignoringImpCasts(anyOf(
50 integerLiteral(equals(1)), integerLiteral(equals(0))))))
51 .bind(
"SizeBinaryOp")),
52 hasParent(implicitCastExpr(
53 hasImplicitDestinationType(booleanType()),
55 hasParent(unaryOperator(hasOperatorName(
"!")).bind(
"NegOnSize")),
57 hasParent(explicitCastExpr(hasDestinationType(booleanType()))));
60 cxxMemberCallExpr(on(expr(anyOf(hasType(ValidContainer),
61 hasType(pointsTo(ValidContainer)),
62 hasType(references(ValidContainer))))),
63 callee(cxxMethodDecl(hasName(
"size"))), WrongUse,
64 unless(hasAncestor(cxxMethodDecl(
65 ofClass(equalsBoundNode(
"container"))))))
66 .bind(
"SizeCallExpr"),
70 const auto DefaultConstructor = cxxConstructExpr(
71 hasDeclaration(cxxConstructorDecl(isDefaultConstructor())));
73 const auto WrongComparend = anyOf(
74 ignoringImpCasts(stringLiteral(hasSize(0))),
75 ignoringImpCasts(cxxBindTemporaryExpr(has(DefaultConstructor))),
76 ignoringImplicit(DefaultConstructor),
78 hasDeclaration(cxxConstructorDecl(isCopyConstructor())),
79 has(expr(ignoringImpCasts(DefaultConstructor)))),
81 hasDeclaration(cxxConstructorDecl(isMoveConstructor())),
82 has(expr(ignoringImpCasts(DefaultConstructor)))));
88 expr(hasType(pointsTo(ValidContainer))).bind(
"Pointee"))),
89 expr(hasType(ValidContainer)).bind(
"STLObject"));
92 anyOf(hasOverloadedOperatorName(
"=="),
93 hasOverloadedOperatorName(
"!=")),
94 anyOf(allOf(hasArgument(0, WrongComparend), hasArgument(1, STLArg)),
95 allOf(hasArgument(0, STLArg), hasArgument(1, WrongComparend))),
97 cxxMethodDecl(ofClass(equalsBoundNode(
"container"))))))
103 const auto *MemberCall =
104 Result.Nodes.getNodeAs<CXXMemberCallExpr>(
"SizeCallExpr");
105 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>(
"BinCmp");
106 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>(
"SizeBinaryOp");
107 const auto *Pointee = Result.Nodes.getNodeAs<Expr>(
"Pointee");
110 ? MemberCall->getImplicitObjectArgument()
111 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>(
"STLObject"));
113 std::string ReplacementText =
114 Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()),
118 ReplacementText =
"(" + ReplacementText +
")";
120 if (E->getType()->isPointerType())
121 ReplacementText +=
"->empty()";
123 ReplacementText +=
".empty()";
126 if (BinCmp->getOperator() == OO_ExclaimEqual) {
127 ReplacementText =
"!" + ReplacementText;
130 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
131 }
else if (BinaryOp) {
132 bool Negation =
false;
133 const bool ContainerIsLHS =
134 !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
135 const auto OpCode = BinaryOp->getOpcode();
137 if (ContainerIsLHS) {
138 if (
const auto *Literal = llvm::dyn_cast<IntegerLiteral>(
139 BinaryOp->getRHS()->IgnoreImpCasts()))
140 Value = Literal->getValue().getLimitedValue();
145 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts())
154 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
155 OpCode == BinaryOperatorKind::BO_NE))
159 if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
160 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
165 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
166 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
168 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
169 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
173 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
175 if ((OpCode == BinaryOperatorKind::BO_GT ||
176 OpCode == BinaryOperatorKind::BO_GE) &&
179 if ((OpCode == BinaryOperatorKind::BO_LT ||
180 OpCode == BinaryOperatorKind::BO_LE) &&
185 ReplacementText =
"!" + ReplacementText;
186 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
192 if (
const auto *UnaryOp =
193 Result.Nodes.getNodeAs<UnaryOperator>(
"NegOnSize"))
194 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
197 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
198 "!" + ReplacementText);
202 diag(MemberCall->getLocStart(),
203 "the 'empty' method should be used to check "
204 "for emptiness instead of 'size'")
207 diag(BinCmp->getLocStart(),
208 "the 'empty' method should be used to check "
209 "for emptiness instead of comparing to an empty object")
213 const auto *Container = Result.Nodes.getNodeAs<NamedDecl>(
"container");
214 const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>(
"empty");
216 diag(Empty->getLocation(),
"method %0::empty() defined here",
bool IsBinaryOrTernary(const Expr *E)
LangOptions getLangOpts() const
Returns the language options from the context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
std::unique_ptr< ast_matchers::MatchFinder > Finder
Base class for all clang-tidy checks.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
ClangTidyContext & Context
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.