clang-tools  14.0.0git
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 ast_matchers {
20 AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam,
21  AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr,
22  CXXConstructExpr),
23  internal::Matcher<Expr>, ArgMatcher,
24  internal::Matcher<ParmVarDecl>, ParamMatcher) {
25  BoundNodesTreeBuilder Result;
26  // The first argument of an overloaded member operator is the implicit object
27  // argument of the method which should not be matched against a parameter, so
28  // we skip over it here.
29  BoundNodesTreeBuilder Matches;
30  unsigned ArgIndex = cxxOperatorCallExpr(callee(cxxMethodDecl()))
31  .matches(Node, Finder, &Matches)
32  ? 1
33  : 0;
34  int ParamIndex = 0;
35  for (; ArgIndex < Node.getNumArgs(); ++ArgIndex) {
36  BoundNodesTreeBuilder ArgMatches(*Builder);
37  if (ArgMatcher.matches(*(Node.getArg(ArgIndex)->IgnoreParenCasts()), Finder,
38  &ArgMatches)) {
39  BoundNodesTreeBuilder ParamMatches(ArgMatches);
40  if (expr(anyOf(cxxConstructExpr(hasDeclaration(cxxConstructorDecl(
41  hasParameter(ParamIndex, ParamMatcher)))),
42  callExpr(callee(functionDecl(
43  hasParameter(ParamIndex, ParamMatcher))))))
44  .matches(Node, Finder, &ParamMatches)) {
45  Result.addMatch(ParamMatches);
46  *Builder = std::move(Result);
47  return true;
48  }
49  }
50  ++ParamIndex;
51  }
52  return false;
53 }
54 
55 AST_MATCHER(Expr, usedInBooleanContext) {
56  const char *ExprName = "__booleanContextExpr";
57  auto Result =
58  expr(expr().bind(ExprName),
59  anyOf(hasParent(
60  mapAnyOf(varDecl, fieldDecl).with(hasType(booleanType()))),
61  hasParent(cxxConstructorDecl(
62  hasAnyConstructorInitializer(cxxCtorInitializer(
63  withInitializer(expr(equalsBoundNode(ExprName))),
64  forField(hasType(booleanType())))))),
65  hasParent(stmt(anyOf(
66  explicitCastExpr(hasDestinationType(booleanType())),
67  mapAnyOf(ifStmt, doStmt, whileStmt, forStmt,
68  conditionalOperator)
69  .with(hasCondition(expr(equalsBoundNode(ExprName)))),
70  parenListExpr(hasParent(varDecl(hasType(booleanType())))),
71  parenExpr(hasParent(
72  explicitCastExpr(hasDestinationType(booleanType())))),
73  returnStmt(forFunction(returns(booleanType()))),
74  cxxUnresolvedConstructExpr(hasType(booleanType())),
75  invocation(hasAnyArgumentWithParam(
76  expr(equalsBoundNode(ExprName)),
77  parmVarDecl(hasType(booleanType())))),
78  binaryOperator(hasAnyOperatorName("&&", "||")),
79  unaryOperator(hasOperatorName("!")).bind("NegOnSize"))))))
80  .matches(Node, Finder, Builder);
81  Builder->removeBindings([ExprName](const BoundNodesMap &Nodes) {
82  return Nodes.getNode(ExprName).getNodeKind().isNone();
83  });
84  return Result;
85 }
86 AST_MATCHER(CXXConstructExpr, isDefaultConstruction) {
87  return Node.getConstructor()->isDefaultConstructor();
88 }
89 } // namespace ast_matchers
90 namespace tidy {
91 namespace readability {
92 
94 
95 ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,
96  ClangTidyContext *Context)
97  : ClangTidyCheck(Name, Context) {}
98 
99 void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) {
100  const auto ValidContainerRecord = cxxRecordDecl(isSameOrDerivedFrom(
101  namedDecl(
102  has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
103  hasName("size"),
104  returns(qualType(isInteger(), unless(booleanType()),
105  unless(elaboratedType()))))
106  .bind("size")),
107  has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),
108  hasName("empty"), returns(booleanType()))
109  .bind("empty")))
110  .bind("container")));
111 
112  const auto ValidContainerNonTemplateType =
113  qualType(hasUnqualifiedDesugaredType(
114  recordType(hasDeclaration(ValidContainerRecord))));
115  const auto ValidContainerTemplateType =
116  qualType(hasUnqualifiedDesugaredType(templateSpecializationType(
117  hasDeclaration(classTemplateDecl(has(ValidContainerRecord))))));
118 
119  const auto ValidContainer = qualType(
120  anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType));
121 
122  const auto WrongUse =
123  anyOf(hasParent(binaryOperator(
124  isComparisonOperator(),
125  hasEitherOperand(anyOf(integerLiteral(equals(1)),
126  integerLiteral(equals(0)))))
127  .bind("SizeBinaryOp")),
128  usedInBooleanContext());
129 
130  Finder->addMatcher(
131  cxxMemberCallExpr(on(expr(anyOf(hasType(ValidContainer),
132  hasType(pointsTo(ValidContainer)),
133  hasType(references(ValidContainer))))
134  .bind("MemberCallObject")),
135  callee(cxxMethodDecl(hasName("size"))), WrongUse,
136  unless(hasAncestor(cxxMethodDecl(
137  ofClass(equalsBoundNode("container"))))))
138  .bind("SizeCallExpr"),
139  this);
140 
141  Finder->addMatcher(
142  callExpr(has(cxxDependentScopeMemberExpr(
143  hasObjectExpression(
144  expr(anyOf(hasType(ValidContainer),
145  hasType(pointsTo(ValidContainer)),
146  hasType(references(ValidContainer))))
147  .bind("MemberCallObject")),
148  hasMemberName("size"))),
149  WrongUse,
150  unless(hasAncestor(
151  cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
152  .bind("SizeCallExpr"),
153  this);
154 
155  // Comparison to empty string or empty constructor.
156  const auto WrongComparend = anyOf(
157  stringLiteral(hasSize(0)), cxxConstructExpr(isDefaultConstruction()),
158  cxxUnresolvedConstructExpr(argumentCountIs(0)));
159  // Match the object being compared.
160  const auto STLArg =
161  anyOf(unaryOperator(
162  hasOperatorName("*"),
163  hasUnaryOperand(
164  expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))),
165  expr(hasType(ValidContainer)).bind("STLObject"));
166  Finder->addMatcher(
167  binaryOperation(hasAnyOperatorName("==", "!="),
168  hasOperands(WrongComparend,
169  STLArg),
170  unless(hasAncestor(cxxMethodDecl(
171  ofClass(equalsBoundNode("container"))))))
172  .bind("BinCmp"),
173  this);
174 }
175 
176 void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
177  const auto *MemberCall = Result.Nodes.getNodeAs<Expr>("SizeCallExpr");
178  const auto *MemberCallObject =
179  Result.Nodes.getNodeAs<Expr>("MemberCallObject");
180  const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");
181  const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>("BinCmp");
182  const auto *BinCmpRewritten =
183  Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>("BinCmp");
184  const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
185  const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");
186  const auto *E =
187  MemberCallObject
188  ? MemberCallObject
189  : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));
190  FixItHint Hint;
191  std::string ReplacementText = std::string(
192  Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()),
193  *Result.SourceManager, getLangOpts()));
194  if (isBinaryOrTernary(E) || isa<UnaryOperator>(E)) {
195  ReplacementText = "(" + ReplacementText + ")";
196  }
197  if (E->getType()->isPointerType())
198  ReplacementText += "->empty()";
199  else
200  ReplacementText += ".empty()";
201 
202  if (BinCmp) {
203  if (BinCmp->getOperator() == OO_ExclaimEqual) {
204  ReplacementText = "!" + ReplacementText;
205  }
206  Hint =
207  FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
208  } else if (BinCmpTempl) {
209  if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE) {
210  ReplacementText = "!" + ReplacementText;
211  }
212  Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
213  ReplacementText);
214  } else if (BinCmpRewritten) {
215  if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE) {
216  ReplacementText = "!" + ReplacementText;
217  }
218  Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),
219  ReplacementText);
220  } else if (BinaryOp) { // Determine the correct transformation.
221  bool Negation = false;
222  const bool ContainerIsLHS =
223  !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
224  const auto OpCode = BinaryOp->getOpcode();
225  uint64_t Value = 0;
226  if (ContainerIsLHS) {
227  if (const auto *Literal = llvm::dyn_cast<IntegerLiteral>(
228  BinaryOp->getRHS()->IgnoreImpCasts()))
229  Value = Literal->getValue().getLimitedValue();
230  else
231  return;
232  } else {
233  Value =
234  llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts())
235  ->getValue()
236  .getLimitedValue();
237  }
238 
239  // Constant that is not handled.
240  if (Value > 1)
241  return;
242 
243  if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
244  OpCode == BinaryOperatorKind::BO_NE))
245  return;
246 
247  // Always true, no warnings for that.
248  if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
249  (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
250  return;
251 
252  // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
253  if (Value == 1) {
254  if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
255  (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
256  return;
257  if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
258  (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
259  return;
260  }
261 
262  if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
263  Negation = true;
264  if ((OpCode == BinaryOperatorKind::BO_GT ||
265  OpCode == BinaryOperatorKind::BO_GE) &&
266  ContainerIsLHS)
267  Negation = true;
268  if ((OpCode == BinaryOperatorKind::BO_LT ||
269  OpCode == BinaryOperatorKind::BO_LE) &&
270  !ContainerIsLHS)
271  Negation = true;
272 
273  if (Negation)
274  ReplacementText = "!" + ReplacementText;
275  Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
276  ReplacementText);
277 
278  } else {
279  // If there is a conversion above the size call to bool, it is safe to just
280  // replace size with empty.
281  if (const auto *UnaryOp =
282  Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
283  Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
284  ReplacementText);
285  else
286  Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
287  "!" + ReplacementText);
288  }
289 
290  auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
291 
292  if (WarnLoc.isValid()) {
293  diag(WarnLoc, "the 'empty' method should be used to check "
294  "for emptiness instead of 'size'")
295  << Hint;
296  } else {
297  WarnLoc = BinCmpTempl
298  ? BinCmpTempl->getBeginLoc()
299  : (BinCmp ? BinCmp->getBeginLoc()
300  : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc()
301  : SourceLocation{}));
302  diag(WarnLoc, "the 'empty' method should be used to check "
303  "for emptiness instead of comparing to an empty object")
304  << Hint;
305  }
306 
307  const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container");
308  if (const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) {
309  // The definition of the empty() method is the same for all implicit
310  // instantiations. In order to avoid duplicate or inconsistent warnings
311  // (depending on how deduplication is done), we use the same class name
312  // for all implicit instantiations of a template.
313  if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation)
314  Container = CTS->getSpecializedTemplate();
315  }
316  const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty");
317 
318  diag(Empty->getLocation(), "method %0::empty() defined here",
319  DiagnosticIDs::Note)
320  << Container;
321 }
322 
323 } // namespace readability
324 } // namespace tidy
325 } // namespace clang
E
const Expr * E
Definition: AvoidBindCheck.cpp:88
clang::tidy::cppcoreguidelines::getSourceText
static std::string getSourceText(const CXXDestructorDecl &Destructor)
Definition: VirtualClassDestructorCheck.cpp:109
clang::tidy::ClangTidyCheck
Base class for all clang-tidy checks.
Definition: ClangTidyCheck.h:54
clang::tidy::ClangTidyCheck::getLangOpts
const LangOptions & getLangOpts() const
Returns the language options from the context.
Definition: ClangTidyCheck.h:420
clang::tidy::utils::isBinaryOrTernary
bool isBinaryOrTernary(const Expr *E)
Definition: ASTUtils.cpp:27
clang::ast_matchers
Definition: AbseilMatcher.h:14
Builder
CodeCompletionBuilder Builder
Definition: CodeCompletionStringsTests.cpp:36
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:71
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:28
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:25
clang::ast_matchers::AST_MATCHER
AST_MATCHER(Expr, isMacroID)
Definition: PreferIsaOrDynCastInConditionalsCheck.cpp:19
ContainerSizeEmptyCheck.h
clang::doc::serialize::isPublic
static bool isPublic(const clang::AccessSpecifier AS, const clang::Linkage Link)
Definition: Serialize.cpp:223
clang::clangd::Empty
@ Empty
Definition: FuzzyMatch.h:42
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::ast_matchers::AST_POLYMORPHIC_MATCHER_P2
AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam, AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr, CXXConstructExpr), internal::Matcher< Expr >, ArgMatcher, internal::Matcher< ParmVarDecl >, ParamMatcher)
Definition: ContainerSizeEmptyCheck.cpp:20
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:176
clang::tidy::readability::ContainerSizeEmptyCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: ContainerSizeEmptyCheck.cpp:99