blob: 53eed8f3c982ea8e7931f83403db73292eb6e400 [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) {
Gabor Horvatha4e35ec2015-12-12 11:31:25 +000018 static const char *const ContainerNames[] = {"std::array",
19 "std::deque",
20 "std::forward_list",
21 "std::list",
22 "std::map",
23 "std::multimap",
24 "std::multiset",
25 "std::priority_queue",
26 "std::queue",
27 "std::set",
28 "std::stack",
29 "std::unordered_map",
30 "std::unordered_multimap",
31 "std::unordered_multiset",
32 "std::unordered_set",
33 "std::vector"};
Benjamin Kramer73d27492015-04-17 13:52:08 +000034 return std::binary_search(std::begin(ContainerNames),
35 std::end(ContainerNames), ClassName);
Alexander Kornienko4babd682015-01-15 15:46:58 +000036}
Alexander Kornienko4babd682015-01-15 15:46:58 +000037
38namespace clang {
Alexander Kornienko50d7f4612015-06-17 13:11:37 +000039namespace {
Alexander Kornienko4babd682015-01-15 15:46:58 +000040AST_MATCHER(NamedDecl, stlContainer) {
41 return isContainer(Node.getQualifiedNameAsString());
42}
Alexander Kornienko50d7f4612015-06-17 13:11:37 +000043} // namespace
Alexander Kornienko4babd682015-01-15 15:46:58 +000044
Alexander Kornienko4babd682015-01-15 15:46:58 +000045namespace tidy {
46namespace readability {
47
48ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,
49 ClangTidyContext *Context)
50 : ClangTidyCheck(Name, Context) {}
51
52void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) {
Aaron Ballman1f1b0672015-09-02 16:05:21 +000053 // Only register the matchers for C++; the functionality currently does not
54 // provide any benefit to other languages, despite being benign.
55 if (!getLangOpts().CPlusPlus)
56 return;
57
Alexander Kornienko4babd682015-01-15 15:46:58 +000058 const auto WrongUse = anyOf(
59 hasParent(
60 binaryOperator(
61 anyOf(has(integerLiteral(equals(0))),
62 allOf(anyOf(hasOperatorName("<"), hasOperatorName(">="),
63 hasOperatorName(">"), hasOperatorName("<=")),
Gabor Horvatha4e35ec2015-12-12 11:31:25 +000064 hasEitherOperand(
65 ignoringImpCasts(integerLiteral(equals(1)))))))
Alexander Kornienko4babd682015-01-15 15:46:58 +000066 .bind("SizeBinaryOp")),
67 hasParent(implicitCastExpr(
Gabor Horvatha4fd3be2016-02-09 09:26:11 +000068 hasImplicitDestinationType(booleanType()),
Alexander Kornienko4babd682015-01-15 15:46:58 +000069 anyOf(
70 hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")),
71 anything()))),
Gabor Horvatha4fd3be2016-02-09 09:26:11 +000072 hasParent(explicitCastExpr(hasDestinationType(booleanType()))));
Alexander Kornienko4babd682015-01-15 15:46:58 +000073
74 Finder->addMatcher(
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +000075 cxxMemberCallExpr(
Alexander Kornienko4babd682015-01-15 15:46:58 +000076 on(expr(anyOf(hasType(namedDecl(stlContainer())),
Alexander Kornienkofebfd3452015-01-22 12:27:09 +000077 hasType(pointsTo(namedDecl(stlContainer()))),
78 hasType(references(namedDecl(stlContainer())))))
79 .bind("STLObject")),
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +000080 callee(cxxMethodDecl(hasName("size"))), WrongUse)
81 .bind("SizeCallExpr"),
Alexander Kornienko4babd682015-01-15 15:46:58 +000082 this);
83}
84
85void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
86 const auto *MemberCall =
87 Result.Nodes.getNodeAs<CXXMemberCallExpr>("SizeCallExpr");
88 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
89 const auto *E = Result.Nodes.getNodeAs<Expr>("STLObject");
90 FixItHint Hint;
Alexander Kornienko96e7b8b2015-01-22 12:40:47 +000091 std::string ReplacementText = Lexer::getSourceText(
92 CharSourceRange::getTokenRange(E->getSourceRange()),
93 *Result.SourceManager, Result.Context->getLangOpts());
Alexander Kornienko4babd682015-01-15 15:46:58 +000094 if (E->getType()->isPointerType())
95 ReplacementText += "->empty()";
96 else
97 ReplacementText += ".empty()";
98
99 if (BinaryOp) { // Determine the correct transformation.
100 bool Negation = false;
Gabor Horvatha4e35ec2015-12-12 11:31:25 +0000101 const bool ContainerIsLHS =
102 !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
Alexander Kornienko4babd682015-01-15 15:46:58 +0000103 const auto OpCode = BinaryOp->getOpcode();
104 uint64_t Value = 0;
105 if (ContainerIsLHS) {
Gabor Horvatha4e35ec2015-12-12 11:31:25 +0000106 if (const auto *Literal = llvm::dyn_cast<IntegerLiteral>(
107 BinaryOp->getRHS()->IgnoreImpCasts()))
Alexander Kornienko4babd682015-01-15 15:46:58 +0000108 Value = Literal->getValue().getLimitedValue();
109 else
110 return;
111 } else {
Gabor Horvatha4e35ec2015-12-12 11:31:25 +0000112 Value =
113 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts())
114 ->getValue()
115 .getLimitedValue();
Alexander Kornienko4babd682015-01-15 15:46:58 +0000116 }
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
Gabor Horvath1f30cf62015-12-28 17:20:33 +0000127 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
128 if (Value == 1) {
129 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
130 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
131 return;
132 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
133 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
134 return;
135 }
Gabor Horvathc6ff9c32015-12-21 09:43:52 +0000136
Alexander Kornienko4babd682015-01-15 15:46:58 +0000137 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
138 Negation = true;
139 if ((OpCode == BinaryOperatorKind::BO_GT ||
140 OpCode == BinaryOperatorKind::BO_GE) &&
141 ContainerIsLHS)
142 Negation = true;
143 if ((OpCode == BinaryOperatorKind::BO_LT ||
144 OpCode == BinaryOperatorKind::BO_LE) &&
145 !ContainerIsLHS)
146 Negation = true;
147
148 if (Negation)
149 ReplacementText = "!" + ReplacementText;
150 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
151 ReplacementText);
152
153 } else {
154 // If there is a conversion above the size call to bool, it is safe to just
155 // replace size with empty.
156 if (const auto *UnaryOp =
157 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
158 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
159 ReplacementText);
160 else
161 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
162 "!" + ReplacementText);
163 }
Alexander Kornienko301130e2015-11-09 15:53:28 +0000164 diag(MemberCall->getLocStart(), "the 'empty' method should be used to check "
165 "for emptiness instead of 'size'")
Alexander Kornienko4babd682015-01-15 15:46:58 +0000166 << Hint;
167}
168
169} // namespace readability
170} // namespace tidy
171} // namespace clang