8
8
9
9
#include " UseStartsEndsWithCheck.h"
10
10
11
+ #include " ../utils/ASTUtils.h"
11
12
#include " ../utils/OptionsUtils.h"
12
13
#include " clang/Lex/Lexer.h"
13
14
16
17
using namespace clang ::ast_matchers;
17
18
18
19
namespace clang ::tidy::modernize {
20
+ struct NotLengthExprForStringNode {
21
+ NotLengthExprForStringNode (std::string ID, DynTypedNode Node,
22
+ ASTContext *Context)
23
+ : ID(std::move(ID)), Node(std::move(Node)), Context(Context) {}
24
+ bool operator ()(const internal::BoundNodesMap &Nodes) const {
25
+ // Match a string literal and an integer size or strlen() call.
26
+ if (const auto *StringLiteralNode = Nodes.getNodeAs <StringLiteral>(ID)) {
27
+ if (const auto *IntegerLiteralSizeNode = Node.get <IntegerLiteral>()) {
28
+ return StringLiteralNode->getLength () !=
29
+ IntegerLiteralSizeNode->getValue ().getZExtValue ();
30
+ }
31
+
32
+ if (const auto *StrlenNode = Node.get <CallExpr>()) {
33
+ if (StrlenNode->getDirectCallee ()->getName () != " strlen" ||
34
+ StrlenNode->getNumArgs () != 1 ) {
35
+ return true ;
36
+ }
37
+
38
+ if (const auto *StrlenArgNode = dyn_cast<StringLiteral>(
39
+ StrlenNode->getArg (0 )->IgnoreParenImpCasts ())) {
40
+ return StrlenArgNode->getLength () != StringLiteralNode->getLength ();
41
+ }
42
+ }
43
+ }
44
+
45
+ // Match a string variable and a call to length() or size().
46
+ if (const auto *ExprNode = Nodes.getNodeAs <Expr>(ID)) {
47
+ if (const auto *MemberCallNode = Node.get <CXXMemberCallExpr>()) {
48
+ const CXXMethodDecl *MethodDeclNode = MemberCallNode->getMethodDecl ();
49
+ const StringRef Name = MethodDeclNode->getName ();
50
+ if (!MethodDeclNode->isConst () || MethodDeclNode->getNumParams () != 0 ||
51
+ (Name != " size" && Name != " length" )) {
52
+ return true ;
53
+ }
54
+
55
+ if (const auto *OnNode =
56
+ dyn_cast<Expr>(MemberCallNode->getImplicitObjectArgument ())) {
57
+ return !utils::areStatementsIdentical (OnNode->IgnoreParenImpCasts (),
58
+ ExprNode->IgnoreParenImpCasts (),
59
+ *Context);
60
+ }
61
+ }
62
+ }
63
+
64
+ return true ;
65
+ }
66
+
67
+ private:
68
+ std::string ID;
69
+ DynTypedNode Node;
70
+ ASTContext *Context;
71
+ };
72
+
73
+ AST_MATCHER_P (Expr, lengthExprForStringNode, std::string, ID) {
74
+ return Builder->removeBindings (NotLengthExprForStringNode (
75
+ ID, DynTypedNode::create (Node), &(Finder->getASTContext ())));
76
+ }
19
77
20
78
UseStartsEndsWithCheck::UseStartsEndsWithCheck (StringRef Name,
21
79
ClangTidyContext *Context)
22
80
: ClangTidyCheck(Name, Context) {}
23
81
24
82
void UseStartsEndsWithCheck::registerMatchers (MatchFinder *Finder) {
25
83
const auto ZeroLiteral = integerLiteral (equals (0 ));
84
+
26
85
const auto HasStartsWithMethodWithName = [](const std::string &Name) {
27
86
return hasMethod (
28
87
cxxMethodDecl (hasName (Name), isConst (), parameterCountIs (1 ))
@@ -32,119 +91,104 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
32
91
anyOf (HasStartsWithMethodWithName (" starts_with" ),
33
92
HasStartsWithMethodWithName (" startsWith" ),
34
93
HasStartsWithMethodWithName (" startswith" ));
35
- const auto ClassWithStartsWithFunction = cxxRecordDecl (anyOf (
36
- HasStartsWithMethod, hasAnyBase (hasType (hasCanonicalType (hasDeclaration (
37
- cxxRecordDecl (HasStartsWithMethod)))))));
94
+ const auto OnClassWithStartsWithFunction =
95
+ on (hasType (hasCanonicalType (hasDeclaration (cxxRecordDecl (
96
+ anyOf (HasStartsWithMethod,
97
+ hasAnyBase (hasType (hasCanonicalType (
98
+ hasDeclaration (cxxRecordDecl (HasStartsWithMethod)))))))))));
38
99
100
+ const auto HasEndsWithMethodWithName = [](const std::string &Name) {
101
+ return hasMethod (
102
+ cxxMethodDecl (hasName (Name), isConst (), parameterCountIs (1 ))
103
+ .bind (" ends_with_fun" ));
104
+ };
105
+ const auto HasEndsWithMethod = anyOf (HasEndsWithMethodWithName (" ends_with" ),
106
+ HasEndsWithMethodWithName (" endsWith" ),
107
+ HasEndsWithMethodWithName (" endswith" ));
108
+ const auto OnClassWithEndsWithFunction =
109
+ on (expr (hasType (hasCanonicalType (hasDeclaration (cxxRecordDecl (
110
+ anyOf (HasEndsWithMethod,
111
+ hasAnyBase (hasType (hasCanonicalType (hasDeclaration (
112
+ cxxRecordDecl (HasEndsWithMethod)))))))))))
113
+ .bind (" haystack" ));
114
+
115
+ // Case 1: X.find(Y) [!=]= 0 -> starts_with.
39
116
const auto FindExpr = cxxMemberCallExpr (
40
- // A method call with no second argument or the second argument is zero...
41
117
anyOf (argumentCountIs (1 ), hasArgument (1 , ZeroLiteral)),
42
- // ... named find...
43
118
callee (cxxMethodDecl (hasName (" find" )).bind (" find_fun" )),
44
- // ... on a class with a starts_with function.
45
- on (hasType (
46
- hasCanonicalType (hasDeclaration (ClassWithStartsWithFunction)))),
47
- // Bind search expression.
48
- hasArgument (0 , expr ().bind (" search_expr" )));
119
+ OnClassWithStartsWithFunction, hasArgument (0 , expr ().bind (" needle" )));
49
120
121
+ // Case 2: X.rfind(Y, 0) [!=]= 0 -> starts_with.
50
122
const auto RFindExpr = cxxMemberCallExpr (
51
- // A method call with a second argument of zero...
52
123
hasArgument (1 , ZeroLiteral),
53
- // ... named rfind...
54
124
callee (cxxMethodDecl (hasName (" rfind" )).bind (" find_fun" )),
55
- // ... on a class with a starts_with function.
56
- on (hasType (
57
- hasCanonicalType (hasDeclaration (ClassWithStartsWithFunction)))),
58
- // Bind search expression.
59
- hasArgument (0 , expr ().bind (" 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 (" string_literal_arg" )),
66
- hasArgument (LengthArgIndex,
67
- anyOf (integerLiteral ().bind (" integer_literal_size_arg" ),
68
- callExpr (callee (functionDecl (parameterCountIs (1 ),
69
- hasName (" strlen" ))),
70
- hasArgument (0 , stringLiteral ().bind (
71
- " 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
- decl ().bind (" string_var_decl" )))),
80
- hasArgument (LengthArgIndex,
81
- cxxMemberCallExpr (
82
- callee (cxxMethodDecl (isConst (), parameterCountIs (0 ),
83
- hasAnyName (" size" , " length" ))),
84
- on (declRefExpr (
85
- to (decl (equalsBoundNode (" 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
- };
125
+ OnClassWithStartsWithFunction, hasArgument (0 , expr ().bind (" needle" )));
96
126
127
+ // Case 3: X.compare(0, LEN(Y), Y) [!=]= 0 -> starts_with.
97
128
const auto CompareExpr = cxxMemberCallExpr (
98
- // A method call with three arguments...
99
- argumentCountIs (3 ),
100
- // ... where the first argument is zero...
101
- hasArgument (0 , ZeroLiteral),
102
- // ... named compare...
129
+ argumentCountIs (3 ), hasArgument (0 , ZeroLiteral),
103
130
callee (cxxMethodDecl (hasName (" compare" )).bind (" find_fun" )),
104
- // ... on a class with a starts_with function...
105
- on (hasType (
106
- hasCanonicalType (hasDeclaration (ClassWithStartsWithFunction)))),
107
- // ... where the third argument is some string and the second a length.
108
- HasStringAndLengthArgs (2 , 1 ),
109
- // Bind search expression.
110
- hasArgument (2 , expr ().bind (" search_expr" )));
131
+ OnClassWithStartsWithFunction, hasArgument (2 , expr ().bind (" needle" )),
132
+ hasArgument (1 , lengthExprForStringNode (" needle" )));
111
133
134
+ // Case 4: X.compare(LEN(X) - LEN(Y), LEN(Y), Y) [!=]= 0 -> ends_with.
135
+ const auto CompareEndsWithExpr = cxxMemberCallExpr (
136
+ argumentCountIs (3 ),
137
+ callee (cxxMethodDecl (hasName (" compare" )).bind (" find_fun" )),
138
+ OnClassWithEndsWithFunction, hasArgument (2 , expr ().bind (" needle" )),
139
+ hasArgument (1 , lengthExprForStringNode (" needle" )),
140
+ hasArgument (0 ,
141
+ binaryOperator (hasOperatorName (" -" ),
142
+ hasLHS (lengthExprForStringNode (" haystack" )),
143
+ hasRHS (lengthExprForStringNode (" needle" )))));
144
+
145
+ // All cases comparing to 0.
112
146
Finder->addMatcher (
113
- // Match [=!]= with a zero on one side and (r?)find|compare on the other.
114
147
binaryOperator (
115
148
hasAnyOperatorName (" ==" , " !=" ),
116
- hasOperands (cxxMemberCallExpr (anyOf (FindExpr, RFindExpr, CompareExpr))
149
+ hasOperands (cxxMemberCallExpr (anyOf (FindExpr, RFindExpr, CompareExpr,
150
+ CompareEndsWithExpr))
117
151
.bind (" find_expr" ),
118
152
ZeroLiteral))
119
153
.bind (" expr" ),
120
154
this );
155
+
156
+ // Case 5: X.rfind(Y) [!=]= LEN(X) - LEN(Y) -> ends_with.
157
+ Finder->addMatcher (
158
+ binaryOperator (
159
+ hasAnyOperatorName (" ==" , " !=" ),
160
+ hasOperands (
161
+ cxxMemberCallExpr (
162
+ anyOf (
163
+ argumentCountIs (1 ),
164
+ allOf (argumentCountIs (2 ),
165
+ hasArgument (
166
+ 1 ,
167
+ anyOf (declRefExpr (to (varDecl (hasName (" npos" )))),
168
+ memberExpr (member (hasName (" npos" ))))))),
169
+ callee (cxxMethodDecl (hasName (" rfind" )).bind (" find_fun" )),
170
+ OnClassWithEndsWithFunction,
171
+ hasArgument (0 , expr ().bind (" needle" )))
172
+ .bind (" find_expr" ),
173
+ binaryOperator (hasOperatorName (" -" ),
174
+ hasLHS (lengthExprForStringNode (" haystack" )),
175
+ hasRHS (lengthExprForStringNode (" needle" )))))
176
+ .bind (" expr" ),
177
+ this );
121
178
}
122
179
123
180
void UseStartsEndsWithCheck::check (const MatchFinder::MatchResult &Result) {
124
181
const auto *ComparisonExpr = Result.Nodes .getNodeAs <BinaryOperator>(" expr" );
125
182
const auto *FindExpr = Result.Nodes .getNodeAs <CXXMemberCallExpr>(" find_expr" );
126
183
const auto *FindFun = Result.Nodes .getNodeAs <CXXMethodDecl>(" find_fun" );
127
- const auto *SearchExpr = Result.Nodes .getNodeAs <Expr>(" search_expr " );
184
+ const auto *SearchExpr = Result.Nodes .getNodeAs <Expr>(" needle " );
128
185
const auto *StartsWithFunction =
129
186
Result.Nodes .getNodeAs <CXXMethodDecl>(" starts_with_fun" );
130
-
131
- const auto *StringLiteralArg =
132
- Result.Nodes .getNodeAs <StringLiteral>(" string_literal_arg" );
133
- const auto *IntegerLiteralSizeArg =
134
- Result.Nodes .getNodeAs <IntegerLiteral>(" integer_literal_size_arg" );
135
- const auto *StrlenArg = Result.Nodes .getNodeAs <StringLiteral>(" 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
- }
187
+ const auto *EndsWithFunction =
188
+ Result.Nodes .getNodeAs <CXXMethodDecl>(" ends_with_fun" );
189
+ assert (bool (StartsWithFunction) != bool (EndsWithFunction));
190
+ const CXXMethodDecl *ReplacementFunction =
191
+ StartsWithFunction ? StartsWithFunction : EndsWithFunction;
148
192
149
193
if (ComparisonExpr->getBeginLoc ().isMacroID ()) {
150
194
return ;
@@ -154,26 +198,26 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
154
198
155
199
auto Diagnostic =
156
200
diag (FindExpr->getExprLoc (), " use %0 instead of %1() %select{==|!=}2 0" )
157
- << StartsWithFunction ->getName () << FindFun->getName () << Neg;
201
+ << ReplacementFunction ->getName () << FindFun->getName () << Neg;
158
202
159
- // Remove possible arguments after search expression and ' [!=]= 0 ' suffix.
203
+ // Remove possible arguments after search expression and ' [!=]= .+ ' suffix.
160
204
Diagnostic << FixItHint::CreateReplacement (
161
205
CharSourceRange::getTokenRange (
162
206
Lexer::getLocForEndOfToken (SearchExpr->getEndLoc (), 0 ,
163
207
*Result.SourceManager , getLangOpts ()),
164
208
ComparisonExpr->getEndLoc ()),
165
209
" )" );
166
210
167
- // Remove possible '0 [!=]= ' prefix.
211
+ // Remove possible '.+ [!=]= ' prefix.
168
212
Diagnostic << FixItHint::CreateRemoval (CharSourceRange::getCharRange (
169
213
ComparisonExpr->getBeginLoc (), FindExpr->getBeginLoc ()));
170
214
171
- // Replace method name by 'starts_with '.
215
+ // Replace method name by '(starts|ends)_with '.
172
216
// Remove possible arguments before search expression.
173
217
Diagnostic << FixItHint::CreateReplacement (
174
218
CharSourceRange::getCharRange (FindExpr->getExprLoc (),
175
219
SearchExpr->getBeginLoc ()),
176
- (StartsWithFunction ->getName () + " (" ).str ());
220
+ (ReplacementFunction ->getName () + " (" ).str ());
177
221
178
222
// Add possible negation '!'.
179
223
if (Neg) {
0 commit comments