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