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 "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchers.h"
12 #include "clang/Lex/Lexer.h"
13 #include "llvm/ADT/StringRef.h"
14 #include "../utils/Matchers.h"
15 
16 using namespace clang::ast_matchers;
17 
18 namespace clang {
19 namespace tidy {
20 namespace readability {
21 
22 ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,
23                                                  ClangTidyContext *Context)
24     : ClangTidyCheck(Name, Context) {}
25 
26 void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) {
27   // Only register the matchers for C++; the functionality currently does not
28   // provide any benefit to other languages, despite being benign.
29   if (!getLangOpts().CPlusPlus)
30     return;
31 
32   const auto stlContainer = hasAnyName(
33       "array", "basic_string", "deque", "forward_list", "list", "map",
34       "multimap", "multiset", "priority_queue", "queue", "set", "stack",
35       "unordered_map", "unordered_multimap", "unordered_multiset",
36       "unordered_set", "vector");
37 
38   const auto WrongUse = anyOf(
39       hasParent(binaryOperator(
40                     matchers::isComparisonOperator(),
41                     hasEitherOperand(ignoringImpCasts(anyOf(
42                         integerLiteral(equals(1)), integerLiteral(equals(0))))))
43                     .bind("SizeBinaryOp")),
44       hasParent(implicitCastExpr(
45           hasImplicitDestinationType(booleanType()),
46           anyOf(
47               hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")),
48               anything()))),
49       hasParent(explicitCastExpr(hasDestinationType(booleanType()))));
50 
51   Finder->addMatcher(
52       cxxMemberCallExpr(
53           on(expr(anyOf(hasType(namedDecl(stlContainer)),
54                         hasType(pointsTo(namedDecl(stlContainer))),
55                         hasType(references(namedDecl(stlContainer)))))
56                  .bind("STLObject")),
57           callee(cxxMethodDecl(hasName("size"))), WrongUse)
58           .bind("SizeCallExpr"),
59       this);
60 }
61 
62 void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
63   const auto *MemberCall =
64       Result.Nodes.getNodeAs<CXXMemberCallExpr>("SizeCallExpr");
65   const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
66   const auto *E = Result.Nodes.getNodeAs<Expr>("STLObject");
67   FixItHint Hint;
68   std::string ReplacementText = Lexer::getSourceText(
69       CharSourceRange::getTokenRange(E->getSourceRange()),
70       *Result.SourceManager, Result.Context->getLangOpts());
71   if (E->getType()->isPointerType())
72     ReplacementText += "->empty()";
73   else
74     ReplacementText += ".empty()";
75 
76   if (BinaryOp) { // Determine the correct transformation.
77     bool Negation = false;
78     const bool ContainerIsLHS =
79         !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
80     const auto OpCode = BinaryOp->getOpcode();
81     uint64_t Value = 0;
82     if (ContainerIsLHS) {
83       if (const auto *Literal = llvm::dyn_cast<IntegerLiteral>(
84               BinaryOp->getRHS()->IgnoreImpCasts()))
85         Value = Literal->getValue().getLimitedValue();
86       else
87         return;
88     } else {
89       Value =
90           llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts())
91               ->getValue()
92               .getLimitedValue();
93     }
94 
95     // Constant that is not handled.
96     if (Value > 1)
97       return;
98 
99     if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
100                        OpCode == BinaryOperatorKind::BO_NE))
101       return;
102 
103     // Always true, no warnings for that.
104     if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
105         (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
106       return;
107 
108     // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
109     if (Value == 1) {
110       if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
111           (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
112         return;
113       if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
114           (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
115         return;
116     }
117 
118     if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
119       Negation = true;
120     if ((OpCode == BinaryOperatorKind::BO_GT ||
121          OpCode == BinaryOperatorKind::BO_GE) &&
122         ContainerIsLHS)
123       Negation = true;
124     if ((OpCode == BinaryOperatorKind::BO_LT ||
125          OpCode == BinaryOperatorKind::BO_LE) &&
126         !ContainerIsLHS)
127       Negation = true;
128 
129     if (Negation)
130       ReplacementText = "!" + ReplacementText;
131     Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
132                                         ReplacementText);
133 
134   } else {
135     // If there is a conversion above the size call to bool, it is safe to just
136     // replace size with empty.
137     if (const auto *UnaryOp =
138             Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
139       Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
140                                           ReplacementText);
141     else
142       Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
143                                           "!" + ReplacementText);
144   }
145   diag(MemberCall->getLocStart(), "the 'empty' method should be used to check "
146                                   "for emptiness instead of 'size'")
147       << Hint;
148 }
149 
150 } // namespace readability
151 } // namespace tidy
152 } // namespace clang
153