blob: 56396156e6a47015c3fc90892c360e97a43c8f0e [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
Gabor Horvathf7c39d52016-02-09 10:20:48 +000017static bool isContainerName(llvm::StringRef ClassName) {
18 static const char *const ContainerNames[] = {"array",
Gabor Horvath533c01d2016-04-19 13:29:05 +000019 "basic_string",
Gabor Horvathf7c39d52016-02-09 10:20:48 +000020 "deque",
21 "forward_list",
22 "list",
23 "map",
24 "multimap",
25 "multiset",
26 "priority_queue",
27 "queue",
28 "set",
29 "stack",
30 "unordered_map",
31 "unordered_multimap",
32 "unordered_multiset",
33 "unordered_set",
34 "vector"};
Benjamin Kramer73d27492015-04-17 13:52:08 +000035 return std::binary_search(std::begin(ContainerNames),
36 std::end(ContainerNames), ClassName);
Alexander Kornienko4babd682015-01-15 15:46:58 +000037}
Alexander Kornienko4babd682015-01-15 15:46:58 +000038
39namespace clang {
Alexander Kornienko50d7f4612015-06-17 13:11:37 +000040namespace {
Alexander Kornienko4babd682015-01-15 15:46:58 +000041AST_MATCHER(NamedDecl, stlContainer) {
Gabor Horvathf7c39d52016-02-09 10:20:48 +000042 if (!isContainerName(Node.getName()))
43 return false;
44
45 return StringRef(Node.getQualifiedNameAsString()).startswith("std::");
Alexander Kornienko4babd682015-01-15 15:46:58 +000046}
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(
Gabor Horvath533c01d2016-04-19 13:29:05 +000063 hasParent(binaryOperator(
64 anyOf(hasOperatorName("<"), hasOperatorName(">="),
65 hasOperatorName(">"), hasOperatorName("<="),
66 hasOperatorName("=="), hasOperatorName("!=")),
67 hasEitherOperand(ignoringImpCasts(anyOf(
68 integerLiteral(equals(1)), integerLiteral(equals(0))))))
69 .bind("SizeBinaryOp")),
Alexander Kornienko4babd682015-01-15 15:46:58 +000070 hasParent(implicitCastExpr(
Gabor Horvatha4fd3be2016-02-09 09:26:11 +000071 hasImplicitDestinationType(booleanType()),
Alexander Kornienko4babd682015-01-15 15:46:58 +000072 anyOf(
73 hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")),
74 anything()))),
Gabor Horvatha4fd3be2016-02-09 09:26:11 +000075 hasParent(explicitCastExpr(hasDestinationType(booleanType()))));
Alexander Kornienko4babd682015-01-15 15:46:58 +000076
77 Finder->addMatcher(
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +000078 cxxMemberCallExpr(
Alexander Kornienko4babd682015-01-15 15:46:58 +000079 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")),
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +000083 callee(cxxMethodDecl(hasName("size"))), WrongUse)
84 .bind("SizeCallExpr"),
Alexander Kornienko4babd682015-01-15 15:46:58 +000085 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;
Gabor Horvatha4e35ec2015-12-12 11:31:25 +0000104 const bool ContainerIsLHS =
105 !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
Alexander Kornienko4babd682015-01-15 15:46:58 +0000106 const auto OpCode = BinaryOp->getOpcode();
107 uint64_t Value = 0;
108 if (ContainerIsLHS) {
Gabor Horvatha4e35ec2015-12-12 11:31:25 +0000109 if (const auto *Literal = llvm::dyn_cast<IntegerLiteral>(
110 BinaryOp->getRHS()->IgnoreImpCasts()))
Alexander Kornienko4babd682015-01-15 15:46:58 +0000111 Value = Literal->getValue().getLimitedValue();
112 else
113 return;
114 } else {
Gabor Horvatha4e35ec2015-12-12 11:31:25 +0000115 Value =
116 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts())
117 ->getValue()
118 .getLimitedValue();
Alexander Kornienko4babd682015-01-15 15:46:58 +0000119 }
120
121 // Constant that is not handled.
122 if (Value > 1)
123 return;
124
Gabor Horvath533c01d2016-04-19 13:29:05 +0000125 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
126 OpCode == BinaryOperatorKind::BO_NE))
127 return;
128
Alexander Kornienko4babd682015-01-15 15:46:58 +0000129 // Always true, no warnings for that.
130 if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
131 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
132 return;
133
Gabor Horvath1f30cf62015-12-28 17:20:33 +0000134 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
135 if (Value == 1) {
136 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
137 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
138 return;
139 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
140 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
141 return;
142 }
Gabor Horvathc6ff9c32015-12-21 09:43:52 +0000143
Alexander Kornienko4babd682015-01-15 15:46:58 +0000144 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
145 Negation = true;
146 if ((OpCode == BinaryOperatorKind::BO_GT ||
147 OpCode == BinaryOperatorKind::BO_GE) &&
148 ContainerIsLHS)
149 Negation = true;
150 if ((OpCode == BinaryOperatorKind::BO_LT ||
151 OpCode == BinaryOperatorKind::BO_LE) &&
152 !ContainerIsLHS)
153 Negation = true;
154
155 if (Negation)
156 ReplacementText = "!" + ReplacementText;
157 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
158 ReplacementText);
159
160 } else {
161 // If there is a conversion above the size call to bool, it is safe to just
162 // replace size with empty.
163 if (const auto *UnaryOp =
164 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
165 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
166 ReplacementText);
167 else
168 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
169 "!" + ReplacementText);
170 }
Alexander Kornienko301130e2015-11-09 15:53:28 +0000171 diag(MemberCall->getLocStart(), "the 'empty' method should be used to check "
172 "for emptiness instead of 'size'")
Alexander Kornienko4babd682015-01-15 15:46:58 +0000173 << Hint;
174}
175
176} // namespace readability
177} // namespace tidy
178} // namespace clang