@@ -22,28 +22,46 @@ struct NotLengthExprForStringNode {
2222 NotLengthExprForStringNode (std::string ID, DynTypedNode Node,
2323 ASTContext *Context)
2424 : ID(std::move(ID)), Node(std::move(Node)), Context(Context) {}
25+
2526 bool operator ()(const internal::BoundNodesMap &Nodes) const {
26- // Match a string literal and an integer size or strlen() call.
2727 if (const auto *StringLiteralNode = Nodes.getNodeAs <StringLiteral>(ID)) {
28+ // Match direct integer literals
2829 if (const auto *IntegerLiteralSizeNode = Node.get <IntegerLiteral>()) {
2930 return StringLiteralNode->getLength () !=
3031 IntegerLiteralSizeNode->getValue ().getZExtValue ();
3132 }
3233
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+ }
3747 }
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+ }
4260 }
4361 }
4462 }
4563
46- // Match a string variable and a call to length() or size().
64+ // Match member function calls on string variables
4765 if (const auto *ExprNode = Nodes.getNodeAs <Expr>(ID)) {
4866 if (const auto *MemberCallNode = Node.get <CXXMemberCallExpr>()) {
4967 const CXXMethodDecl *MethodDeclNode = MemberCallNode->getMethodDecl ();
@@ -53,8 +71,8 @@ struct NotLengthExprForStringNode {
5371 return true ;
5472 }
5573
56- if (const auto *OnNode =
57- dyn_cast<Expr>( MemberCallNode->getImplicitObjectArgument ())) {
74+ if (const auto *OnNode = dyn_cast<Expr>(
75+ MemberCallNode->getImplicitObjectArgument ())) {
5876 return !utils::areStatementsIdentical (OnNode->IgnoreParenImpCasts (),
5977 ExprNode->IgnoreParenImpCasts (),
6078 *Context);
@@ -83,6 +101,55 @@ UseStartsEndsWithCheck::UseStartsEndsWithCheck(StringRef Name,
83101void UseStartsEndsWithCheck::registerMatchers (MatchFinder *Finder) {
84102 const auto ZeroLiteral = integerLiteral (equals (0 ));
85103
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+
86153 const auto ClassTypeWithMethod = [](const StringRef MethodBoundName,
87154 const auto ... Methods) {
88155 return cxxRecordDecl (anyOf (
@@ -173,7 +240,101 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
173240 this );
174241}
175242
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+
176320void 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) {
177338 const auto *ComparisonExpr = Result.Nodes .getNodeAs <BinaryOperator>(" expr" );
178339 const auto *FindExpr = Result.Nodes .getNodeAs <CXXMemberCallExpr>(" find_expr" );
179340 const auto *FindFun = Result.Nodes .getNodeAs <CXXMethodDecl>(" find_fun" );
0 commit comments