@@ -22,28 +22,46 @@ struct NotLengthExprForStringNode {
22
22
NotLengthExprForStringNode (std::string ID, DynTypedNode Node,
23
23
ASTContext *Context)
24
24
: ID(std::move(ID)), Node(std::move(Node)), Context(Context) {}
25
+
25
26
bool operator ()(const internal::BoundNodesMap &Nodes) const {
26
- // Match a string literal and an integer size or strlen() call.
27
27
if (const auto *StringLiteralNode = Nodes.getNodeAs <StringLiteral>(ID)) {
28
+ // Match direct integer literals
28
29
if (const auto *IntegerLiteralSizeNode = Node.get <IntegerLiteral>()) {
29
30
return StringLiteralNode->getLength () !=
30
31
IntegerLiteralSizeNode->getValue ().getZExtValue ();
31
32
}
32
33
33
- if (const auto *StrlenNode = Node.get <CallExpr>()) {
34
- if (StrlenNode->getDirectCallee ()->getName () != " strlen" ||
35
- StrlenNode->getNumArgs () != 1 ) {
36
- return true ;
34
+ // Match strlen() calls
35
+ if (const auto *CallNode = Node.get <CallExpr>()) {
36
+ if (const auto *FD = CallNode->getDirectCallee ()) {
37
+ if (FD->getName () == " strlen" && CallNode->getNumArgs () == 1 ) {
38
+ if (const auto *StrArg = CallNode->getArg (0 )->IgnoreParenImpCasts ()) {
39
+ // Handle both string literals and string variables in strlen
40
+ if (const auto *StrLit = dyn_cast<StringLiteral>(StrArg)) {
41
+ return StrLit->getLength () != StringLiteralNode->getLength ();
42
+ } else if (const auto *StrVar = dyn_cast<Expr>(StrArg)) {
43
+ return !utils::areStatementsIdentical (StrVar, StringLiteralNode, *Context);
44
+ }
45
+ }
46
+ }
37
47
}
38
-
39
- if (const auto *StrlenArgNode = dyn_cast<StringLiteral>(
40
- StrlenNode->getArg (0 )->IgnoreParenImpCasts ())) {
41
- return StrlenArgNode->getLength () != StringLiteralNode->getLength ();
48
+ }
49
+
50
+ // Match size()/length() member calls
51
+ if (const auto *MemberCall = Node.get <CXXMemberCallExpr>()) {
52
+ if (const auto *Method = MemberCall->getMethodDecl ()) {
53
+ StringRef Name = Method->getName ();
54
+ if (Method->isConst () && Method->getNumParams () == 0 &&
55
+ (Name == " size" || Name == " length" )) {
56
+ // For string literals used in comparison, allow size/length calls
57
+ // on any string variable
58
+ return false ;
59
+ }
42
60
}
43
61
}
44
62
}
45
63
46
- // Match a string variable and a call to length() or size().
64
+ // Match member function calls on string variables
47
65
if (const auto *ExprNode = Nodes.getNodeAs <Expr>(ID)) {
48
66
if (const auto *MemberCallNode = Node.get <CXXMemberCallExpr>()) {
49
67
const CXXMethodDecl *MethodDeclNode = MemberCallNode->getMethodDecl ();
@@ -53,8 +71,8 @@ struct NotLengthExprForStringNode {
53
71
return true ;
54
72
}
55
73
56
- if (const auto *OnNode =
57
- dyn_cast<Expr>( MemberCallNode->getImplicitObjectArgument ())) {
74
+ if (const auto *OnNode = dyn_cast<Expr>(
75
+ MemberCallNode->getImplicitObjectArgument ())) {
58
76
return !utils::areStatementsIdentical (OnNode->IgnoreParenImpCasts (),
59
77
ExprNode->IgnoreParenImpCasts (),
60
78
*Context);
@@ -83,6 +101,55 @@ UseStartsEndsWithCheck::UseStartsEndsWithCheck(StringRef Name,
83
101
void UseStartsEndsWithCheck::registerMatchers (MatchFinder *Finder) {
84
102
const auto ZeroLiteral = integerLiteral (equals (0 ));
85
103
104
+ // Match the substring call
105
+ const auto SubstrCall = cxxMemberCallExpr (
106
+ callee (cxxMethodDecl (hasName (" substr" ))),
107
+ hasArgument (0 , ZeroLiteral),
108
+ hasArgument (1 , expr ().bind (" length" )),
109
+ on (expr ().bind (" str" )))
110
+ .bind (" substr_fun" );
111
+
112
+ // Match string literals
113
+ const auto Literal = stringLiteral ().bind (" literal" );
114
+
115
+ // Helper for matching comparison operators
116
+ auto AddSubstrMatcher = [&](auto Matcher) {
117
+ Finder->addMatcher (
118
+ traverse (TK_IgnoreUnlessSpelledInSource, std::move (Matcher)), this );
119
+ };
120
+
121
+ // Match str.substr(0,n) == "literal"
122
+ AddSubstrMatcher (
123
+ binaryOperation (
124
+ hasOperatorName (" ==" ),
125
+ hasLHS (SubstrCall),
126
+ hasRHS (Literal))
127
+ .bind (" positiveComparison" ));
128
+
129
+ // Also match "literal" == str.substr(0,n)
130
+ AddSubstrMatcher (
131
+ binaryOperation (
132
+ hasOperatorName (" ==" ),
133
+ hasLHS (Literal),
134
+ hasRHS (SubstrCall))
135
+ .bind (" positiveComparison" ));
136
+
137
+ // Match str.substr(0,n) != "literal"
138
+ AddSubstrMatcher (
139
+ binaryOperation (
140
+ hasOperatorName (" !=" ),
141
+ hasLHS (SubstrCall),
142
+ hasRHS (Literal))
143
+ .bind (" negativeComparison" ));
144
+
145
+ // Also match "literal" != str.substr(0,n)
146
+ AddSubstrMatcher (
147
+ binaryOperation (
148
+ hasOperatorName (" !=" ),
149
+ hasLHS (Literal),
150
+ hasRHS (SubstrCall))
151
+ .bind (" negativeComparison" ));
152
+
86
153
const auto ClassTypeWithMethod = [](const StringRef MethodBoundName,
87
154
const auto ... Methods) {
88
155
return cxxRecordDecl (anyOf (
@@ -173,7 +240,101 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
173
240
this );
174
241
}
175
242
243
+ void UseStartsEndsWithCheck::handleSubstrMatch (const MatchFinder::MatchResult &Result) {
244
+ const auto *SubstrCall = Result.Nodes .getNodeAs <CXXMemberCallExpr>(" substr_fun" );
245
+ const auto *PositiveComparison = Result.Nodes .getNodeAs <Expr>(" positiveComparison" );
246
+ const auto *NegativeComparison = Result.Nodes .getNodeAs <Expr>(" negativeComparison" );
247
+
248
+ if (!SubstrCall || (!PositiveComparison && !NegativeComparison))
249
+ return ;
250
+
251
+ const bool Negated = NegativeComparison != nullptr ;
252
+ const auto *Comparison = Negated ? NegativeComparison : PositiveComparison;
253
+
254
+ if (SubstrCall->getBeginLoc ().isMacroID ())
255
+ return ;
256
+
257
+ const auto *Str = Result.Nodes .getNodeAs <Expr>(" str" );
258
+ const auto *Literal = Result.Nodes .getNodeAs <StringLiteral>(" literal" );
259
+ const auto *Length = Result.Nodes .getNodeAs <Expr>(" length" );
260
+
261
+ if (!Str || !Literal || !Length)
262
+ return ;
263
+
264
+ // Special handling for strlen and size/length member calls
265
+ const bool IsValidLength = [&]() {
266
+ if (const auto *LengthInt = dyn_cast<IntegerLiteral>(Length)) {
267
+ return LengthInt->getValue ().getZExtValue () == Literal->getLength ();
268
+ }
269
+
270
+ if (const auto *Call = dyn_cast<CallExpr>(Length)) {
271
+ if (const auto *FD = Call->getDirectCallee ()) {
272
+ if (FD->getName () == " strlen" && Call->getNumArgs () == 1 ) {
273
+ if (const auto *StrArg = dyn_cast<StringLiteral>(
274
+ Call->getArg (0 )->IgnoreParenImpCasts ())) {
275
+ return StrArg->getLength () == Literal->getLength ();
276
+ }
277
+ }
278
+ }
279
+ }
280
+
281
+ if (const auto *MemberCall = dyn_cast<CXXMemberCallExpr>(Length)) {
282
+ if (const auto *Method = MemberCall->getMethodDecl ()) {
283
+ StringRef Name = Method->getName ();
284
+ if (Method->isConst () && Method->getNumParams () == 0 &&
285
+ (Name == " size" || Name == " length" )) {
286
+ // For string literals in comparison, we'll trust that size()/length()
287
+ // calls are valid
288
+ return true ;
289
+ }
290
+ }
291
+ }
292
+
293
+ return false ;
294
+ }();
295
+
296
+ if (!IsValidLength) {
297
+ return ;
298
+ }
299
+
300
+ // Get the string expression and literal text for the replacement
301
+ const std::string StrText = Lexer::getSourceText (
302
+ CharSourceRange::getTokenRange (Str->getSourceRange ()),
303
+ *Result.SourceManager , getLangOpts ()).str ();
304
+
305
+ const std::string LiteralText = Lexer::getSourceText (
306
+ CharSourceRange::getTokenRange (Literal->getSourceRange ()),
307
+ *Result.SourceManager , getLangOpts ()).str ();
308
+
309
+ const std::string ReplacementText = (Negated ? " !" : " " ) + StrText + " .starts_with(" +
310
+ LiteralText + " )" ;
311
+
312
+ auto Diag = diag (SubstrCall->getExprLoc (),
313
+ " use starts_with() instead of substr(0, n) comparison" );
314
+
315
+ Diag << FixItHint::CreateReplacement (
316
+ CharSourceRange::getTokenRange (Comparison->getSourceRange ()),
317
+ ReplacementText);
318
+ }
319
+
176
320
void UseStartsEndsWithCheck::check (const MatchFinder::MatchResult &Result) {
321
+ // Try substr pattern first
322
+ const auto *SubstrCall = Result.Nodes .getNodeAs <CXXMemberCallExpr>(" substr_fun" );
323
+ if (SubstrCall) {
324
+ const auto *PositiveComparison = Result.Nodes .getNodeAs <Expr>(" positiveComparison" );
325
+ const auto *NegativeComparison = Result.Nodes .getNodeAs <Expr>(" negativeComparison" );
326
+
327
+ if (PositiveComparison || NegativeComparison) {
328
+ handleSubstrMatch (Result);
329
+ return ;
330
+ }
331
+ }
332
+
333
+ // Then try find/compare patterns
334
+ handleFindCompareMatch (Result);
335
+ }
336
+
337
+ void UseStartsEndsWithCheck::handleFindCompareMatch (const MatchFinder::MatchResult &Result) {
177
338
const auto *ComparisonExpr = Result.Nodes .getNodeAs <BinaryOperator>(" expr" );
178
339
const auto *FindExpr = Result.Nodes .getNodeAs <CXXMemberCallExpr>(" find_expr" );
179
340
const auto *FindFun = Result.Nodes .getNodeAs <CXXMethodDecl>(" find_fun" );
0 commit comments