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