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