brintos

brintos / llvm-project-archived public Read only

0
0
Text · 10.8 KiB · eebd609 Raw
273 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 9#include "UseStartsEndsWithCheck.h"10 11#include "../utils/ASTUtils.h"12#include "../utils/Matchers.h"13#include "clang/ASTMatchers/ASTMatchers.h"14#include "clang/Lex/Lexer.h"15 16#include <string>17 18using namespace clang::ast_matchers;19 20namespace clang::tidy::modernize {21 22static bool isNegativeComparison(const Expr *ComparisonExpr) {23  if (const auto *Op = llvm::dyn_cast<BinaryOperator>(ComparisonExpr))24    return Op->getOpcode() == BO_NE;25 26  if (const auto *Op = llvm::dyn_cast<CXXOperatorCallExpr>(ComparisonExpr))27    return Op->getOperator() == OO_ExclaimEqual;28 29  if (const auto *Op =30          llvm::dyn_cast<CXXRewrittenBinaryOperator>(ComparisonExpr))31    return Op->getOperator() == BO_NE;32 33  return false;34}35 36namespace {37 38struct NotLengthExprForStringNode {39  NotLengthExprForStringNode(std::string ID, DynTypedNode Node,40                             ASTContext *Context)41      : ID(std::move(ID)), Node(std::move(Node)), Context(Context) {}42  bool operator()(const internal::BoundNodesMap &Nodes) const {43    // Match a string literal and an integer size or strlen() call.44    if (const auto *StringLiteralNode = Nodes.getNodeAs<StringLiteral>(ID)) {45      if (const auto *IntegerLiteralSizeNode = Node.get<IntegerLiteral>()) {46        return StringLiteralNode->getLength() !=47               IntegerLiteralSizeNode->getValue().getZExtValue();48      }49 50      if (const auto *StrlenNode = Node.get<CallExpr>()) {51        if (StrlenNode->getDirectCallee()->getName() != "strlen" ||52            StrlenNode->getNumArgs() != 1) {53          return true;54        }55 56        if (const auto *StrlenArgNode = dyn_cast<StringLiteral>(57                StrlenNode->getArg(0)->IgnoreParenImpCasts())) {58          return StrlenArgNode->getLength() != StringLiteralNode->getLength();59        }60      }61    }62 63    // Match a string variable and a call to length() or size().64    if (const auto *ExprNode = Nodes.getNodeAs<Expr>(ID)) {65      if (const auto *MemberCallNode = Node.get<CXXMemberCallExpr>()) {66        const CXXMethodDecl *MethodDeclNode = MemberCallNode->getMethodDecl();67        const StringRef Name = MethodDeclNode->getName();68        if (!MethodDeclNode->isConst() || MethodDeclNode->getNumParams() != 0 ||69            (Name != "size" && Name != "length")) {70          return true;71        }72 73        if (const auto *OnNode =74                dyn_cast<Expr>(MemberCallNode->getImplicitObjectArgument())) {75          return !utils::areStatementsIdentical(OnNode->IgnoreParenImpCasts(),76                                                ExprNode->IgnoreParenImpCasts(),77                                                *Context);78        }79      }80    }81 82    return true;83  }84 85private:86  std::string ID;87  DynTypedNode Node;88  ASTContext *Context;89};90 91AST_MATCHER_P(Expr, lengthExprForStringNode, std::string, ID) {92  return Builder->removeBindings(NotLengthExprForStringNode(93      ID, DynTypedNode::create(Node), &(Finder->getASTContext())));94}95 96} // namespace97 98UseStartsEndsWithCheck::UseStartsEndsWithCheck(StringRef Name,99                                               ClangTidyContext *Context)100    : ClangTidyCheck(Name, Context) {}101 102void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {103  const auto ZeroLiteral = integerLiteral(equals(0));104 105  const auto ClassTypeWithMethod = [](const StringRef MethodBoundName,106                                      const auto... Methods) {107    return cxxRecordDecl(anyOf(108        hasMethod(cxxMethodDecl(isConst(), parameterCountIs(1),109                                returns(booleanType()), hasAnyName(Methods))110                      .bind(MethodBoundName))...));111  };112 113  const auto OnClassWithStartsWithFunction =114      ClassTypeWithMethod("starts_with_fun", "starts_with", "startsWith",115                          "startswith", "StartsWith");116 117  const auto OnClassWithEndsWithFunction = ClassTypeWithMethod(118      "ends_with_fun", "ends_with", "endsWith", "endswith", "EndsWith");119 120  // Case 1: X.find(Y, [0], [LEN(Y)]) [!=]= 0 -> starts_with.121  const auto FindExpr = cxxMemberCallExpr(122      callee(123          cxxMethodDecl(hasName("find"), ofClass(OnClassWithStartsWithFunction))124              .bind("find_fun")),125      hasArgument(0, expr().bind("needle")),126      anyOf(127          // Detect the expression: X.find(Y);128          argumentCountIs(1),129          // Detect the expression: X.find(Y, 0);130          allOf(argumentCountIs(2), hasArgument(1, ZeroLiteral)),131          // Detect the expression: X.find(Y, 0, LEN(Y));132          allOf(argumentCountIs(3), hasArgument(1, ZeroLiteral),133                hasArgument(2, lengthExprForStringNode("needle")))));134 135  // Case 2: X.rfind(Y, 0, [LEN(Y)]) [!=]= 0 -> starts_with.136  const auto RFindExpr = cxxMemberCallExpr(137      callee(cxxMethodDecl(hasName("rfind"),138                           ofClass(OnClassWithStartsWithFunction))139                 .bind("find_fun")),140      hasArgument(0, expr().bind("needle")),141      anyOf(142          // Detect the expression: X.rfind(Y, 0);143          allOf(argumentCountIs(2), hasArgument(1, ZeroLiteral)),144          // Detect the expression: X.rfind(Y, 0, LEN(Y));145          allOf(argumentCountIs(3), hasArgument(1, ZeroLiteral),146                hasArgument(2, lengthExprForStringNode("needle")))));147 148  // Case 3: X.compare(0, LEN(Y), Y) [!=]= 0 -> starts_with.149  const auto CompareExpr = cxxMemberCallExpr(150      argumentCountIs(3), hasArgument(0, ZeroLiteral),151      callee(cxxMethodDecl(hasName("compare"),152                           ofClass(OnClassWithStartsWithFunction))153                 .bind("find_fun")),154      hasArgument(2, expr().bind("needle")),155      hasArgument(1, lengthExprForStringNode("needle")));156 157  // Case 4: X.compare(LEN(X) - LEN(Y), LEN(Y), Y) [!=]= 0 -> ends_with.158  const auto CompareEndsWithExpr = cxxMemberCallExpr(159      argumentCountIs(3),160      callee(cxxMethodDecl(hasName("compare"),161                           ofClass(OnClassWithEndsWithFunction))162                 .bind("find_fun")),163      on(expr().bind("haystack")), hasArgument(2, expr().bind("needle")),164      hasArgument(1, lengthExprForStringNode("needle")),165      hasArgument(0,166                  binaryOperator(hasOperatorName("-"),167                                 hasLHS(lengthExprForStringNode("haystack")),168                                 hasRHS(lengthExprForStringNode("needle")))));169 170  // All cases comparing to 0.171  Finder->addMatcher(172      binaryOperator(173          matchers::isEqualityOperator(),174          hasOperands(cxxMemberCallExpr(anyOf(FindExpr, RFindExpr, CompareExpr,175                                              CompareEndsWithExpr))176                          .bind("find_expr"),177                      ZeroLiteral))178          .bind("expr"),179      this);180 181  // Case 5: X.rfind(Y) [!=]= LEN(X) - LEN(Y) -> ends_with.182  Finder->addMatcher(183      binaryOperator(184          matchers::isEqualityOperator(),185          hasOperands(186              cxxMemberCallExpr(187                  anyOf(188                      argumentCountIs(1),189                      allOf(argumentCountIs(2),190                            hasArgument(191                                1,192                                anyOf(declRefExpr(to(varDecl(hasName("npos")))),193                                      memberExpr(member(hasName("npos"))))))),194                  callee(cxxMethodDecl(hasName("rfind"),195                                       ofClass(OnClassWithEndsWithFunction))196                             .bind("find_fun")),197                  on(expr().bind("haystack")),198                  hasArgument(0, expr().bind("needle")))199                  .bind("find_expr"),200              binaryOperator(hasOperatorName("-"),201                             hasLHS(lengthExprForStringNode("haystack")),202                             hasRHS(lengthExprForStringNode("needle")))))203          .bind("expr"),204      this);205 206  // Case 6: X.substr(0, LEN(Y)) [!=]= Y -> starts_with.207  Finder->addMatcher(208      binaryOperation(209          hasAnyOperatorName("==", "!="),210          hasOperands(211              expr().bind("needle"),212              cxxMemberCallExpr(213                  argumentCountIs(2), hasArgument(0, ZeroLiteral),214                  hasArgument(1, lengthExprForStringNode("needle")),215                  callee(cxxMethodDecl(hasName("substr"),216                                       ofClass(OnClassWithStartsWithFunction))217                             .bind("find_fun")))218                  .bind("find_expr")))219          .bind("expr"),220      this);221}222 223void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {224  const auto *ComparisonExpr = Result.Nodes.getNodeAs<Expr>("expr");225  const auto *FindExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>("find_expr");226  const auto *FindFun = Result.Nodes.getNodeAs<CXXMethodDecl>("find_fun");227  const auto *SearchExpr = Result.Nodes.getNodeAs<Expr>("needle");228  const auto *StartsWithFunction =229      Result.Nodes.getNodeAs<CXXMethodDecl>("starts_with_fun");230  const auto *EndsWithFunction =231      Result.Nodes.getNodeAs<CXXMethodDecl>("ends_with_fun");232  assert(bool(StartsWithFunction) != bool(EndsWithFunction));233 234  const CXXMethodDecl *ReplacementFunction =235      StartsWithFunction ? StartsWithFunction : EndsWithFunction;236 237  if (ComparisonExpr->getBeginLoc().isMacroID() ||238      FindExpr->getBeginLoc().isMacroID())239    return;240 241  // Make sure FindExpr->getArg(0) can be used to make a range in the FitItHint.242  if (FindExpr->getNumArgs() == 0)243    return;244 245  // Retrieve the source text of the search expression.246  const auto SearchExprText = Lexer::getSourceText(247      CharSourceRange::getTokenRange(SearchExpr->getSourceRange()),248      *Result.SourceManager, Result.Context->getLangOpts());249 250  auto Diagnostic = diag(FindExpr->getExprLoc(), "use %0 instead of %1")251                    << ReplacementFunction->getName() << FindFun->getName();252 253  // Remove everything before the function call.254  Diagnostic << FixItHint::CreateRemoval(CharSourceRange::getCharRange(255      ComparisonExpr->getBeginLoc(), FindExpr->getBeginLoc()));256 257  // Rename the function to `starts_with` or `ends_with`.258  Diagnostic << FixItHint::CreateReplacement(FindExpr->getExprLoc(),259                                             ReplacementFunction->getName());260 261  // Replace arguments and everything after the function call.262  Diagnostic << FixItHint::CreateReplacement(263      CharSourceRange::getTokenRange(FindExpr->getArg(0)->getBeginLoc(),264                                     ComparisonExpr->getEndLoc()),265      (SearchExprText + ")").str());266 267  // Add negation if necessary.268  if (isNegativeComparison(ComparisonExpr))269    Diagnostic << FixItHint::CreateInsertion(FindExpr->getBeginLoc(), "!");270}271 272} // namespace clang::tidy::modernize273