clang-tools  5.0.0
ContainerSizeEmptyCheck.cpp
Go to the documentation of this file.
1 //===--- ContainerSizeEmptyCheck.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 //===----------------------------------------------------------------------===//
10 #include "../utils/ASTUtils.h"
11 #include "../utils/Matchers.h"
12 #include "clang/AST/ASTContext.h"
13 #include "clang/ASTMatchers/ASTMatchers.h"
14 #include "clang/Lex/Lexer.h"
15 #include "llvm/ADT/StringRef.h"
16 
17 using namespace clang::ast_matchers;
18 
19 namespace clang {
20 namespace tidy {
21 namespace readability {
22 
24 
25 ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,
27  : ClangTidyCheck(Name, Context) {}
28 
30  // Only register the matchers for C++; the functionality currently does not
31  // provide any benefit to other languages, despite being benign.
32  if (!getLangOpts().CPlusPlus)
33  return;
34 
35  const auto ValidContainer = cxxRecordDecl(isSameOrDerivedFrom(
36  namedDecl(
37  has(cxxMethodDecl(
38  isConst(), parameterCountIs(0), isPublic(), hasName("size"),
39  returns(qualType(isInteger(), unless(booleanType()))))
40  .bind("size")),
41  has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
42  hasName("empty"), returns(booleanType()))
43  .bind("empty")))
44  .bind("container")));
45 
46  const auto WrongUse = anyOf(
47  hasParent(binaryOperator(
48  matchers::isComparisonOperator(),
49  hasEitherOperand(ignoringImpCasts(anyOf(
50  integerLiteral(equals(1)), integerLiteral(equals(0))))))
51  .bind("SizeBinaryOp")),
52  hasParent(implicitCastExpr(
53  hasImplicitDestinationType(booleanType()),
54  anyOf(
55  hasParent(unaryOperator(hasOperatorName("!")).bind("NegOnSize")),
56  anything()))),
57  hasParent(explicitCastExpr(hasDestinationType(booleanType()))));
58 
59  Finder->addMatcher(
60  cxxMemberCallExpr(on(expr(anyOf(hasType(ValidContainer),
61  hasType(pointsTo(ValidContainer)),
62  hasType(references(ValidContainer))))),
63  callee(cxxMethodDecl(hasName("size"))), WrongUse,
64  unless(hasAncestor(cxxMethodDecl(
65  ofClass(equalsBoundNode("container"))))))
66  .bind("SizeCallExpr"),
67  this);
68 
69  // Empty constructor matcher.
70  const auto DefaultConstructor = cxxConstructExpr(
71  hasDeclaration(cxxConstructorDecl(isDefaultConstructor())));
72  // Comparison to empty string or empty constructor.
73  const auto WrongComparend = anyOf(
74  ignoringImpCasts(stringLiteral(hasSize(0))),
75  ignoringImpCasts(cxxBindTemporaryExpr(has(DefaultConstructor))),
76  ignoringImplicit(DefaultConstructor),
77  cxxConstructExpr(
78  hasDeclaration(cxxConstructorDecl(isCopyConstructor())),
79  has(expr(ignoringImpCasts(DefaultConstructor)))),
80  cxxConstructExpr(
81  hasDeclaration(cxxConstructorDecl(isMoveConstructor())),
82  has(expr(ignoringImpCasts(DefaultConstructor)))));
83  // Match the object being compared.
84  const auto STLArg =
85  anyOf(unaryOperator(
86  hasOperatorName("*"),
87  hasUnaryOperand(
88  expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))),
89  expr(hasType(ValidContainer)).bind("STLObject"));
90  Finder->addMatcher(
91  cxxOperatorCallExpr(
92  anyOf(hasOverloadedOperatorName("=="),
93  hasOverloadedOperatorName("!=")),
94  anyOf(allOf(hasArgument(0, WrongComparend), hasArgument(1, STLArg)),
95  allOf(hasArgument(0, STLArg), hasArgument(1, WrongComparend))),
96  unless(hasAncestor(
97  cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
98  .bind("BinCmp"),
99  this);
100 }
101 
102 void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
103  const auto *MemberCall =
104  Result.Nodes.getNodeAs<CXXMemberCallExpr>("SizeCallExpr");
105  const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");
106  const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
107  const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");
108  const auto *E =
109  MemberCall
110  ? MemberCall->getImplicitObjectArgument()
111  : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));
112  FixItHint Hint;
113  std::string ReplacementText =
114  Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()),
115  *Result.SourceManager, getLangOpts());
116  if (BinCmp && IsBinaryOrTernary(E)) {
117  // Not just a DeclRefExpr, so parenthesize to be on the safe side.
118  ReplacementText = "(" + ReplacementText + ")";
119  }
120  if (E->getType()->isPointerType())
121  ReplacementText += "->empty()";
122  else
123  ReplacementText += ".empty()";
124 
125  if (BinCmp) {
126  if (BinCmp->getOperator() == OO_ExclaimEqual) {
127  ReplacementText = "!" + ReplacementText;
128  }
129  Hint =
130  FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
131  } else if (BinaryOp) { // Determine the correct transformation.
132  bool Negation = false;
133  const bool ContainerIsLHS =
134  !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
135  const auto OpCode = BinaryOp->getOpcode();
136  uint64_t Value = 0;
137  if (ContainerIsLHS) {
138  if (const auto *Literal = llvm::dyn_cast<IntegerLiteral>(
139  BinaryOp->getRHS()->IgnoreImpCasts()))
140  Value = Literal->getValue().getLimitedValue();
141  else
142  return;
143  } else {
144  Value =
145  llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts())
146  ->getValue()
147  .getLimitedValue();
148  }
149 
150  // Constant that is not handled.
151  if (Value > 1)
152  return;
153 
154  if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
155  OpCode == BinaryOperatorKind::BO_NE))
156  return;
157 
158  // Always true, no warnings for that.
159  if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
160  (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
161  return;
162 
163  // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
164  if (Value == 1) {
165  if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
166  (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
167  return;
168  if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
169  (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
170  return;
171  }
172 
173  if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
174  Negation = true;
175  if ((OpCode == BinaryOperatorKind::BO_GT ||
176  OpCode == BinaryOperatorKind::BO_GE) &&
177  ContainerIsLHS)
178  Negation = true;
179  if ((OpCode == BinaryOperatorKind::BO_LT ||
180  OpCode == BinaryOperatorKind::BO_LE) &&
181  !ContainerIsLHS)
182  Negation = true;
183 
184  if (Negation)
185  ReplacementText = "!" + ReplacementText;
186  Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
187  ReplacementText);
188 
189  } else {
190  // If there is a conversion above the size call to bool, it is safe to just
191  // replace size with empty.
192  if (const auto *UnaryOp =
193  Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
194  Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
195  ReplacementText);
196  else
197  Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
198  "!" + ReplacementText);
199  }
200 
201  if (MemberCall) {
202  diag(MemberCall->getLocStart(),
203  "the 'empty' method should be used to check "
204  "for emptiness instead of 'size'")
205  << Hint;
206  } else {
207  diag(BinCmp->getLocStart(),
208  "the 'empty' method should be used to check "
209  "for emptiness instead of comparing to an empty object")
210  << Hint;
211  }
212 
213  const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container");
214  const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty");
215 
216  diag(Empty->getLocation(), "method %0::empty() defined here",
217  DiagnosticIDs::Note)
218  << Container;
219 }
220 
221 } // namespace readability
222 } // namespace tidy
223 } // namespace clang
bool IsBinaryOrTernary(const Expr *E)
Definition: ASTUtils.cpp:28
LangOptions getLangOpts() const
Returns the language options from the context.
Definition: ClangTidy.h:187
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
StringHandle Name
std::unique_ptr< ast_matchers::MatchFinder > Finder
Definition: ClangTidy.cpp:275
Base class for all clang-tidy checks.
Definition: ClangTidy.h:127
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
ClangTidyContext & Context
Definition: ClangTidy.cpp:87
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Definition: ClangTidy.cpp:416