blob: dadfff8d883a3d292364a84030c21ab6173f16f5 [file] [log] [blame]
Alexander Kornienko1b677db2015-03-09 12:18:39 +00001//===--- ContainerSizeEmptyCheck.cpp - clang-tidy -------------------------===//
Alexander Kornienko4babd682015-01-15 15:46:58 +00002//
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//===----------------------------------------------------------------------===//
Alexander Kornienko1b677db2015-03-09 12:18:39 +00009#include "ContainerSizeEmptyCheck.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"
Alexander Kornienko4babd682015-01-15 15:46:58 +000014
15using namespace clang::ast_matchers;
16
Benjamin Kramer73d27492015-04-17 13:52:08 +000017static bool isContainer(llvm::StringRef ClassName) {
18 static const char *ContainerNames[] = {
19 "std::array",
20 "std::deque",
21 "std::forward_list",
22 "std::list",
23 "std::map",
24 "std::multimap",
25 "std::multiset",
26 "std::priority_queue",
27 "std::queue",
28 "std::set",
29 "std::stack",
30 "std::unordered_map",
31 "std::unordered_multimap",
32 "std::unordered_multiset",
33 "std::unordered_set",
34 "std::vector"
35 };
36 return std::binary_search(std::begin(ContainerNames),
37 std::end(ContainerNames), ClassName);
Alexander Kornienko4babd682015-01-15 15:46:58 +000038}
Alexander Kornienko4babd682015-01-15 15:46:58 +000039
40namespace clang {
Alexander Kornienko50d7f4612015-06-17 13:11:37 +000041namespace {
Alexander Kornienkofebfd3452015-01-22 12:27:09 +000042AST_MATCHER(QualType, isBoolType) { return Node->isBooleanType(); }
Alexander Kornienko4babd682015-01-15 15:46:58 +000043
44AST_MATCHER(NamedDecl, stlContainer) {
45 return isContainer(Node.getQualifiedNameAsString());
46}
Alexander Kornienko50d7f4612015-06-17 13:11:37 +000047} // namespace
Alexander Kornienko4babd682015-01-15 15:46:58 +000048
Alexander Kornienko4babd682015-01-15 15:46:58 +000049namespace tidy {
50namespace readability {
51
52ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,
53 ClangTidyContext *Context)
54 : ClangTidyCheck(Name, Context) {}
55
56void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) {
Aaron Ballman1f1b0672015-09-02 16:05:21 +000057 // Only register the matchers for C++; the functionality currently does not
58 // provide any benefit to other languages, despite being benign.
59 if (!getLangOpts().CPlusPlus)
60 return;
61
Alexander Kornienko4babd682015-01-15 15:46:58 +000062 const auto WrongUse = anyOf(
63 hasParent(
64 binaryOperator(
65 anyOf(has(integerLiteral(equals(0))),
66 allOf(anyOf(hasOperatorName("<"), hasOperatorName(">="),
67 hasOperatorName(">"), hasOperatorName("<=")),
Alexander Kornienkoa6354ca2015-01-23 14:43:06 +000068 hasEitherOperand(integerLiteral(equals(1))))))
Alexander Kornienko4babd682015-01-15 15:46:58 +000069 .bind("SizeBinaryOp")),
70 hasParent(implicitCastExpr(
Alexander Kornienkofebfd3452015-01-22 12:27:09 +000071 hasImplicitDestinationType(isBoolType()),
Alexander Kornienko4babd682015-01-15 15:46:58 +000072 anyOf(
73 hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")),
74 anything()))),
Alexander Kornienkofebfd3452015-01-22 12:27:09 +000075 hasParent(explicitCastExpr(hasDestinationType(isBoolType()))));
Alexander Kornienko4babd682015-01-15 15:46:58 +000076
77 Finder->addMatcher(
78 memberCallExpr(
79 on(expr(anyOf(hasType(namedDecl(stlContainer())),
Alexander Kornienkofebfd3452015-01-22 12:27:09 +000080 hasType(pointsTo(namedDecl(stlContainer()))),
81 hasType(references(namedDecl(stlContainer())))))
82 .bind("STLObject")),
Alexander Kornienko4babd682015-01-15 15:46:58 +000083 callee(methodDecl(hasName("size"))), WrongUse).bind("SizeCallExpr"),
84 this);
85}
86
87void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
88 const auto *MemberCall =
89 Result.Nodes.getNodeAs<CXXMemberCallExpr>("SizeCallExpr");
90 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
91 const auto *E = Result.Nodes.getNodeAs<Expr>("STLObject");
92 FixItHint Hint;
Alexander Kornienko96e7b8b2015-01-22 12:40:47 +000093 std::string ReplacementText = Lexer::getSourceText(
94 CharSourceRange::getTokenRange(E->getSourceRange()),
95 *Result.SourceManager, Result.Context->getLangOpts());
Alexander Kornienko4babd682015-01-15 15:46:58 +000096 if (E->getType()->isPointerType())
97 ReplacementText += "->empty()";
98 else
99 ReplacementText += ".empty()";
100
101 if (BinaryOp) { // Determine the correct transformation.
102 bool Negation = false;
103 const bool ContainerIsLHS = !llvm::isa<IntegerLiteral>(BinaryOp->getLHS());
104 const auto OpCode = BinaryOp->getOpcode();
105 uint64_t Value = 0;
106 if (ContainerIsLHS) {
107 if (const auto *Literal =
108 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getRHS()))
109 Value = Literal->getValue().getLimitedValue();
110 else
111 return;
112 } else {
113 Value = llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS())
114 ->getValue()
115 .getLimitedValue();
116 }
117
118 // Constant that is not handled.
119 if (Value > 1)
120 return;
121
122 // Always true, no warnings for that.
123 if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
124 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
125 return;
126
127 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
128 Negation = true;
129 if ((OpCode == BinaryOperatorKind::BO_GT ||
130 OpCode == BinaryOperatorKind::BO_GE) &&
131 ContainerIsLHS)
132 Negation = true;
133 if ((OpCode == BinaryOperatorKind::BO_LT ||
134 OpCode == BinaryOperatorKind::BO_LE) &&
135 !ContainerIsLHS)
136 Negation = true;
137
138 if (Negation)
139 ReplacementText = "!" + ReplacementText;
140 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
141 ReplacementText);
142
143 } else {
144 // If there is a conversion above the size call to bool, it is safe to just
145 // replace size with empty.
146 if (const auto *UnaryOp =
147 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
148 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
149 ReplacementText);
150 else
151 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
152 "!" + ReplacementText);
153 }
154 diag(MemberCall->getLocStart(),
155 "The 'empty' method should be used to check for emptiness instead "
156 "of 'size'.")
157 << Hint;
158}
159
160} // namespace readability
161} // namespace tidy
162} // namespace clang