1bc8cff1dSNicolas van Kempen //===--- UseStartsEndsWithCheck.cpp - clang-tidy --------------------------===// 2bc8cff1dSNicolas van Kempen // 3bc8cff1dSNicolas van Kempen // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4bc8cff1dSNicolas van Kempen // See https://llvm.org/LICENSE.txt for license information. 5bc8cff1dSNicolas van Kempen // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6bc8cff1dSNicolas van Kempen // 7bc8cff1dSNicolas van Kempen //===----------------------------------------------------------------------===// 8bc8cff1dSNicolas van Kempen 9bc8cff1dSNicolas van Kempen #include "UseStartsEndsWithCheck.h" 10bc8cff1dSNicolas van Kempen 11f1367a47SNicolas van Kempen #include "../utils/ASTUtils.h" 1218b50189SJulian Schmidt #include "../utils/Matchers.h" 1318b50189SJulian Schmidt #include "clang/ASTMatchers/ASTMatchers.h" 14bc8cff1dSNicolas van Kempen #include "clang/Lex/Lexer.h" 15bc8cff1dSNicolas van Kempen 16bc8cff1dSNicolas van Kempen #include <string> 17bc8cff1dSNicolas van Kempen 18bc8cff1dSNicolas van Kempen using namespace clang::ast_matchers; 19bc8cff1dSNicolas van Kempen 20bc8cff1dSNicolas van Kempen namespace clang::tidy::modernize { 2150844828SHelmut Januschka 2250844828SHelmut Januschka static bool isNegativeComparison(const Expr *ComparisonExpr) { 23*2f02b5afSNicolas van Kempen if (const auto *Op = llvm::dyn_cast<BinaryOperator>(ComparisonExpr)) 24*2f02b5afSNicolas van Kempen return Op->getOpcode() == BO_NE; 2550844828SHelmut Januschka 2650844828SHelmut Januschka if (const auto *Op = llvm::dyn_cast<CXXOperatorCallExpr>(ComparisonExpr)) 2750844828SHelmut Januschka return Op->getOperator() == OO_ExclaimEqual; 2850844828SHelmut Januschka 29*2f02b5afSNicolas van Kempen if (const auto *Op = 30*2f02b5afSNicolas van Kempen llvm::dyn_cast<CXXRewrittenBinaryOperator>(ComparisonExpr)) 31*2f02b5afSNicolas van Kempen return Op->getOperator() == BO_NE; 32*2f02b5afSNicolas van Kempen 3350844828SHelmut Januschka return false; 3450844828SHelmut Januschka } 3550844828SHelmut Januschka 36f1367a47SNicolas van Kempen struct NotLengthExprForStringNode { 37f1367a47SNicolas van Kempen NotLengthExprForStringNode(std::string ID, DynTypedNode Node, 38f1367a47SNicolas van Kempen ASTContext *Context) 39f1367a47SNicolas van Kempen : ID(std::move(ID)), Node(std::move(Node)), Context(Context) {} 40f1367a47SNicolas van Kempen bool operator()(const internal::BoundNodesMap &Nodes) const { 41f1367a47SNicolas van Kempen // Match a string literal and an integer size or strlen() call. 42f1367a47SNicolas van Kempen if (const auto *StringLiteralNode = Nodes.getNodeAs<StringLiteral>(ID)) { 43f1367a47SNicolas van Kempen if (const auto *IntegerLiteralSizeNode = Node.get<IntegerLiteral>()) { 44f1367a47SNicolas van Kempen return StringLiteralNode->getLength() != 45f1367a47SNicolas van Kempen IntegerLiteralSizeNode->getValue().getZExtValue(); 46f1367a47SNicolas van Kempen } 47f1367a47SNicolas van Kempen 48f1367a47SNicolas van Kempen if (const auto *StrlenNode = Node.get<CallExpr>()) { 49f1367a47SNicolas van Kempen if (StrlenNode->getDirectCallee()->getName() != "strlen" || 50f1367a47SNicolas van Kempen StrlenNode->getNumArgs() != 1) { 51f1367a47SNicolas van Kempen return true; 52f1367a47SNicolas van Kempen } 53f1367a47SNicolas van Kempen 54f1367a47SNicolas van Kempen if (const auto *StrlenArgNode = dyn_cast<StringLiteral>( 55f1367a47SNicolas van Kempen StrlenNode->getArg(0)->IgnoreParenImpCasts())) { 56f1367a47SNicolas van Kempen return StrlenArgNode->getLength() != StringLiteralNode->getLength(); 57f1367a47SNicolas van Kempen } 58f1367a47SNicolas van Kempen } 59f1367a47SNicolas van Kempen } 60f1367a47SNicolas van Kempen 61f1367a47SNicolas van Kempen // Match a string variable and a call to length() or size(). 62f1367a47SNicolas van Kempen if (const auto *ExprNode = Nodes.getNodeAs<Expr>(ID)) { 63f1367a47SNicolas van Kempen if (const auto *MemberCallNode = Node.get<CXXMemberCallExpr>()) { 64f1367a47SNicolas van Kempen const CXXMethodDecl *MethodDeclNode = MemberCallNode->getMethodDecl(); 65f1367a47SNicolas van Kempen const StringRef Name = MethodDeclNode->getName(); 66f1367a47SNicolas van Kempen if (!MethodDeclNode->isConst() || MethodDeclNode->getNumParams() != 0 || 67f1367a47SNicolas van Kempen (Name != "size" && Name != "length")) { 68f1367a47SNicolas van Kempen return true; 69f1367a47SNicolas van Kempen } 70f1367a47SNicolas van Kempen 71f1367a47SNicolas van Kempen if (const auto *OnNode = 72f1367a47SNicolas van Kempen dyn_cast<Expr>(MemberCallNode->getImplicitObjectArgument())) { 73f1367a47SNicolas van Kempen return !utils::areStatementsIdentical(OnNode->IgnoreParenImpCasts(), 74f1367a47SNicolas van Kempen ExprNode->IgnoreParenImpCasts(), 75f1367a47SNicolas van Kempen *Context); 76f1367a47SNicolas van Kempen } 77f1367a47SNicolas van Kempen } 78f1367a47SNicolas van Kempen } 79f1367a47SNicolas van Kempen 80f1367a47SNicolas van Kempen return true; 81f1367a47SNicolas van Kempen } 82f1367a47SNicolas van Kempen 83f1367a47SNicolas van Kempen private: 84f1367a47SNicolas van Kempen std::string ID; 85f1367a47SNicolas van Kempen DynTypedNode Node; 86f1367a47SNicolas van Kempen ASTContext *Context; 87f1367a47SNicolas van Kempen }; 88f1367a47SNicolas van Kempen 89f1367a47SNicolas van Kempen AST_MATCHER_P(Expr, lengthExprForStringNode, std::string, ID) { 90f1367a47SNicolas van Kempen return Builder->removeBindings(NotLengthExprForStringNode( 91f1367a47SNicolas van Kempen ID, DynTypedNode::create(Node), &(Finder->getASTContext()))); 92f1367a47SNicolas van Kempen } 93bc8cff1dSNicolas van Kempen 94bc8cff1dSNicolas van Kempen UseStartsEndsWithCheck::UseStartsEndsWithCheck(StringRef Name, 95bc8cff1dSNicolas van Kempen ClangTidyContext *Context) 96bc8cff1dSNicolas van Kempen : ClangTidyCheck(Name, Context) {} 97bc8cff1dSNicolas van Kempen 98bc8cff1dSNicolas van Kempen void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) { 99bc8cff1dSNicolas van Kempen const auto ZeroLiteral = integerLiteral(equals(0)); 100f1367a47SNicolas van Kempen 10118b50189SJulian Schmidt const auto ClassTypeWithMethod = [](const StringRef MethodBoundName, 10218b50189SJulian Schmidt const auto... Methods) { 10318b50189SJulian Schmidt return cxxRecordDecl(anyOf( 10418b50189SJulian Schmidt hasMethod(cxxMethodDecl(isConst(), parameterCountIs(1), 10518b50189SJulian Schmidt returns(booleanType()), hasAnyName(Methods)) 10618b50189SJulian Schmidt .bind(MethodBoundName))...)); 107bc8cff1dSNicolas van Kempen }; 108bc8cff1dSNicolas van Kempen 10918b50189SJulian Schmidt const auto OnClassWithStartsWithFunction = 11018b50189SJulian Schmidt ClassTypeWithMethod("starts_with_fun", "starts_with", "startsWith", 11118b50189SJulian Schmidt "startswith", "StartsWith"); 11218b50189SJulian Schmidt 11318b50189SJulian Schmidt const auto OnClassWithEndsWithFunction = ClassTypeWithMethod( 11418b50189SJulian Schmidt "ends_with_fun", "ends_with", "endsWith", "endswith", "EndsWith"); 115f1367a47SNicolas van Kempen 116f1367a47SNicolas van Kempen // Case 1: X.find(Y) [!=]= 0 -> starts_with. 117bc8cff1dSNicolas van Kempen const auto FindExpr = cxxMemberCallExpr( 118bc8cff1dSNicolas van Kempen anyOf(argumentCountIs(1), hasArgument(1, ZeroLiteral)), 11918b50189SJulian Schmidt callee( 12018b50189SJulian Schmidt cxxMethodDecl(hasName("find"), ofClass(OnClassWithStartsWithFunction)) 12118b50189SJulian Schmidt .bind("find_fun")), 12218b50189SJulian Schmidt hasArgument(0, expr().bind("needle"))); 123bc8cff1dSNicolas van Kempen 124f1367a47SNicolas van Kempen // Case 2: X.rfind(Y, 0) [!=]= 0 -> starts_with. 125bc8cff1dSNicolas van Kempen const auto RFindExpr = cxxMemberCallExpr( 126bc8cff1dSNicolas van Kempen hasArgument(1, ZeroLiteral), 12718b50189SJulian Schmidt callee(cxxMethodDecl(hasName("rfind"), 12818b50189SJulian Schmidt ofClass(OnClassWithStartsWithFunction)) 12918b50189SJulian Schmidt .bind("find_fun")), 13018b50189SJulian Schmidt hasArgument(0, expr().bind("needle"))); 131bc8cff1dSNicolas van Kempen 132f1367a47SNicolas van Kempen // Case 3: X.compare(0, LEN(Y), Y) [!=]= 0 -> starts_with. 133ef590698SNicolas van Kempen const auto CompareExpr = cxxMemberCallExpr( 134f1367a47SNicolas van Kempen argumentCountIs(3), hasArgument(0, ZeroLiteral), 13518b50189SJulian Schmidt callee(cxxMethodDecl(hasName("compare"), 13618b50189SJulian Schmidt ofClass(OnClassWithStartsWithFunction)) 13718b50189SJulian Schmidt .bind("find_fun")), 13818b50189SJulian Schmidt hasArgument(2, expr().bind("needle")), 139f1367a47SNicolas van Kempen hasArgument(1, lengthExprForStringNode("needle"))); 140bc8cff1dSNicolas van Kempen 141f1367a47SNicolas van Kempen // Case 4: X.compare(LEN(X) - LEN(Y), LEN(Y), Y) [!=]= 0 -> ends_with. 142f1367a47SNicolas van Kempen const auto CompareEndsWithExpr = cxxMemberCallExpr( 143f1367a47SNicolas van Kempen argumentCountIs(3), 14418b50189SJulian Schmidt callee(cxxMethodDecl(hasName("compare"), 14518b50189SJulian Schmidt ofClass(OnClassWithEndsWithFunction)) 14618b50189SJulian Schmidt .bind("find_fun")), 14718b50189SJulian Schmidt on(expr().bind("haystack")), hasArgument(2, expr().bind("needle")), 148f1367a47SNicolas van Kempen hasArgument(1, lengthExprForStringNode("needle")), 149f1367a47SNicolas van Kempen hasArgument(0, 150f1367a47SNicolas van Kempen binaryOperator(hasOperatorName("-"), 151f1367a47SNicolas van Kempen hasLHS(lengthExprForStringNode("haystack")), 152f1367a47SNicolas van Kempen hasRHS(lengthExprForStringNode("needle"))))); 153f1367a47SNicolas van Kempen 154f1367a47SNicolas van Kempen // All cases comparing to 0. 155bc8cff1dSNicolas van Kempen Finder->addMatcher( 156ef590698SNicolas van Kempen binaryOperator( 15718b50189SJulian Schmidt matchers::isEqualityOperator(), 158f1367a47SNicolas van Kempen hasOperands(cxxMemberCallExpr(anyOf(FindExpr, RFindExpr, CompareExpr, 159f1367a47SNicolas van Kempen CompareEndsWithExpr)) 160ef590698SNicolas van Kempen .bind("find_expr"), 161ef590698SNicolas van Kempen ZeroLiteral)) 162bc8cff1dSNicolas van Kempen .bind("expr"), 163bc8cff1dSNicolas van Kempen this); 164f1367a47SNicolas van Kempen 165f1367a47SNicolas van Kempen // Case 5: X.rfind(Y) [!=]= LEN(X) - LEN(Y) -> ends_with. 166f1367a47SNicolas van Kempen Finder->addMatcher( 167f1367a47SNicolas van Kempen binaryOperator( 16818b50189SJulian Schmidt matchers::isEqualityOperator(), 169f1367a47SNicolas van Kempen hasOperands( 170f1367a47SNicolas van Kempen cxxMemberCallExpr( 171f1367a47SNicolas van Kempen anyOf( 172f1367a47SNicolas van Kempen argumentCountIs(1), 173f1367a47SNicolas van Kempen allOf(argumentCountIs(2), 174f1367a47SNicolas van Kempen hasArgument( 175f1367a47SNicolas van Kempen 1, 176f1367a47SNicolas van Kempen anyOf(declRefExpr(to(varDecl(hasName("npos")))), 177f1367a47SNicolas van Kempen memberExpr(member(hasName("npos"))))))), 17818b50189SJulian Schmidt callee(cxxMethodDecl(hasName("rfind"), 17918b50189SJulian Schmidt ofClass(OnClassWithEndsWithFunction)) 18018b50189SJulian Schmidt .bind("find_fun")), 18118b50189SJulian Schmidt on(expr().bind("haystack")), 182f1367a47SNicolas van Kempen hasArgument(0, expr().bind("needle"))) 183f1367a47SNicolas van Kempen .bind("find_expr"), 184f1367a47SNicolas van Kempen binaryOperator(hasOperatorName("-"), 185f1367a47SNicolas van Kempen hasLHS(lengthExprForStringNode("haystack")), 186f1367a47SNicolas van Kempen hasRHS(lengthExprForStringNode("needle"))))) 187f1367a47SNicolas van Kempen .bind("expr"), 188f1367a47SNicolas van Kempen this); 18950844828SHelmut Januschka 19050844828SHelmut Januschka // Case 6: X.substr(0, LEN(Y)) [!=]= Y -> starts_with. 19150844828SHelmut Januschka Finder->addMatcher( 192*2f02b5afSNicolas van Kempen binaryOperation( 19350844828SHelmut Januschka hasAnyOperatorName("==", "!="), 19450844828SHelmut Januschka hasOperands( 19550844828SHelmut Januschka expr().bind("needle"), 19650844828SHelmut Januschka cxxMemberCallExpr( 19750844828SHelmut Januschka argumentCountIs(2), hasArgument(0, ZeroLiteral), 19850844828SHelmut Januschka hasArgument(1, lengthExprForStringNode("needle")), 19950844828SHelmut Januschka callee(cxxMethodDecl(hasName("substr"), 20050844828SHelmut Januschka ofClass(OnClassWithStartsWithFunction)) 20150844828SHelmut Januschka .bind("find_fun"))) 20250844828SHelmut Januschka .bind("find_expr"))) 20350844828SHelmut Januschka .bind("expr"), 20450844828SHelmut Januschka this); 205bc8cff1dSNicolas van Kempen } 206bc8cff1dSNicolas van Kempen 207bc8cff1dSNicolas van Kempen void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) { 20850844828SHelmut Januschka const auto *ComparisonExpr = Result.Nodes.getNodeAs<Expr>("expr"); 209bc8cff1dSNicolas van Kempen const auto *FindExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>("find_expr"); 210bc8cff1dSNicolas van Kempen const auto *FindFun = Result.Nodes.getNodeAs<CXXMethodDecl>("find_fun"); 211f1367a47SNicolas van Kempen const auto *SearchExpr = Result.Nodes.getNodeAs<Expr>("needle"); 212bc8cff1dSNicolas van Kempen const auto *StartsWithFunction = 213bc8cff1dSNicolas van Kempen Result.Nodes.getNodeAs<CXXMethodDecl>("starts_with_fun"); 214f1367a47SNicolas van Kempen const auto *EndsWithFunction = 215f1367a47SNicolas van Kempen Result.Nodes.getNodeAs<CXXMethodDecl>("ends_with_fun"); 216f1367a47SNicolas van Kempen assert(bool(StartsWithFunction) != bool(EndsWithFunction)); 21750844828SHelmut Januschka 218f1367a47SNicolas van Kempen const CXXMethodDecl *ReplacementFunction = 219f1367a47SNicolas van Kempen StartsWithFunction ? StartsWithFunction : EndsWithFunction; 220ef590698SNicolas van Kempen 22150844828SHelmut Januschka if (ComparisonExpr->getBeginLoc().isMacroID() || 22250844828SHelmut Januschka FindExpr->getBeginLoc().isMacroID()) 223bc8cff1dSNicolas van Kempen return; 224bc8cff1dSNicolas van Kempen 22550844828SHelmut Januschka // Make sure FindExpr->getArg(0) can be used to make a range in the FitItHint. 22650844828SHelmut Januschka if (FindExpr->getNumArgs() == 0) 22750844828SHelmut Januschka return; 228bc8cff1dSNicolas van Kempen 22950844828SHelmut Januschka // Retrieve the source text of the search expression. 23050844828SHelmut Januschka const auto SearchExprText = Lexer::getSourceText( 23150844828SHelmut Januschka CharSourceRange::getTokenRange(SearchExpr->getSourceRange()), 23250844828SHelmut Januschka *Result.SourceManager, Result.Context->getLangOpts()); 233bc8cff1dSNicolas van Kempen 23450844828SHelmut Januschka auto Diagnostic = diag(FindExpr->getExprLoc(), "use %0 instead of %1") 23550844828SHelmut Januschka << ReplacementFunction->getName() << FindFun->getName(); 236bc8cff1dSNicolas van Kempen 23750844828SHelmut Januschka // Remove everything before the function call. 238bc8cff1dSNicolas van Kempen Diagnostic << FixItHint::CreateRemoval(CharSourceRange::getCharRange( 239bc8cff1dSNicolas van Kempen ComparisonExpr->getBeginLoc(), FindExpr->getBeginLoc())); 240bc8cff1dSNicolas van Kempen 24150844828SHelmut Januschka // Rename the function to `starts_with` or `ends_with`. 24250844828SHelmut Januschka Diagnostic << FixItHint::CreateReplacement(FindExpr->getExprLoc(), 24350844828SHelmut Januschka ReplacementFunction->getName()); 244bc8cff1dSNicolas van Kempen 24550844828SHelmut Januschka // Replace arguments and everything after the function call. 24650844828SHelmut Januschka Diagnostic << FixItHint::CreateReplacement( 24750844828SHelmut Januschka CharSourceRange::getTokenRange(FindExpr->getArg(0)->getBeginLoc(), 24850844828SHelmut Januschka ComparisonExpr->getEndLoc()), 24950844828SHelmut Januschka (SearchExprText + ")").str()); 25050844828SHelmut Januschka 25150844828SHelmut Januschka // Add negation if necessary. 25250844828SHelmut Januschka if (isNegativeComparison(ComparisonExpr)) 253bc8cff1dSNicolas van Kempen Diagnostic << FixItHint::CreateInsertion(FindExpr->getBeginLoc(), "!"); 254bc8cff1dSNicolas van Kempen } 255bc8cff1dSNicolas van Kempen 256bc8cff1dSNicolas van Kempen } // namespace clang::tidy::modernize 257