blob: 407da55aee0455e1fdd0cca47fd8df471cca22aa [file] [log] [blame]
Alexander Kornienko4babd682015-01-15 15:46:58 +00001//===--- ContainerSizeEmpty.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 "ContainerSizeEmpty.h"
10
11#include "llvm/ADT/StringRef.h"
12#include "llvm/ADT/StringSet.h"
13
14#include "clang/AST/ASTContext.h"
15#include "clang/ASTMatchers/ASTMatchers.h"
Alexander Kornienko4babd682015-01-15 15:46:58 +000016#include "clang/Lex/Lexer.h"
17
18using namespace clang::ast_matchers;
19
20namespace {
21bool isContainer(llvm::StringRef ClassName) {
22 static const llvm::StringSet<> ContainerNames = [] {
23 llvm::StringSet<> RetVal;
24 RetVal.insert("std::vector");
25 RetVal.insert("std::list");
26 RetVal.insert("std::array");
27 RetVal.insert("std::deque");
28 RetVal.insert("std::forward_list");
29 RetVal.insert("std::set");
30 RetVal.insert("std::map");
31 RetVal.insert("std::multiset");
32 RetVal.insert("std::multimap");
33 RetVal.insert("std::unordered_set");
34 RetVal.insert("std::unordered_map");
35 RetVal.insert("std::unordered_multiset");
36 RetVal.insert("std::unordered_multimap");
37 RetVal.insert("std::stack");
38 RetVal.insert("std::queue");
39 RetVal.insert("std::priority_queue");
40 return RetVal;
41 }();
42 return ContainerNames.find(ClassName) != ContainerNames.end();
43}
44}
45
46namespace clang {
47namespace ast_matchers {
Alexander Kornienkofebfd3452015-01-22 12:27:09 +000048AST_MATCHER(QualType, isBoolType) { return Node->isBooleanType(); }
Alexander Kornienko4babd682015-01-15 15:46:58 +000049
50AST_MATCHER(NamedDecl, stlContainer) {
51 return isContainer(Node.getQualifiedNameAsString());
52}
53}
54}
55
56namespace clang {
57namespace tidy {
58namespace readability {
59
60ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,
61 ClangTidyContext *Context)
62 : ClangTidyCheck(Name, Context) {}
63
64void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) {
65 const auto WrongUse = anyOf(
66 hasParent(
67 binaryOperator(
68 anyOf(has(integerLiteral(equals(0))),
69 allOf(anyOf(hasOperatorName("<"), hasOperatorName(">="),
70 hasOperatorName(">"), hasOperatorName("<=")),
71 anyOf(hasRHS(integerLiteral(equals(1))),
72 hasLHS(integerLiteral(equals(1)))))))
73 .bind("SizeBinaryOp")),
74 hasParent(implicitCastExpr(
Alexander Kornienkofebfd3452015-01-22 12:27:09 +000075 hasImplicitDestinationType(isBoolType()),
Alexander Kornienko4babd682015-01-15 15:46:58 +000076 anyOf(
77 hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")),
78 anything()))),
Alexander Kornienkofebfd3452015-01-22 12:27:09 +000079 hasParent(explicitCastExpr(hasDestinationType(isBoolType()))));
Alexander Kornienko4babd682015-01-15 15:46:58 +000080
81 Finder->addMatcher(
82 memberCallExpr(
83 on(expr(anyOf(hasType(namedDecl(stlContainer())),
Alexander Kornienkofebfd3452015-01-22 12:27:09 +000084 hasType(pointsTo(namedDecl(stlContainer()))),
85 hasType(references(namedDecl(stlContainer())))))
86 .bind("STLObject")),
Alexander Kornienko4babd682015-01-15 15:46:58 +000087 callee(methodDecl(hasName("size"))), WrongUse).bind("SizeCallExpr"),
88 this);
89}
90
91void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
92 const auto *MemberCall =
93 Result.Nodes.getNodeAs<CXXMemberCallExpr>("SizeCallExpr");
94 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
95 const auto *E = Result.Nodes.getNodeAs<Expr>("STLObject");
96 FixItHint Hint;
Alexander Kornienko96e7b8b2015-01-22 12:40:47 +000097 std::string ReplacementText = Lexer::getSourceText(
98 CharSourceRange::getTokenRange(E->getSourceRange()),
99 *Result.SourceManager, Result.Context->getLangOpts());
Alexander Kornienko4babd682015-01-15 15:46:58 +0000100 if (E->getType()->isPointerType())
101 ReplacementText += "->empty()";
102 else
103 ReplacementText += ".empty()";
104
105 if (BinaryOp) { // Determine the correct transformation.
106 bool Negation = false;
107 const bool ContainerIsLHS = !llvm::isa<IntegerLiteral>(BinaryOp->getLHS());
108 const auto OpCode = BinaryOp->getOpcode();
109 uint64_t Value = 0;
110 if (ContainerIsLHS) {
111 if (const auto *Literal =
112 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getRHS()))
113 Value = Literal->getValue().getLimitedValue();
114 else
115 return;
116 } else {
117 Value = llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS())
118 ->getValue()
119 .getLimitedValue();
120 }
121
122 // Constant that is not handled.
123 if (Value > 1)
124 return;
125
126 // Always true, no warnings for that.
127 if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
128 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
129 return;
130
131 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
132 Negation = true;
133 if ((OpCode == BinaryOperatorKind::BO_GT ||
134 OpCode == BinaryOperatorKind::BO_GE) &&
135 ContainerIsLHS)
136 Negation = true;
137 if ((OpCode == BinaryOperatorKind::BO_LT ||
138 OpCode == BinaryOperatorKind::BO_LE) &&
139 !ContainerIsLHS)
140 Negation = true;
141
142 if (Negation)
143 ReplacementText = "!" + ReplacementText;
144 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
145 ReplacementText);
146
147 } else {
148 // If there is a conversion above the size call to bool, it is safe to just
149 // replace size with empty.
150 if (const auto *UnaryOp =
151 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
152 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
153 ReplacementText);
154 else
155 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
156 "!" + ReplacementText);
157 }
158 diag(MemberCall->getLocStart(),
159 "The 'empty' method should be used to check for emptiness instead "
160 "of 'size'.")
161 << Hint;
162}
163
164} // namespace readability
165} // namespace tidy
166} // namespace clang