1 //===--- ContainerSizeEmptyCheck.cpp - clang-tidy -------------------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 #include "ContainerSizeEmptyCheck.h"
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"
16 
17 using namespace clang::ast_matchers;
18 
19 namespace clang {
20 namespace tidy {
21 namespace readability {
22 
23 using utils::IsBinaryOrTernary;
24 
25 ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,
26                                                  ClangTidyContext *Context)
27     : ClangTidyCheck(Name, Context) {}
28 
29 void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) {
30   // Only register the matchers for C++; the functionality currently does not
31   // provide any benefit to other languages, despite being benign.
32   if (!getLangOpts().CPlusPlus)
33     return;
34 
35   const auto ValidContainer = qualType(hasUnqualifiedDesugaredType(
36       recordType(hasDeclaration(cxxRecordDecl(isSameOrDerivedFrom(
37           namedDecl(
38               has(cxxMethodDecl(
39                       isConst(), parameterCountIs(0), isPublic(),
40                       hasName("size"),
41                       returns(qualType(isInteger(), unless(booleanType()))))
42                       .bind("size")),
43               has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
44                                 hasName("empty"), returns(booleanType()))
45                       .bind("empty")))
46               .bind("container")))))));
47 
48   const auto WrongUse = anyOf(
49       hasParent(binaryOperator(
50                     matchers::isComparisonOperator(),
51                     hasEitherOperand(ignoringImpCasts(anyOf(
52                         integerLiteral(equals(1)), integerLiteral(equals(0))))))
53                     .bind("SizeBinaryOp")),
54       hasParent(implicitCastExpr(
55           hasImplicitDestinationType(booleanType()),
56           anyOf(
57               hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")),
58               anything()))),
59       hasParent(explicitCastExpr(hasDestinationType(booleanType()))));
60 
61   Finder->addMatcher(
62       cxxMemberCallExpr(on(expr(anyOf(hasType(ValidContainer),
63                                       hasType(pointsTo(ValidContainer)),
64                                       hasType(references(ValidContainer))))),
65                         callee(cxxMethodDecl(hasName("size"))), WrongUse,
66                         unless(hasAncestor(cxxMethodDecl(
67                             ofClass(equalsBoundNode("container"))))))
68           .bind("SizeCallExpr"),
69       this);
70 
71   // Empty constructor matcher.
72   const auto DefaultConstructor = cxxConstructExpr(
73           hasDeclaration(cxxConstructorDecl(isDefaultConstructor())));
74   // Comparison to empty string or empty constructor.
75   const auto WrongComparend = anyOf(
76       ignoringImpCasts(stringLiteral(hasSize(0))),
77       ignoringImpCasts(cxxBindTemporaryExpr(has(DefaultConstructor))),
78       ignoringImplicit(DefaultConstructor),
79       cxxConstructExpr(
80           hasDeclaration(cxxConstructorDecl(isCopyConstructor())),
81           has(expr(ignoringImpCasts(DefaultConstructor)))),
82       cxxConstructExpr(
83           hasDeclaration(cxxConstructorDecl(isMoveConstructor())),
84           has(expr(ignoringImpCasts(DefaultConstructor)))));
85   // Match the object being compared.
86   const auto STLArg =
87       anyOf(unaryOperator(
88                 hasOperatorName("*"),
89                 hasUnaryOperand(
90                     expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))),
91             expr(hasType(ValidContainer)).bind("STLObject"));
92   Finder->addMatcher(
93       cxxOperatorCallExpr(
94           anyOf(hasOverloadedOperatorName("=="),
95                 hasOverloadedOperatorName("!=")),
96           anyOf(allOf(hasArgument(0, WrongComparend), hasArgument(1, STLArg)),
97                 allOf(hasArgument(0, STLArg), hasArgument(1, WrongComparend))),
98           unless(hasAncestor(
99               cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
100           .bind("BinCmp"),
101       this);
102 }
103 
104 void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
105   const auto *MemberCall =
106       Result.Nodes.getNodeAs<CXXMemberCallExpr>("SizeCallExpr");
107   const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");
108   const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
109   const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");
110   const auto *E =
111       MemberCall
112           ? MemberCall->getImplicitObjectArgument()
113           : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));
114   FixItHint Hint;
115   std::string ReplacementText =
116       Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()),
117                            *Result.SourceManager, getLangOpts());
118   if (BinCmp && IsBinaryOrTernary(E)) {
119     // Not just a DeclRefExpr, so parenthesize to be on the safe side.
120     ReplacementText = "(" + ReplacementText + ")";
121   }
122   if (E->getType()->isPointerType())
123     ReplacementText += "->empty()";
124   else
125     ReplacementText += ".empty()";
126 
127   if (BinCmp) {
128     if (BinCmp->getOperator() == OO_ExclaimEqual) {
129       ReplacementText = "!" + ReplacementText;
130     }
131     Hint =
132         FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
133   } else if (BinaryOp) {  // Determine the correct transformation.
134     bool Negation = false;
135     const bool ContainerIsLHS =
136         !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
137     const auto OpCode = BinaryOp->getOpcode();
138     uint64_t Value = 0;
139     if (ContainerIsLHS) {
140       if (const auto *Literal = llvm::dyn_cast<IntegerLiteral>(
141               BinaryOp->getRHS()->IgnoreImpCasts()))
142         Value = Literal->getValue().getLimitedValue();
143       else
144         return;
145     } else {
146       Value =
147           llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts())
148               ->getValue()
149               .getLimitedValue();
150     }
151 
152     // Constant that is not handled.
153     if (Value > 1)
154       return;
155 
156     if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
157                        OpCode == BinaryOperatorKind::BO_NE))
158       return;
159 
160     // Always true, no warnings for that.
161     if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
162         (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
163       return;
164 
165     // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
166     if (Value == 1) {
167       if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
168           (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
169         return;
170       if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
171           (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
172         return;
173     }
174 
175     if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
176       Negation = true;
177     if ((OpCode == BinaryOperatorKind::BO_GT ||
178          OpCode == BinaryOperatorKind::BO_GE) &&
179         ContainerIsLHS)
180       Negation = true;
181     if ((OpCode == BinaryOperatorKind::BO_LT ||
182          OpCode == BinaryOperatorKind::BO_LE) &&
183         !ContainerIsLHS)
184       Negation = true;
185 
186     if (Negation)
187       ReplacementText = "!" + ReplacementText;
188     Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
189                                         ReplacementText);
190 
191   } else {
192     // If there is a conversion above the size call to bool, it is safe to just
193     // replace size with empty.
194     if (const auto *UnaryOp =
195             Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
196       Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
197                                           ReplacementText);
198     else
199       Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
200                                           "!" + ReplacementText);
201   }
202 
203   if (MemberCall) {
204     diag(MemberCall->getLocStart(),
205          "the 'empty' method should be used to check "
206          "for emptiness instead of 'size'")
207         << Hint;
208   } else {
209     diag(BinCmp->getLocStart(),
210          "the 'empty' method should be used to check "
211          "for emptiness instead of comparing to an empty object")
212         << Hint;
213   }
214 
215   const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container");
216   const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty");
217 
218   diag(Empty->getLocation(), "method %0::empty() defined here",
219        DiagnosticIDs::Note)
220       << Container;
221 }
222 
223 } // namespace readability
224 } // namespace tidy
225 } // namespace clang
226