1//===--- UseStartsEndsWithCheck.cpp - clang-tidy --------------------------===//
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-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "UseStartsEndsWithCheck.h"
10
11#include "../utils/OptionsUtils.h"
12#include "clang/Lex/Lexer.h"
13
14#include <string>
15
16using namespace clang::ast_matchers;
17
18namespace clang::tidy::modernize {
19
20UseStartsEndsWithCheck::UseStartsEndsWithCheck(StringRef Name,
21 ClangTidyContext *Context)
22 : ClangTidyCheck(Name, Context) {}
23
24void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
25 const auto ZeroLiteral = integerLiteral(equals(Value: 0));
26 const auto HasStartsWithMethodWithName = [](const std::string &Name) {
27 return hasMethod(
28 InnerMatcher: cxxMethodDecl(hasName(Name), isConst(), parameterCountIs(N: 1))
29 .bind(ID: "starts_with_fun"));
30 };
31 const auto HasStartsWithMethod =
32 anyOf(HasStartsWithMethodWithName("starts_with"),
33 HasStartsWithMethodWithName("startsWith"),
34 HasStartsWithMethodWithName("startswith"));
35 const auto ClassWithStartsWithFunction = cxxRecordDecl(anyOf(
36 HasStartsWithMethod, hasAnyBase(BaseSpecMatcher: hasType(InnerMatcher: hasCanonicalType(InnerMatcher: hasDeclaration(
37 InnerMatcher: cxxRecordDecl(HasStartsWithMethod)))))));
38
39 const auto FindExpr = cxxMemberCallExpr(
40 // A method call with no second argument or the second argument is zero...
41 anyOf(argumentCountIs(N: 1), hasArgument(N: 1, InnerMatcher: ZeroLiteral)),
42 // ... named find...
43 callee(InnerMatcher: cxxMethodDecl(hasName(Name: "find")).bind(ID: "find_fun")),
44 // ... on a class with a starts_with function.
45 on(InnerMatcher: hasType(
46 InnerMatcher: hasCanonicalType(InnerMatcher: hasDeclaration(InnerMatcher: ClassWithStartsWithFunction)))),
47 // Bind search expression.
48 hasArgument(N: 0, InnerMatcher: expr().bind(ID: "search_expr")));
49
50 const auto RFindExpr = cxxMemberCallExpr(
51 // A method call with a second argument of zero...
52 hasArgument(N: 1, InnerMatcher: ZeroLiteral),
53 // ... named rfind...
54 callee(InnerMatcher: cxxMethodDecl(hasName(Name: "rfind")).bind(ID: "find_fun")),
55 // ... on a class with a starts_with function.
56 on(InnerMatcher: hasType(
57 InnerMatcher: hasCanonicalType(InnerMatcher: hasDeclaration(InnerMatcher: ClassWithStartsWithFunction)))),
58 // Bind search expression.
59 hasArgument(N: 0, InnerMatcher: expr().bind(ID: "search_expr")));
60
61 // Match a string literal and an integer or strlen() call matching the length.
62 const auto HasStringLiteralAndLengthArgs = [](const auto StringArgIndex,
63 const auto LengthArgIndex) {
64 return allOf(
65 hasArgument(StringArgIndex, stringLiteral().bind(ID: "string_literal_arg")),
66 hasArgument(LengthArgIndex,
67 anyOf(integerLiteral().bind(ID: "integer_literal_size_arg"),
68 callExpr(callee(InnerMatcher: functionDecl(parameterCountIs(N: 1),
69 hasName(Name: "strlen"))),
70 hasArgument(N: 0, InnerMatcher: stringLiteral().bind(
71 ID: "strlen_arg"))))));
72 };
73
74 // Match a string variable and a call to length() or size().
75 const auto HasStringVariableAndSizeCallArgs = [](const auto StringArgIndex,
76 const auto LengthArgIndex) {
77 return allOf(
78 hasArgument(StringArgIndex, declRefExpr(hasDeclaration(
79 InnerMatcher: decl().bind(ID: "string_var_decl")))),
80 hasArgument(LengthArgIndex,
81 cxxMemberCallExpr(
82 callee(InnerMatcher: cxxMethodDecl(isConst(), parameterCountIs(N: 0),
83 hasAnyName("size", "length"))),
84 on(InnerMatcher: declRefExpr(
85 to(InnerMatcher: decl(equalsBoundNode(ID: "string_var_decl"))))))));
86 };
87
88 // Match either one of the two cases above.
89 const auto HasStringAndLengthArgs =
90 [HasStringLiteralAndLengthArgs, HasStringVariableAndSizeCallArgs](
91 const auto StringArgIndex, const auto LengthArgIndex) {
92 return anyOf(
93 HasStringLiteralAndLengthArgs(StringArgIndex, LengthArgIndex),
94 HasStringVariableAndSizeCallArgs(StringArgIndex, LengthArgIndex));
95 };
96
97 const auto CompareExpr = cxxMemberCallExpr(
98 // A method call with three arguments...
99 argumentCountIs(N: 3),
100 // ... where the first argument is zero...
101 hasArgument(N: 0, InnerMatcher: ZeroLiteral),
102 // ... named compare...
103 callee(InnerMatcher: cxxMethodDecl(hasName(Name: "compare")).bind(ID: "find_fun")),
104 // ... on a class with a starts_with function...
105 on(InnerMatcher: hasType(
106 InnerMatcher: hasCanonicalType(InnerMatcher: hasDeclaration(InnerMatcher: ClassWithStartsWithFunction)))),
107 // ... where the third argument is some string and the second a length.
108 HasStringAndLengthArgs(2, 1),
109 // Bind search expression.
110 hasArgument(N: 2, InnerMatcher: expr().bind(ID: "search_expr")));
111
112 Finder->addMatcher(
113 // Match [=!]= with a zero on one side and (r?)find|compare on the other.
114 NodeMatch: binaryOperator(
115 hasAnyOperatorName("==", "!="),
116 hasOperands(Matcher1: cxxMemberCallExpr(anyOf(FindExpr, RFindExpr, CompareExpr))
117 .bind(ID: "find_expr"),
118 Matcher2: ZeroLiteral))
119 .bind(ID: "expr"),
120 Action: this);
121}
122
123void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
124 const auto *ComparisonExpr = Result.Nodes.getNodeAs<BinaryOperator>(ID: "expr");
125 const auto *FindExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>(ID: "find_expr");
126 const auto *FindFun = Result.Nodes.getNodeAs<CXXMethodDecl>(ID: "find_fun");
127 const auto *SearchExpr = Result.Nodes.getNodeAs<Expr>(ID: "search_expr");
128 const auto *StartsWithFunction =
129 Result.Nodes.getNodeAs<CXXMethodDecl>(ID: "starts_with_fun");
130
131 const auto *StringLiteralArg =
132 Result.Nodes.getNodeAs<StringLiteral>(ID: "string_literal_arg");
133 const auto *IntegerLiteralSizeArg =
134 Result.Nodes.getNodeAs<IntegerLiteral>(ID: "integer_literal_size_arg");
135 const auto *StrlenArg = Result.Nodes.getNodeAs<StringLiteral>(ID: "strlen_arg");
136
137 // Filter out compare cases where the length does not match string literal.
138 if (StringLiteralArg && IntegerLiteralSizeArg &&
139 StringLiteralArg->getLength() !=
140 IntegerLiteralSizeArg->getValue().getZExtValue()) {
141 return;
142 }
143
144 if (StringLiteralArg && StrlenArg &&
145 StringLiteralArg->getLength() != StrlenArg->getLength()) {
146 return;
147 }
148
149 if (ComparisonExpr->getBeginLoc().isMacroID()) {
150 return;
151 }
152
153 const bool Neg = ComparisonExpr->getOpcode() == BO_NE;
154
155 auto Diagnostic =
156 diag(Loc: FindExpr->getExprLoc(), Description: "use %0 instead of %1() %select{==|!=}2 0")
157 << StartsWithFunction->getName() << FindFun->getName() << Neg;
158
159 // Remove possible arguments after search expression and ' [!=]= 0' suffix.
160 Diagnostic << FixItHint::CreateReplacement(
161 CharSourceRange::getTokenRange(
162 Lexer::getLocForEndOfToken(Loc: SearchExpr->getEndLoc(), Offset: 0,
163 SM: *Result.SourceManager, LangOpts: getLangOpts()),
164 ComparisonExpr->getEndLoc()),
165 ")");
166
167 // Remove possible '0 [!=]= ' prefix.
168 Diagnostic << FixItHint::CreateRemoval(CharSourceRange::getCharRange(
169 ComparisonExpr->getBeginLoc(), FindExpr->getBeginLoc()));
170
171 // Replace method name by 'starts_with'.
172 // Remove possible arguments before search expression.
173 Diagnostic << FixItHint::CreateReplacement(
174 CharSourceRange::getCharRange(FindExpr->getExprLoc(),
175 SearchExpr->getBeginLoc()),
176 (StartsWithFunction->getName() + "(").str());
177
178 // Add possible negation '!'.
179 if (Neg) {
180 Diagnostic << FixItHint::CreateInsertion(InsertionLoc: FindExpr->getBeginLoc(), Code: "!");
181 }
182}
183
184} // namespace clang::tidy::modernize
185

source code of clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp