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