blob: b0c6fb5717acada588811d6b12a22171eba911c8 [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"
Etienne Bergeron9d265992016-04-21 16:57:56 +000014#include "../utils/Matchers.h"
Alexander Kornienko4babd682015-01-15 15:46:58 +000015
16using namespace clang::ast_matchers;
17
Alexander Kornienko4babd682015-01-15 15:46:58 +000018namespace clang {
Alexander Kornienko4babd682015-01-15 15:46:58 +000019namespace tidy {
20namespace readability {
21
22ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,
23 ClangTidyContext *Context)
24 : ClangTidyCheck(Name, Context) {}
25
26void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) {
Aaron Ballman1f1b0672015-09-02 16:05:21 +000027 // Only register the matchers for C++; the functionality currently does not
28 // provide any benefit to other languages, despite being benign.
29 if (!getLangOpts().CPlusPlus)
30 return;
31
Etienne Bergeron9d265992016-04-21 16:57:56 +000032 const auto stlContainer = hasAnyName(
33 "array", "basic_string", "deque", "forward_list", "list", "map",
34 "multimap", "multiset", "priority_queue", "queue", "set", "stack",
35 "unordered_map", "unordered_multimap", "unordered_multiset",
36 "unordered_set", "vector");
37
Alexander Kornienko4babd682015-01-15 15:46:58 +000038 const auto WrongUse = anyOf(
Gabor Horvath533c01d2016-04-19 13:29:05 +000039 hasParent(binaryOperator(
Etienne Bergeron9d265992016-04-21 16:57:56 +000040 matchers::isComparisonOperator(),
Gabor Horvath533c01d2016-04-19 13:29:05 +000041 hasEitherOperand(ignoringImpCasts(anyOf(
42 integerLiteral(equals(1)), integerLiteral(equals(0))))))
43 .bind("SizeBinaryOp")),
Alexander Kornienko4babd682015-01-15 15:46:58 +000044 hasParent(implicitCastExpr(
Gabor Horvatha4fd3be2016-02-09 09:26:11 +000045 hasImplicitDestinationType(booleanType()),
Alexander Kornienko4babd682015-01-15 15:46:58 +000046 anyOf(
47 hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")),
48 anything()))),
Gabor Horvatha4fd3be2016-02-09 09:26:11 +000049 hasParent(explicitCastExpr(hasDestinationType(booleanType()))));
Alexander Kornienko4babd682015-01-15 15:46:58 +000050
51 Finder->addMatcher(
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +000052 cxxMemberCallExpr(
Etienne Bergeron9d265992016-04-21 16:57:56 +000053 on(expr(anyOf(hasType(namedDecl(stlContainer)),
54 hasType(pointsTo(namedDecl(stlContainer))),
55 hasType(references(namedDecl(stlContainer)))))
Alexander Kornienkofebfd3452015-01-22 12:27:09 +000056 .bind("STLObject")),
Aaron Ballmanb9ea09c2015-09-17 13:31:25 +000057 callee(cxxMethodDecl(hasName("size"))), WrongUse)
58 .bind("SizeCallExpr"),
Alexander Kornienko4babd682015-01-15 15:46:58 +000059 this);
60}
61
62void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
63 const auto *MemberCall =
64 Result.Nodes.getNodeAs<CXXMemberCallExpr>("SizeCallExpr");
65 const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
66 const auto *E = Result.Nodes.getNodeAs<Expr>("STLObject");
67 FixItHint Hint;
Alexander Kornienko96e7b8b2015-01-22 12:40:47 +000068 std::string ReplacementText = Lexer::getSourceText(
69 CharSourceRange::getTokenRange(E->getSourceRange()),
70 *Result.SourceManager, Result.Context->getLangOpts());
Alexander Kornienko4babd682015-01-15 15:46:58 +000071 if (E->getType()->isPointerType())
72 ReplacementText += "->empty()";
73 else
74 ReplacementText += ".empty()";
75
76 if (BinaryOp) { // Determine the correct transformation.
77 bool Negation = false;
Gabor Horvatha4e35ec2015-12-12 11:31:25 +000078 const bool ContainerIsLHS =
79 !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
Alexander Kornienko4babd682015-01-15 15:46:58 +000080 const auto OpCode = BinaryOp->getOpcode();
81 uint64_t Value = 0;
82 if (ContainerIsLHS) {
Gabor Horvatha4e35ec2015-12-12 11:31:25 +000083 if (const auto *Literal = llvm::dyn_cast<IntegerLiteral>(
84 BinaryOp->getRHS()->IgnoreImpCasts()))
Alexander Kornienko4babd682015-01-15 15:46:58 +000085 Value = Literal->getValue().getLimitedValue();
86 else
87 return;
88 } else {
Gabor Horvatha4e35ec2015-12-12 11:31:25 +000089 Value =
90 llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts())
91 ->getValue()
92 .getLimitedValue();
Alexander Kornienko4babd682015-01-15 15:46:58 +000093 }
94
95 // Constant that is not handled.
96 if (Value > 1)
97 return;
98
Gabor Horvath533c01d2016-04-19 13:29:05 +000099 if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
100 OpCode == BinaryOperatorKind::BO_NE))
101 return;
102
Alexander Kornienko4babd682015-01-15 15:46:58 +0000103 // Always true, no warnings for that.
104 if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
105 (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
106 return;
107
Gabor Horvath1f30cf62015-12-28 17:20:33 +0000108 // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
109 if (Value == 1) {
110 if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
111 (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
112 return;
113 if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
114 (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
115 return;
116 }
Gabor Horvathc6ff9c32015-12-21 09:43:52 +0000117
Alexander Kornienko4babd682015-01-15 15:46:58 +0000118 if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
119 Negation = true;
120 if ((OpCode == BinaryOperatorKind::BO_GT ||
121 OpCode == BinaryOperatorKind::BO_GE) &&
122 ContainerIsLHS)
123 Negation = true;
124 if ((OpCode == BinaryOperatorKind::BO_LT ||
125 OpCode == BinaryOperatorKind::BO_LE) &&
126 !ContainerIsLHS)
127 Negation = true;
128
129 if (Negation)
130 ReplacementText = "!" + ReplacementText;
131 Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
132 ReplacementText);
133
134 } else {
135 // If there is a conversion above the size call to bool, it is safe to just
136 // replace size with empty.
137 if (const auto *UnaryOp =
138 Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
139 Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
140 ReplacementText);
141 else
142 Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
143 "!" + ReplacementText);
144 }
Alexander Kornienko301130e2015-11-09 15:53:28 +0000145 diag(MemberCall->getLocStart(), "the 'empty' method should be used to check "
146 "for emptiness instead of 'size'")
Alexander Kornienko4babd682015-01-15 15:46:58 +0000147 << Hint;
148}
149
150} // namespace readability
151} // namespace tidy
152} // namespace clang