brintos

brintos / llvm-project-archived public Read only

0
0
Text · 6.5 KiB · efcf13d Raw
150 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 "ContainerContainsCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12#include "clang/Lex/Lexer.h"13 14using namespace clang::ast_matchers;15 16namespace clang::tidy::readability {17void ContainerContainsCheck::registerMatchers(MatchFinder *Finder) {18  const auto Literal0 = integerLiteral(equals(0));19  const auto Literal1 = integerLiteral(equals(1));20 21  const auto ClassWithContains = cxxRecordDecl(22      hasMethod(cxxMethodDecl(isConst(), parameterCountIs(1), isPublic(),23                              unless(isDeleted()), returns(booleanType()),24                              hasAnyName("contains", "Contains"))25                    .bind("contains_fun")));26 27  const auto CountCall =28      cxxMemberCallExpr(argumentCountIs(1),29                        callee(cxxMethodDecl(hasAnyName("count", "Count"),30                                             ofClass(ClassWithContains))))31          .bind("call");32 33  const auto FindCall =34      // Either one argument, or assume the second argument is the position to35      // start searching from.36      cxxMemberCallExpr(37          anyOf(argumentCountIs(1),38                allOf(argumentCountIs(2), hasArgument(1, Literal0))),39          callee(cxxMethodDecl(hasAnyName("find", "Find"),40                               ofClass(ClassWithContains))))41          .bind("call");42 43  const auto EndCall = cxxMemberCallExpr(44      argumentCountIs(0), callee(cxxMethodDecl(hasAnyName("end", "End"),45                                               ofClass(ClassWithContains))));46 47  const auto StringNpos = anyOf(declRefExpr(to(varDecl(hasName("npos")))),48                                memberExpr(member(hasName("npos"))));49 50  auto AddSimpleMatcher = [&](const auto &Matcher) {51    Finder->addMatcher(traverse(TK_IgnoreUnlessSpelledInSource, Matcher), this);52  };53 54  // Find membership tests which use `count()`.55  Finder->addMatcher(implicitCastExpr(hasImplicitDestinationType(booleanType()),56                                      hasSourceExpression(CountCall))57                         .bind("positiveComparison"),58                     this);59  AddSimpleMatcher(60      binaryOperation(hasOperatorName("!="), hasOperands(CountCall, Literal0))61          .bind("positiveComparison"));62  AddSimpleMatcher(63      binaryOperation(hasLHS(CountCall), hasOperatorName(">"), hasRHS(Literal0))64          .bind("positiveComparison"));65  AddSimpleMatcher(66      binaryOperation(hasLHS(Literal0), hasOperatorName("<"), hasRHS(CountCall))67          .bind("positiveComparison"));68  AddSimpleMatcher(binaryOperation(hasLHS(CountCall), hasOperatorName(">="),69                                   hasRHS(Literal1))70                       .bind("positiveComparison"));71  AddSimpleMatcher(binaryOperation(hasLHS(Literal1), hasOperatorName("<="),72                                   hasRHS(CountCall))73                       .bind("positiveComparison"));74 75  // Find inverted membership tests which use `count()`.76  AddSimpleMatcher(77      binaryOperation(hasOperatorName("=="), hasOperands(CountCall, Literal0))78          .bind("negativeComparison"));79  AddSimpleMatcher(binaryOperation(hasLHS(CountCall), hasOperatorName("<="),80                                   hasRHS(Literal0))81                       .bind("negativeComparison"));82  AddSimpleMatcher(binaryOperation(hasLHS(Literal0), hasOperatorName(">="),83                                   hasRHS(CountCall))84                       .bind("negativeComparison"));85  AddSimpleMatcher(86      binaryOperation(hasLHS(CountCall), hasOperatorName("<"), hasRHS(Literal1))87          .bind("negativeComparison"));88  AddSimpleMatcher(89      binaryOperation(hasLHS(Literal1), hasOperatorName(">"), hasRHS(CountCall))90          .bind("negativeComparison"));91 92  // Find membership tests based on `find() == end()` or `find() == npos`.93  AddSimpleMatcher(94      binaryOperation(hasOperatorName("!="),95                      hasOperands(FindCall, anyOf(EndCall, StringNpos)))96          .bind("positiveComparison"));97  AddSimpleMatcher(98      binaryOperation(hasOperatorName("=="),99                      hasOperands(FindCall, anyOf(EndCall, StringNpos)))100          .bind("negativeComparison"));101}102 103void ContainerContainsCheck::check(const MatchFinder::MatchResult &Result) {104  // Extract the information about the match105  const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>("call");106  const auto *PositiveComparison =107      Result.Nodes.getNodeAs<Expr>("positiveComparison");108  const auto *NegativeComparison =109      Result.Nodes.getNodeAs<Expr>("negativeComparison");110  assert((!PositiveComparison || !NegativeComparison) &&111         "only one of PositiveComparison or NegativeComparison should be set");112  const bool Negated = NegativeComparison != nullptr;113  const auto *Comparison = Negated ? NegativeComparison : PositiveComparison;114  const StringRef ContainsFunName =115      Result.Nodes.getNodeAs<CXXMethodDecl>("contains_fun")->getName();116  const Expr *SearchExpr = Call->getArg(0)->IgnoreParenImpCasts();117 118  // Diagnose the issue.119  auto Diag = diag(Call->getExprLoc(), "use '%0' to check for membership")120              << ContainsFunName;121 122  // Don't fix it if it's in a macro invocation. Leave fixing it to the user.123  const SourceLocation FuncCallLoc = Comparison->getEndLoc();124  if (!FuncCallLoc.isValid() || FuncCallLoc.isMacroID())125    return;126 127  const StringRef SearchExprText = Lexer::getSourceText(128      CharSourceRange::getTokenRange(SearchExpr->getSourceRange()),129      *Result.SourceManager, Result.Context->getLangOpts());130 131  // Remove everything before the function call.132  Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(133      Comparison->getBeginLoc(), Call->getBeginLoc()));134 135  // Rename the function to `contains`.136  Diag << FixItHint::CreateReplacement(Call->getExprLoc(), ContainsFunName);137 138  // Replace arguments and everything after the function call.139  Diag << FixItHint::CreateReplacement(140      CharSourceRange::getTokenRange(Call->getArg(0)->getBeginLoc(),141                                     Comparison->getEndLoc()),142      (SearchExprText + ")").str());143 144  // Add negation if necessary.145  if (Negated)146    Diag << FixItHint::CreateInsertion(Call->getBeginLoc(), "!");147}148 149} // namespace clang::tidy::readability150