• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 //===----------------------------------------------------------------------===//
8 #include "ContainerSizeEmptyCheck.h"
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 
22 using utils::IsBinaryOrTernary;
23 
ContainerSizeEmptyCheck(StringRef Name,ClangTidyContext * Context)24 ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,
25                                                  ClangTidyContext *Context)
26     : ClangTidyCheck(Name, Context) {}
27 
registerMatchers(MatchFinder * Finder)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 
check(const MatchFinder::MatchResult & Result)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
229