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