9 #include "../utils/ASTUtils.h"
10 #include "../utils/Matchers.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchers.h"
13 #include "clang/Lex/Lexer.h"
14 #include "llvm/ADT/StringRef.h"
20 namespace readability {
24 ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef
Name,
29 const auto ValidContainer = qualType(hasUnqualifiedDesugaredType(
30 recordType(hasDeclaration(cxxRecordDecl(isSameOrDerivedFrom(
33 isConst(), parameterCountIs(0),
isPublic(),
35 returns(qualType(isInteger(), unless(booleanType()))))
37 has(cxxMethodDecl(isConst(), parameterCountIs(0),
isPublic(),
38 hasName(
"empty"), returns(booleanType()))
40 .bind(
"container")))))));
42 const auto WrongUse = traverse(
43 ast_type_traits::TK_AsIs,
45 hasParent(binaryOperator(isComparisonOperator(),
46 hasEitherOperand(ignoringImpCasts(
47 anyOf(integerLiteral(equals(1)),
48 integerLiteral(equals(0))))))
49 .bind(
"SizeBinaryOp")),
50 hasParent(implicitCastExpr(
51 hasImplicitDestinationType(booleanType()),
53 unaryOperator(hasOperatorName(
"!")).bind(
"NegOnSize")),
55 hasParent(explicitCastExpr(hasDestinationType(booleanType())))));
58 cxxMemberCallExpr(on(expr(anyOf(hasType(ValidContainer),
59 hasType(pointsTo(ValidContainer)),
60 hasType(references(ValidContainer))))),
61 callee(cxxMethodDecl(hasName(
"size"))), WrongUse,
62 unless(hasAncestor(cxxMethodDecl(
63 ofClass(equalsBoundNode(
"container"))))))
64 .bind(
"SizeCallExpr"),
68 const auto DefaultConstructor = cxxConstructExpr(
69 hasDeclaration(cxxConstructorDecl(isDefaultConstructor())));
71 const auto WrongComparend = anyOf(
72 ignoringImpCasts(stringLiteral(hasSize(0))),
73 ignoringImpCasts(cxxBindTemporaryExpr(has(DefaultConstructor))),
74 ignoringImplicit(DefaultConstructor),
76 hasDeclaration(cxxConstructorDecl(isCopyConstructor())),
77 has(expr(ignoringImpCasts(DefaultConstructor)))),
79 hasDeclaration(cxxConstructorDecl(isMoveConstructor())),
80 has(expr(ignoringImpCasts(DefaultConstructor)))));
86 expr(hasType(pointsTo(ValidContainer))).bind(
"Pointee"))),
87 expr(hasType(ValidContainer)).bind(
"STLObject"));
90 hasAnyOverloadedOperatorName(
"==",
"!="),
91 anyOf(allOf(hasArgument(0, WrongComparend), hasArgument(1, STLArg)),
92 allOf(hasArgument(0, STLArg), hasArgument(1, WrongComparend))),
94 cxxMethodDecl(ofClass(equalsBoundNode(
"container"))))))
100 const auto *MemberCall =
101 Result.Nodes.getNodeAs<CXXMemberCallExpr>(
"SizeCallExpr");
102 const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>(
"BinCmp");
103 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>(
"SizeBinaryOp");
104 const auto *Pointee = Result.Nodes.getNodeAs<Expr>(
"Pointee");
107 ? MemberCall->getImplicitObjectArgument()
108 : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>(
"STLObject"));
110 std::string ReplacementText = std::string(
111 Lexer::getSourceText(CharSourceRange::getTokenRange(
E->getSourceRange()),
115 ReplacementText =
"(" + ReplacementText +
")";
117 if (
E->getType()->isPointerType())
118 ReplacementText +=
"->empty()";
120 ReplacementText +=
".empty()";
123 if (BinCmp->getOperator() == OO_ExclaimEqual) {
124 ReplacementText =
"!" + ReplacementText;
127 FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
128 }
else if (BinaryOp) {
129 bool Negation =
false;
130 const bool ContainerIsLHS =
131 !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
132 const auto OpCode = BinaryOp->getOpcode();
134 if (ContainerIsLHS) {
135 if (
const auto *Literal = llvm::dyn_cast<IntegerLiteral>(
136 BinaryOp->getRHS()->IgnoreImpCasts()))
137 Value = Literal->getValue().getLimitedValue();
142 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts())
151 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
152 OpCode == BinaryOperatorKind::BO_NE))
156 if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
157 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
162 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
163 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
165 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
166 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
170 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
172 if ((OpCode == BinaryOperatorKind::BO_GT ||
173 OpCode == BinaryOperatorKind::BO_GE) &&
176 if ((OpCode == BinaryOperatorKind::BO_LT ||
177 OpCode == BinaryOperatorKind::BO_LE) &&
182 ReplacementText =
"!" + ReplacementText;
183 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
189 if (
const auto *UnaryOp =
190 Result.Nodes.getNodeAs<UnaryOperator>(
"NegOnSize"))
191 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
194 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
195 "!" + ReplacementText);
199 diag(MemberCall->getBeginLoc(),
200 "the 'empty' method should be used to check "
201 "for emptiness instead of 'size'")
204 diag(BinCmp->getBeginLoc(),
205 "the 'empty' method should be used to check "
206 "for emptiness instead of comparing to an empty object")
210 const auto *Container = Result.Nodes.getNodeAs<NamedDecl>(
"container");
211 if (
const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) {
216 if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation)
217 Container = CTS->getSpecializedTemplate();
219 const auto *
Empty = Result.Nodes.getNodeAs<FunctionDecl>(
"empty");
221 diag(
Empty->getLocation(),
"method %0::empty() defined here",