@@ -43,7 +43,9 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
43
43
callee (cxxMethodDecl (hasName (" find" )).bind (" find_fun" )),
44
44
// ... on a class with a starts_with function.
45
45
on (hasType (
46
- hasCanonicalType (hasDeclaration (ClassWithStartsWithFunction)))));
46
+ hasCanonicalType (hasDeclaration (ClassWithStartsWithFunction)))),
47
+ // Bind search expression.
48
+ hasArgument (0 , expr ().bind (" search_expr" )));
47
49
48
50
const auto RFindExpr = cxxMemberCallExpr (
49
51
// A method call with a second argument of zero...
@@ -52,15 +54,68 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
52
54
callee (cxxMethodDecl (hasName (" rfind" )).bind (" find_fun" )),
53
55
// ... on a class with a starts_with function.
54
56
on (hasType (
55
- hasCanonicalType (hasDeclaration (ClassWithStartsWithFunction)))));
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
+ };
56
87
57
- const auto FindOrRFindExpr =
58
- cxxMemberCallExpr (anyOf (FindExpr, RFindExpr)).bind (" find_expr" );
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 (3 ),
100
+ // ... where the first argument is zero...
101
+ hasArgument (0 , ZeroLiteral),
102
+ // ... named compare...
103
+ 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" )));
59
111
60
112
Finder->addMatcher (
61
- // Match [=!]= with a zero on one side and a string.(r?)find on the other.
62
- binaryOperator (hasAnyOperatorName (" ==" , " !=" ),
63
- hasOperands (FindOrRFindExpr, ZeroLiteral))
113
+ // Match [=!]= with a zero on one side and (r?)find|compare on the other.
114
+ binaryOperator (
115
+ hasAnyOperatorName (" ==" , " !=" ),
116
+ hasOperands (cxxMemberCallExpr (anyOf (FindExpr, RFindExpr, CompareExpr))
117
+ .bind (" find_expr" ),
118
+ ZeroLiteral))
64
119
.bind (" expr" ),
65
120
this );
66
121
}
@@ -69,23 +124,42 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
69
124
const auto *ComparisonExpr = Result.Nodes .getNodeAs <BinaryOperator>(" expr" );
70
125
const auto *FindExpr = Result.Nodes .getNodeAs <CXXMemberCallExpr>(" find_expr" );
71
126
const auto *FindFun = Result.Nodes .getNodeAs <CXXMethodDecl>(" find_fun" );
127
+ const auto *SearchExpr = Result.Nodes .getNodeAs <Expr>(" search_expr" );
72
128
const auto *StartsWithFunction =
73
129
Result.Nodes .getNodeAs <CXXMethodDecl>(" starts_with_fun" );
74
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
+ }
148
+
75
149
if (ComparisonExpr->getBeginLoc ().isMacroID ()) {
76
150
return ;
77
151
}
78
152
79
153
const bool Neg = ComparisonExpr->getOpcode () == BO_NE;
80
154
81
155
auto Diagnostic =
82
- diag (FindExpr->getBeginLoc (), " use %0 instead of %1() %select{==|!=}2 0" )
156
+ diag (FindExpr->getExprLoc (), " use %0 instead of %1() %select{==|!=}2 0" )
83
157
<< StartsWithFunction->getName () << FindFun->getName () << Neg;
84
158
85
- // Remove possible zero second argument and ' [!=]= 0' suffix.
159
+ // Remove possible arguments after search expression and ' [!=]= 0' suffix.
86
160
Diagnostic << FixItHint::CreateReplacement (
87
161
CharSourceRange::getTokenRange (
88
- Lexer::getLocForEndOfToken (FindExpr-> getArg ( 0 ) ->getEndLoc (), 0 ,
162
+ Lexer::getLocForEndOfToken (SearchExpr ->getEndLoc (), 0 ,
89
163
*Result.SourceManager , getLangOpts ()),
90
164
ComparisonExpr->getEndLoc ()),
91
165
" )" );
@@ -94,11 +168,12 @@ void UseStartsEndsWithCheck::check(const MatchFinder::MatchResult &Result) {
94
168
Diagnostic << FixItHint::CreateRemoval (CharSourceRange::getCharRange (
95
169
ComparisonExpr->getBeginLoc (), FindExpr->getBeginLoc ()));
96
170
97
- // Replace '(r?)find' with 'starts_with'.
171
+ // Replace method name by 'starts_with'.
172
+ // Remove possible arguments before search expression.
98
173
Diagnostic << FixItHint::CreateReplacement (
99
- CharSourceRange::getTokenRange (FindExpr->getExprLoc (),
100
- FindExpr-> getExprLoc ()),
101
- StartsWithFunction->getName ());
174
+ CharSourceRange::getCharRange (FindExpr->getExprLoc (),
175
+ SearchExpr-> getBeginLoc ()),
176
+ ( StartsWithFunction->getName () + " ( " ). str ());
102
177
103
178
// Add possible negation '!'.
104
179
if (Neg) {
0 commit comments