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