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 ast_matchers {
AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam,AST_POLYMORPHIC_SUPPORTED_TYPES (CallExpr,CXXConstructExpr),internal::Matcher<Expr>,ArgMatcher,internal::Matcher<ParmVarDecl>,ParamMatcher)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 
AST_MATCHER(Expr,usedInBooleanContext)55 AST_MATCHER(Expr, usedInBooleanContext) {
56   const char *ExprName = "__booleanContextExpr";
57   auto Result =
58       expr(expr().bind(ExprName),
59            anyOf(hasParent(varDecl(hasType(booleanType()))),
60                  hasParent(cxxConstructorDecl(
61                      hasAnyConstructorInitializer(cxxCtorInitializer(
62                          withInitializer(expr(equalsBoundNode(ExprName))),
63                          forField(hasType(booleanType())))))),
64                  hasParent(fieldDecl(hasType(booleanType()))),
65                  hasParent(stmt(anyOf(
66                      explicitCastExpr(hasDestinationType(booleanType())),
67                      ifStmt(hasCondition(expr(equalsBoundNode(ExprName)))),
68                      doStmt(hasCondition(expr(equalsBoundNode(ExprName)))),
69                      whileStmt(hasCondition(expr(equalsBoundNode(ExprName)))),
70                      forStmt(hasCondition(expr(equalsBoundNode(ExprName)))),
71                      conditionalOperator(
72                          hasCondition(expr(equalsBoundNode(ExprName)))),
73                      parenListExpr(hasParent(varDecl(hasType(booleanType())))),
74                      parenExpr(hasParent(
75                          explicitCastExpr(hasDestinationType(booleanType())))),
76                      returnStmt(forFunction(returns(booleanType()))),
77                      cxxUnresolvedConstructExpr(hasType(booleanType())),
78                      callExpr(hasAnyArgumentWithParam(
79                          expr(equalsBoundNode(ExprName)),
80                          parmVarDecl(hasType(booleanType())))),
81                      binaryOperator(hasAnyOperatorName("&&", "||")),
82                      unaryOperator(hasOperatorName("!")).bind("NegOnSize"))))))
83           .matches(Node, Finder, Builder);
84   Builder->removeBindings([ExprName](const BoundNodesMap &Nodes) {
85     return Nodes.getNode(ExprName).getNodeKind().isNone();
86   });
87   return Result;
88 }
89 } // namespace ast_matchers
90 namespace tidy {
91 namespace readability {
92 
93 using utils::IsBinaryOrTernary;
94 
ContainerSizeEmptyCheck(StringRef Name,ClangTidyContext * Context)95 ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,
96                                                  ClangTidyContext *Context)
97     : ClangTidyCheck(Name, Context) {}
98 
registerMatchers(MatchFinder * Finder)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 = traverse(
123       TK_AsIs,
124       anyOf(
125           hasParent(binaryOperator(isComparisonOperator(),
126                                    hasEitherOperand(ignoringImpCasts(
127                                        anyOf(integerLiteral(equals(1)),
128                                              integerLiteral(equals(0))))))
129                         .bind("SizeBinaryOp")),
130           hasParent(implicitCastExpr(
131               hasImplicitDestinationType(booleanType()),
132               anyOf(hasParent(
133                         unaryOperator(hasOperatorName("!")).bind("NegOnSize")),
134                     anything()))),
135           usedInBooleanContext()));
136 
137   Finder->addMatcher(
138       cxxMemberCallExpr(unless(isInTemplateInstantiation()),
139                         on(expr(anyOf(hasType(ValidContainer),
140                                       hasType(pointsTo(ValidContainer)),
141                                       hasType(references(ValidContainer))))
142                                .bind("MemberCallObject")),
143                         callee(cxxMethodDecl(hasName("size"))), WrongUse,
144                         unless(hasAncestor(cxxMethodDecl(
145                             ofClass(equalsBoundNode("container"))))))
146           .bind("SizeCallExpr"),
147       this);
148 
149   Finder->addMatcher(
150       callExpr(has(cxxDependentScopeMemberExpr(
151                    hasObjectExpression(
152                        expr(anyOf(hasType(ValidContainer),
153                                   hasType(pointsTo(ValidContainer)),
154                                   hasType(references(ValidContainer))))
155                            .bind("MemberCallObject")),
156                    hasMemberName("size"))),
157                WrongUse,
158                unless(hasAncestor(
159                    cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
160           .bind("SizeCallExpr"),
161       this);
162 
163   // Empty constructor matcher.
164   const auto DefaultConstructor = cxxConstructExpr(
165           hasDeclaration(cxxConstructorDecl(isDefaultConstructor())));
166   // Comparison to empty string or empty constructor.
167   const auto WrongComparend = anyOf(
168       ignoringImpCasts(stringLiteral(hasSize(0))),
169       ignoringImpCasts(cxxBindTemporaryExpr(has(DefaultConstructor))),
170       ignoringImplicit(DefaultConstructor),
171       cxxConstructExpr(hasDeclaration(cxxConstructorDecl(isCopyConstructor())),
172                        has(expr(ignoringImpCasts(DefaultConstructor)))),
173       cxxConstructExpr(hasDeclaration(cxxConstructorDecl(isMoveConstructor())),
174                        has(expr(ignoringImpCasts(DefaultConstructor)))),
175       cxxUnresolvedConstructExpr(argumentCountIs(0)));
176   // Match the object being compared.
177   const auto STLArg =
178       anyOf(unaryOperator(
179                 hasOperatorName("*"),
180                 hasUnaryOperand(
181                     expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))),
182             expr(hasType(ValidContainer)).bind("STLObject"));
183   Finder->addMatcher(
184       cxxOperatorCallExpr(
185           unless(isInTemplateInstantiation()),
186           hasAnyOverloadedOperatorName("==", "!="),
187           anyOf(allOf(hasArgument(0, WrongComparend), hasArgument(1, STLArg)),
188                 allOf(hasArgument(0, STLArg), hasArgument(1, WrongComparend))),
189           unless(hasAncestor(
190               cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
191           .bind("BinCmp"),
192       this);
193   Finder->addMatcher(
194       binaryOperator(hasAnyOperatorName("==", "!="),
195                      anyOf(allOf(hasLHS(WrongComparend), hasRHS(STLArg)),
196                            allOf(hasLHS(STLArg), hasRHS(WrongComparend))),
197                      unless(hasAncestor(
198                          cxxMethodDecl(ofClass(equalsBoundNode("container"))))))
199           .bind("BinCmp"),
200       this);
201 }
202 
check(const MatchFinder::MatchResult & Result)203 void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {
204   const auto *MemberCall = Result.Nodes.getNodeAs<Expr>("SizeCallExpr");
205   const auto *MemberCallObject =
206       Result.Nodes.getNodeAs<Expr>("MemberCallObject");
207   const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");
208   const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>("BinCmp");
209   const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");
210   const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");
211   const auto *E =
212       MemberCallObject
213           ? MemberCallObject
214           : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));
215   FixItHint Hint;
216   std::string ReplacementText = std::string(
217       Lexer::getSourceText(CharSourceRange::getTokenRange(E->getSourceRange()),
218                            *Result.SourceManager, getLangOpts()));
219   if (IsBinaryOrTernary(E) || isa<UnaryOperator>(E)) {
220     ReplacementText = "(" + ReplacementText + ")";
221   }
222   if (E->getType()->isPointerType())
223     ReplacementText += "->empty()";
224   else
225     ReplacementText += ".empty()";
226 
227   if (BinCmp) {
228     if (BinCmp->getOperator() == OO_ExclaimEqual) {
229       ReplacementText = "!" + ReplacementText;
230     }
231     Hint =
232         FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);
233   } else if (BinCmpTempl) {
234     if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE) {
235       ReplacementText = "!" + ReplacementText;
236     }
237     Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),
238                                         ReplacementText);
239   } else if (BinaryOp) { // Determine the correct transformation.
240     bool Negation = false;
241     const bool ContainerIsLHS =
242         !llvm::isa<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());
243     const auto OpCode = BinaryOp->getOpcode();
244     uint64_t Value = 0;
245     if (ContainerIsLHS) {
246       if (const auto *Literal = llvm::dyn_cast<IntegerLiteral>(
247               BinaryOp->getRHS()->IgnoreImpCasts()))
248         Value = Literal->getValue().getLimitedValue();
249       else
250         return;
251     } else {
252       Value =
253           llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts())
254               ->getValue()
255               .getLimitedValue();
256     }
257 
258     // Constant that is not handled.
259     if (Value > 1)
260       return;
261 
262     if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||
263                        OpCode == BinaryOperatorKind::BO_NE))
264       return;
265 
266     // Always true, no warnings for that.
267     if ((OpCode == BinaryOperatorKind::BO_GE && Value == 0 && ContainerIsLHS) ||
268         (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && !ContainerIsLHS))
269       return;
270 
271     // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.
272     if (Value == 1) {
273       if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||
274           (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))
275         return;
276       if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||
277           (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))
278         return;
279     }
280 
281     if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)
282       Negation = true;
283     if ((OpCode == BinaryOperatorKind::BO_GT ||
284          OpCode == BinaryOperatorKind::BO_GE) &&
285         ContainerIsLHS)
286       Negation = true;
287     if ((OpCode == BinaryOperatorKind::BO_LT ||
288          OpCode == BinaryOperatorKind::BO_LE) &&
289         !ContainerIsLHS)
290       Negation = true;
291 
292     if (Negation)
293       ReplacementText = "!" + ReplacementText;
294     Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),
295                                         ReplacementText);
296 
297   } else {
298     // If there is a conversion above the size call to bool, it is safe to just
299     // replace size with empty.
300     if (const auto *UnaryOp =
301             Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))
302       Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),
303                                           ReplacementText);
304     else
305       Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),
306                                           "!" + ReplacementText);
307   }
308 
309   auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};
310 
311   if (WarnLoc.isValid()) {
312     diag(WarnLoc, "the 'empty' method should be used to check "
313                   "for emptiness instead of 'size'")
314         << Hint;
315   } else {
316     WarnLoc = BinCmpTempl ? BinCmpTempl->getBeginLoc()
317                           : (BinCmp ? BinCmp->getBeginLoc() : SourceLocation{});
318     diag(WarnLoc, "the 'empty' method should be used to check "
319                   "for emptiness instead of comparing to an empty object")
320         << Hint;
321   }
322 
323   const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container");
324   if (const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) {
325     // The definition of the empty() method is the same for all implicit
326     // instantiations. In order to avoid duplicate or inconsistent warnings
327     // (depending on how deduplication is done), we use the same class name
328     // for all implicit instantiations of a template.
329     if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation)
330       Container = CTS->getSpecializedTemplate();
331   }
332   const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty");
333 
334   diag(Empty->getLocation(), "method %0::empty() defined here",
335        DiagnosticIDs::Note)
336       << Container;
337 }
338 
339 } // namespace readability
340 } // namespace tidy
341 } // namespace clang
342