brintos

brintos / llvm-project-archived public Read only

0
0
Text · 17.9 KiB · 11faf16 Raw
437 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8#include "ContainerSizeEmptyCheck.h"9#include "../utils/ASTUtils.h"10#include "../utils/Matchers.h"11#include "../utils/OptionsUtils.h"12#include "clang/AST/ASTContext.h"13#include "clang/ASTMatchers/ASTMatchers.h"14#include "clang/Lex/Lexer.h"15#include "llvm/ADT/StringRef.h"16 17using namespace clang::ast_matchers;18 19namespace clang::tidy::readability {20 21namespace {22 23AST_POLYMORPHIC_MATCHER_P2(hasAnyArgumentWithParam,24                           AST_POLYMORPHIC_SUPPORTED_TYPES(CallExpr,25                                                           CXXConstructExpr),26                           ast_matchers::internal::Matcher<Expr>, ArgMatcher,27                           ast_matchers::internal::Matcher<ParmVarDecl>,28                           ParamMatcher) {29  ast_matchers::internal::BoundNodesTreeBuilder Result;30  // The first argument of an overloaded member operator is the implicit object31  // argument of the method which should not be matched against a parameter, so32  // we skip over it here.33  ast_matchers::internal::BoundNodesTreeBuilder Matches;34  unsigned ArgIndex = cxxOperatorCallExpr(callee(cxxMethodDecl()))35                              .matches(Node, Finder, &Matches)36                          ? 137                          : 0;38  int ParamIndex = 0;39  for (; ArgIndex < Node.getNumArgs(); ++ArgIndex) {40    ast_matchers::internal::BoundNodesTreeBuilder ArgMatches(*Builder);41    if (ArgMatcher.matches(*(Node.getArg(ArgIndex)->IgnoreParenCasts()), Finder,42                           &ArgMatches)) {43      ast_matchers::internal::BoundNodesTreeBuilder ParamMatches(ArgMatches);44      if (expr(anyOf(cxxConstructExpr(hasDeclaration(cxxConstructorDecl(45                         hasParameter(ParamIndex, ParamMatcher)))),46                     callExpr(callee(functionDecl(47                         hasParameter(ParamIndex, ParamMatcher))))))48              .matches(Node, Finder, &ParamMatches)) {49        Result.addMatch(ParamMatches);50        *Builder = std::move(Result);51        return true;52      }53    }54    ++ParamIndex;55  }56  return false;57}58 59AST_MATCHER(Expr, usedInBooleanContext) {60  const char *ExprName = "__booleanContextExpr";61  auto Result =62      expr(expr().bind(ExprName),63           anyOf(hasParent(64                     mapAnyOf(varDecl, fieldDecl).with(hasType(booleanType()))),65                 hasParent(cxxConstructorDecl(66                     hasAnyConstructorInitializer(cxxCtorInitializer(67                         withInitializer(expr(equalsBoundNode(ExprName))),68                         forField(hasType(booleanType())))))),69                 hasParent(stmt(anyOf(70                     explicitCastExpr(hasDestinationType(booleanType())),71                     mapAnyOf(ifStmt, doStmt, whileStmt, forStmt,72                              conditionalOperator)73                         .with(hasCondition(expr(equalsBoundNode(ExprName)))),74                     parenListExpr(hasParent(varDecl(hasType(booleanType())))),75                     parenExpr(hasParent(76                         explicitCastExpr(hasDestinationType(booleanType())))),77                     returnStmt(forFunction(returns(booleanType()))),78                     cxxUnresolvedConstructExpr(hasType(booleanType())),79                     invocation(hasAnyArgumentWithParam(80                         expr(equalsBoundNode(ExprName)),81                         parmVarDecl(hasType(booleanType())))),82                     binaryOperator(hasAnyOperatorName("&&", "||")),83                     unaryOperator(hasOperatorName("!")).bind("NegOnSize"))))))84          .matches(Node, Finder, Builder);85  Builder->removeBindings(86      [ExprName](const ast_matchers::internal::BoundNodesMap &Nodes) {87        return Nodes.getNode(ExprName).getNodeKind().isNone();88      });89  return Result;90}91 92AST_MATCHER(QualType, isIntegralType) {93  return Node->isIntegralType(Finder->getASTContext());94}95 96AST_MATCHER_P(UserDefinedLiteral, hasLiteral,97              clang::ast_matchers::internal::Matcher<Expr>, InnerMatcher) {98  const UserDefinedLiteral::LiteralOperatorKind LOK =99      Node.getLiteralOperatorKind();100  if (LOK == UserDefinedLiteral::LOK_Template ||101      LOK == UserDefinedLiteral::LOK_Raw)102    return false;103 104  if (const Expr *CookedLiteral = Node.getCookedLiteral())105    return InnerMatcher.matches(*CookedLiteral, Finder, Builder);106  return false;107}108 109AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl,110              clang::ast_matchers::internal::Matcher<CXXMethodDecl>,111              InnerMatcher) {112  return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder);113}114 115AST_POLYMORPHIC_MATCHER_P(116    matchMemberName,117    AST_POLYMORPHIC_SUPPORTED_TYPES(MemberExpr, CXXDependentScopeMemberExpr),118    std::string, MemberName) {119  if (const auto *E = dyn_cast<MemberExpr>(&Node))120    return E->getMemberDecl()->getName() == MemberName;121 122  if (const auto *E = dyn_cast<CXXDependentScopeMemberExpr>(&Node))123    return E->getMember().getAsString() == MemberName;124 125  return false;126}127 128} // namespace129 130using utils::isBinaryOrTernary;131 132ContainerSizeEmptyCheck::ContainerSizeEmptyCheck(StringRef Name,133                                                 ClangTidyContext *Context)134    : ClangTidyCheck(Name, Context),135      ExcludedComparisonTypes(utils::options::parseStringList(136          Options.get("ExcludedComparisonTypes", "::std::array"))) {}137 138void ContainerSizeEmptyCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {139  Options.store(Opts, "ExcludedComparisonTypes",140                utils::options::serializeStringList(ExcludedComparisonTypes));141}142 143void ContainerSizeEmptyCheck::registerMatchers(MatchFinder *Finder) {144  const auto ValidContainerRecord = cxxRecordDecl(isSameOrDerivedFrom(145      namedDecl(has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),146                                  hasAnyName("size", "length"),147                                  returns(qualType(isIntegralType(),148                                                   unless(booleanType()))))149                        .bind("size")),150                has(cxxMethodDecl(isConst(), parameterCountIs(0), isPublic(),151                                  hasName("empty"), returns(booleanType()))152                        .bind("empty")))153          .bind("container")));154 155  const auto ValidContainerNonTemplateType =156      qualType(hasUnqualifiedDesugaredType(157          recordType(hasDeclaration(ValidContainerRecord))));158  const auto ValidContainerTemplateType = qualType(hasUnqualifiedDesugaredType(159      anyOf(templateSpecializationType(160                hasDeclaration(classTemplateDecl(has(ValidContainerRecord)))),161            injectedClassNameType(hasDeclaration(ValidContainerRecord)))));162 163  const auto ValidContainer = qualType(164      anyOf(ValidContainerNonTemplateType, ValidContainerTemplateType));165 166  const auto WrongUse =167      anyOf(hasParent(binaryOperator(168                          isComparisonOperator(),169                          hasEitherOperand(anyOf(integerLiteral(equals(1)),170                                                 integerLiteral(equals(0)))))171                          .bind("SizeBinaryOp")),172            usedInBooleanContext());173 174  const auto NotInEmptyMethodOfContainer = unless(175      forCallable(cxxMethodDecl(hasCanonicalDecl(equalsBoundNode("empty")))));176 177  Finder->addMatcher(178      cxxMemberCallExpr(179          argumentCountIs(0),180          on(expr(anyOf(hasType(ValidContainer),181                        hasType(pointsTo(ValidContainer)),182                        hasType(references(ValidContainer))))183                 .bind("MemberCallObject")),184          callee(185              cxxMethodDecl(hasAnyName("size", "length")).bind("SizeMethod")),186          WrongUse, NotInEmptyMethodOfContainer)187          .bind("SizeCallExpr"),188      this);189 190  Finder->addMatcher(191      callExpr(192          argumentCountIs(0),193          has(mapAnyOf(memberExpr, cxxDependentScopeMemberExpr)194                  .with(195                      hasObjectExpression(196                          expr(anyOf(hasType(ValidContainer),197                                     hasType(pointsTo(ValidContainer)),198                                     hasType(references(ValidContainer))))199                              .bind("MemberCallObject")),200                      anyOf(matchMemberName("size"), matchMemberName("length")))201                  .bind("MemberExpr")),202          WrongUse, NotInEmptyMethodOfContainer)203          .bind("SizeCallExpr"),204      this);205 206  // Comparison to empty string or empty constructor.207  const auto WrongComparend =208      anyOf(stringLiteral(hasSize(0)),209            userDefinedLiteral(hasLiteral(stringLiteral(hasSize(0)))),210            cxxConstructExpr(argumentCountIs(0)),211            cxxUnresolvedConstructExpr(argumentCountIs(0)));212  // Match the object being compared.213  const auto STLArg =214      anyOf(unaryOperator(215                hasOperatorName("*"),216                hasUnaryOperand(217                    expr(hasType(pointsTo(ValidContainer))).bind("Pointee"))),218            expr(hasType(ValidContainer)).bind("STLObject"));219 220  const auto ExcludedComparisonTypesMatcher = qualType(anyOf(221      hasDeclaration(222          cxxRecordDecl(matchers::matchesAnyListedName(ExcludedComparisonTypes))223              .bind("excluded")),224      hasCanonicalType(hasDeclaration(225          cxxRecordDecl(matchers::matchesAnyListedName(ExcludedComparisonTypes))226              .bind("excluded")))));227  const auto SameExcludedComparisonTypesMatcher =228      qualType(anyOf(hasDeclaration(cxxRecordDecl(equalsBoundNode("excluded"))),229                     hasCanonicalType(hasDeclaration(230                         cxxRecordDecl(equalsBoundNode("excluded"))))));231 232  Finder->addMatcher(233      binaryOperation(234          hasAnyOperatorName("==", "!="), hasOperands(WrongComparend, STLArg),235          unless(allOf(hasLHS(hasType(ExcludedComparisonTypesMatcher)),236                       hasRHS(hasType(SameExcludedComparisonTypesMatcher)))),237          NotInEmptyMethodOfContainer)238          .bind("BinCmp"),239      this);240}241 242void ContainerSizeEmptyCheck::check(const MatchFinder::MatchResult &Result) {243  const auto *MemberCall = Result.Nodes.getNodeAs<Expr>("SizeCallExpr");244  const auto *MemberCallObject =245      Result.Nodes.getNodeAs<Expr>("MemberCallObject");246  const auto *BinCmp = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("BinCmp");247  const auto *BinCmpTempl = Result.Nodes.getNodeAs<BinaryOperator>("BinCmp");248  const auto *BinCmpRewritten =249      Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>("BinCmp");250  const auto *BinaryOp = Result.Nodes.getNodeAs<BinaryOperator>("SizeBinaryOp");251  const auto *Pointee = Result.Nodes.getNodeAs<Expr>("Pointee");252  const auto *E =253      MemberCallObject254          ? MemberCallObject255          : (Pointee ? Pointee : Result.Nodes.getNodeAs<Expr>("STLObject"));256  FixItHint Hint;257  std::string ReplacementText =258      E->isImplicitCXXThis()259          ? ""260          : std::string(Lexer::getSourceText(261                CharSourceRange::getTokenRange(E->getSourceRange()),262                *Result.SourceManager, getLangOpts()));263  const auto *OpCallExpr = dyn_cast<CXXOperatorCallExpr>(E);264  if (isBinaryOrTernary(E) || isa<UnaryOperator>(E) ||265      (OpCallExpr && (OpCallExpr->getOperator() == OO_Star))) {266    ReplacementText = "(" + ReplacementText + ")";267  }268  if (OpCallExpr &&269      OpCallExpr->getOperator() == OverloadedOperatorKind::OO_Arrow) {270    // This can happen if the object is a smart pointer. Don't add anything271    // because a '->' is already there (PR#51776), just call the method.272    ReplacementText += "empty()";273  } else if (E->isImplicitCXXThis()) {274    ReplacementText += "empty()";275  } else if (E->getType()->isPointerType())276    ReplacementText += "->empty()";277  else278    ReplacementText += ".empty()";279 280  if (BinCmp) {281    if (BinCmp->getOperator() == OO_ExclaimEqual) {282      ReplacementText = "!" + ReplacementText;283    }284    Hint =285        FixItHint::CreateReplacement(BinCmp->getSourceRange(), ReplacementText);286  } else if (BinCmpTempl) {287    if (BinCmpTempl->getOpcode() == BinaryOperatorKind::BO_NE) {288      ReplacementText = "!" + ReplacementText;289    }290    Hint = FixItHint::CreateReplacement(BinCmpTempl->getSourceRange(),291                                        ReplacementText);292  } else if (BinCmpRewritten) {293    if (BinCmpRewritten->getOpcode() == BinaryOperatorKind::BO_NE) {294      ReplacementText = "!" + ReplacementText;295    }296    Hint = FixItHint::CreateReplacement(BinCmpRewritten->getSourceRange(),297                                        ReplacementText);298  } else if (BinaryOp) { // Determine the correct transformation.299    const auto *LiteralLHS =300        llvm::dyn_cast<IntegerLiteral>(BinaryOp->getLHS()->IgnoreImpCasts());301    const auto *LiteralRHS =302        llvm::dyn_cast<IntegerLiteral>(BinaryOp->getRHS()->IgnoreImpCasts());303    const bool ContainerIsLHS = !LiteralLHS;304 305    uint64_t Value = 0;306    if (LiteralLHS)307      Value = LiteralLHS->getValue().getLimitedValue();308    else if (LiteralRHS)309      Value = LiteralRHS->getValue().getLimitedValue();310    else311      return;312 313    bool Negation = false;314    const auto OpCode = BinaryOp->getOpcode();315 316    // Constant that is not handled.317    if (Value > 1)318      return;319 320    if (Value == 1 && (OpCode == BinaryOperatorKind::BO_EQ ||321                       OpCode == BinaryOperatorKind::BO_NE))322      return;323 324    // Always true/false, no warnings for that.325    if (Value == 0) {326      if ((OpCode == BinaryOperatorKind::BO_GT && !ContainerIsLHS) ||327          (OpCode == BinaryOperatorKind::BO_LT && ContainerIsLHS) ||328          (OpCode == BinaryOperatorKind::BO_LE && !ContainerIsLHS) ||329          (OpCode == BinaryOperatorKind::BO_GE && ContainerIsLHS))330        return;331    }332 333    // Do not warn for size > 1, 1 < size, size <= 1, 1 >= size.334    if (Value == 1) {335      if ((OpCode == BinaryOperatorKind::BO_GT && ContainerIsLHS) ||336          (OpCode == BinaryOperatorKind::BO_LT && !ContainerIsLHS))337        return;338      if ((OpCode == BinaryOperatorKind::BO_LE && ContainerIsLHS) ||339          (OpCode == BinaryOperatorKind::BO_GE && !ContainerIsLHS))340        return;341    }342 343    // Do not warn for size < 1, 1 > size, size <= 0, 0 >= size for non signed344    // types345    if ((OpCode == BinaryOperatorKind::BO_GT && Value == 1 &&346         !ContainerIsLHS) ||347        (OpCode == BinaryOperatorKind::BO_LT && Value == 1 && ContainerIsLHS) ||348        (OpCode == BinaryOperatorKind::BO_GE && Value == 0 &&349         !ContainerIsLHS) ||350        (OpCode == BinaryOperatorKind::BO_LE && Value == 0 && ContainerIsLHS)) {351      const Expr *Container = ContainerIsLHS352                                  ? BinaryOp->getLHS()->IgnoreImpCasts()353                                  : BinaryOp->getRHS()->IgnoreImpCasts();354      if (Container->getType()355              .getCanonicalType()356              .getNonReferenceType()357              ->isSignedIntegerType())358        return;359    }360 361    if (OpCode == BinaryOperatorKind::BO_NE && Value == 0)362      Negation = true;363 364    if ((OpCode == BinaryOperatorKind::BO_GT ||365         OpCode == BinaryOperatorKind::BO_GE) &&366        ContainerIsLHS)367      Negation = true;368 369    if ((OpCode == BinaryOperatorKind::BO_LT ||370         OpCode == BinaryOperatorKind::BO_LE) &&371        !ContainerIsLHS)372      Negation = true;373 374    if (Negation)375      ReplacementText = "!" + ReplacementText;376    Hint = FixItHint::CreateReplacement(BinaryOp->getSourceRange(),377                                        ReplacementText);378 379  } else {380    // If there is a conversion above the size call to bool, it is safe to just381    // replace size with empty.382    if (const auto *UnaryOp =383            Result.Nodes.getNodeAs<UnaryOperator>("NegOnSize"))384      Hint = FixItHint::CreateReplacement(UnaryOp->getSourceRange(),385                                          ReplacementText);386    else387      Hint = FixItHint::CreateReplacement(MemberCall->getSourceRange(),388                                          "!" + ReplacementText);389  }390 391  auto WarnLoc = MemberCall ? MemberCall->getBeginLoc() : SourceLocation{};392 393  if (WarnLoc.isValid()) {394    auto Diag = diag(WarnLoc, "the 'empty' method should be used to check "395                              "for emptiness instead of %0");396    if (const auto *SizeMethod =397            Result.Nodes.getNodeAs<NamedDecl>("SizeMethod"))398      Diag << SizeMethod;399    else if (const auto *DependentExpr =400                 Result.Nodes.getNodeAs<CXXDependentScopeMemberExpr>(401                     "MemberExpr"))402      Diag << DependentExpr->getMember();403    else if (const auto *ME =404                 Result.Nodes.getNodeAs<MemberExpr>("MemberExpr")) {405      Diag << ME->getMemberNameInfo().getName();406    } else407      Diag << "unknown method";408    Diag << Hint;409  } else {410    WarnLoc = BinCmpTempl411                  ? BinCmpTempl->getBeginLoc()412                  : (BinCmp ? BinCmp->getBeginLoc()413                            : (BinCmpRewritten ? BinCmpRewritten->getBeginLoc()414                                               : SourceLocation{}));415    diag(WarnLoc, "the 'empty' method should be used to check "416                  "for emptiness instead of comparing to an empty object")417        << Hint;418  }419 420  const auto *Container = Result.Nodes.getNodeAs<NamedDecl>("container");421  if (const auto *CTS = dyn_cast<ClassTemplateSpecializationDecl>(Container)) {422    // The definition of the empty() method is the same for all implicit423    // instantiations. In order to avoid duplicate or inconsistent warnings424    // (depending on how deduplication is done), we use the same class name425    // for all implicit instantiations of a template.426    if (CTS->getSpecializationKind() == TSK_ImplicitInstantiation)427      Container = CTS->getSpecializedTemplate();428  }429  const auto *Empty = Result.Nodes.getNodeAs<FunctionDecl>("empty");430 431  diag(Empty->getLocation(), "method %0::empty() defined here",432       DiagnosticIDs::Note)433      << Container;434}435 436} // namespace clang::tidy::readability437