blob: b7c1534fed76dc91af76bd50293afa337513ac1e [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("<=")),
Alexander Kornienkoa6354ca2015-01-23 14:43:06 +000071 hasEitherOperand(integerLiteral(equals(1))))))
Alexander Kornienko4babd682015-01-15 15:46:58 +000072 .bind("SizeBinaryOp")),
73 hasParent(implicitCastExpr(
Alexander Kornienkofebfd3452015-01-22 12:27:09 +000074 hasImplicitDestinationType(isBoolType()),
Alexander Kornienko4babd682015-01-15 15:46:58 +000075 anyOf(
76 hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")),
77 anything()))),
Alexander Kornienkofebfd3452015-01-22 12:27:09 +000078 hasParent(explicitCastExpr(hasDestinationType(isBoolType()))));
Alexander Kornienko4babd682015-01-15 15:46:58 +000079
80 Finder->addMatcher(
81 memberCallExpr(
82 on(expr(anyOf(hasType(namedDecl(stlContainer())),
Alexander Kornienkofebfd3452015-01-22 12:27:09 +000083 hasType(pointsTo(namedDecl(stlContainer()))),
84 hasType(references(namedDecl(stlContainer())))))
85 .bind("STLObject")),
Alexander Kornienko4babd682015-01-15 15:46:58 +000086 callee(methodDecl(hasName("size"))), WrongUse).bind("SizeCallExpr"),
87 this);
88}
89
90void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
91 const auto *MemberCall =
92 Result.Nodes.getNodeAs<CXXMemberCallExpr>("SizeCallExpr");
93 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
94 const auto *E = Result.Nodes.getNodeAs<Expr>("STLObject");
95 FixItHint Hint;
Alexander Kornienko96e7b8b2015-01-22 12:40:47 +000096 std::string ReplacementText = Lexer::getSourceText(
97 CharSourceRange::getTokenRange(E->getSourceRange()),
98 *Result.SourceManager, Result.Context->getLangOpts());
Alexander Kornienko4babd682015-01-15 15:46:58 +000099 if (E->getType()->isPointerType())
100 ReplacementText += "->empty()";
101 else
102 ReplacementText += ".empty()";
103
104 if (BinaryOp) { // Determine the correct transformation.
105 bool Negation = false;
106 const bool ContainerIsLHS = !llvm::isa<IntegerLiteral>(BinaryOp->getLHS());
107 const auto OpCode = BinaryOp->getOpcode();
108 uint64_t Value = 0;
109 if (ContainerIsLHS) {
110 if (const auto *Literal =
111 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getRHS()))
112 Value = Literal->getValue().getLimitedValue();
113 else
114 return;
115 } else {
116 Value = llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS())
117 ->getValue()
118 .getLimitedValue();
119 }
120
121 // Constant that is not handled.
122 if (Value > 1)
123 return;
124
125 // Always true, no warnings for that.
126 if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
127 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
128 return;
129
130 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
131 Negation = true;
132 if ((OpCode == BinaryOperatorKind::BO_GT ||
133 OpCode == BinaryOperatorKind::BO_GE) &&
134 ContainerIsLHS)
135 Negation = true;
136 if ((OpCode == BinaryOperatorKind::BO_LT ||
137 OpCode == BinaryOperatorKind::BO_LE) &&
138 !ContainerIsLHS)
139 Negation = true;
140
141 if (Negation)
142 ReplacementText = "!" + ReplacementText;
143 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
144 ReplacementText);
145
146 } else {
147 // If there is a conversion above the size call to bool, it is safe to just
148 // replace size with empty.
149 if (const auto *UnaryOp =
150 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
151 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
152 ReplacementText);
153 else
154 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
155 "!" + ReplacementText);
156 }
157 diag(MemberCall->getLocStart(),
158 "The 'empty' method should be used to check for emptiness instead "
159 "of 'size'.")
160 << Hint;
161}
162
163} // namespace readability
164} // namespace tidy
165} // namespace clang