Skip to content

[clang-tidy] 'modernize-use-starts-ends-with': fixed false positives on find and rfind #129564

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

vbvictor
Copy link
Contributor

@vbvictor vbvictor commented Mar 3, 2025

  1. Fixed false positives where check would match 3-argument calls e.g. s.rfind(v, 0, 3) as if it had only 2 arguments.
  2. Enhanced docs to show that check also matches u.find(v, 0) == 0 case.

Closes #129498.

P.S. In the issue one of the proposed fix to the problem was to transform s.find(v, 0, 3) into s.starts_with({v, 3}). But for me creating a string_view "on the go" is confusing since we create another object, what do others think?
FYI @nicovank

I can implement creation of string_view but for now I ignore those cases.

@llvmbot
Copy link
Member

llvmbot commented Mar 3, 2025

@llvm/pr-subscribers-clang-tools-extra

Author: Baranov Victor (vbvictor)

Changes
  1. Fixed false positives where check would match 3-argument calls e.g. s.rfind(v, 0, 3) as if it had only 2 arguments.
  2. Enhanced docs to show that check also matches u.find(v, 0) == 0 case.

Closes #129498.

P.S. In the issue one of the proposed fix to the problem was to transform s.find(v, 0, 3) into s.starts_with({v, 3}). But for me creating a string_view "on the go" is confusing since we create another object, what do others think?
FYI @nicovank

I can implement creation of string_view but for now I ignore those cases.


Full diff: https://github.com/llvm/llvm-project/pull/129564.diff

4 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp (+4-3)
  • (modified) clang-tools-extra/docs/ReleaseNotes.rst (+4)
  • (modified) clang-tools-extra/docs/clang-tidy/checks/modernize/use-starts-ends-with.rst (+1)
  • (modified) clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp (+15)
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
index ab72c6796bb4c..02a537cccc430 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
@@ -113,9 +113,10 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
   const auto OnClassWithEndsWithFunction = ClassTypeWithMethod(
       "ends_with_fun", "ends_with", "endsWith", "endswith", "EndsWith");
 
-  // Case 1: X.find(Y) [!=]= 0 -> starts_with.
+  // Case 1: X.find(Y, [0]) [!=]= 0 -> starts_with.
   const auto FindExpr = cxxMemberCallExpr(
-      anyOf(argumentCountIs(1), hasArgument(1, ZeroLiteral)),
+      anyOf(argumentCountIs(1),
+            allOf(argumentCountIs(2), hasArgument(1, ZeroLiteral))),
       callee(
           cxxMethodDecl(hasName("find"), ofClass(OnClassWithStartsWithFunction))
               .bind("find_fun")),
@@ -123,7 +124,7 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
 
   // Case 2: X.rfind(Y, 0) [!=]= 0 -> starts_with.
   const auto RFindExpr = cxxMemberCallExpr(
-      hasArgument(1, ZeroLiteral),
+      argumentCountIs(2), hasArgument(1, ZeroLiteral),
       callee(cxxMethodDecl(hasName("rfind"),
                            ofClass(OnClassWithStartsWithFunction))
                  .bind("find_fun")),
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 07a79d6bbe807..e29598ccf0b43 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -137,6 +137,10 @@ Changes in existing checks
   <clang-tidy/checks/performance/move-const-arg>` check by fixing false negatives
   on ternary operators calling ``std::move``.
 
+- Improved :doc:`modernize-use-starts-ends-with
+  <clang-tidy/checks/modernize/use-starts-ends-with>` check by fixing false
+  positives on methods ``find`` and ``rfind`` called with three arguments.
+
 Removed checks
 ^^^^^^^^^^^^^^
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-starts-ends-with.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-starts-ends-with.rst
index 78cd900885ac3..bad1952db22f9 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-starts-ends-with.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-starts-ends-with.rst
@@ -13,6 +13,7 @@ Covered scenarios:
 Expression                                           Replacement
 ---------------------------------------------------- ---------------------
 ``u.find(v) == 0``                                   ``u.starts_with(v)``
+``u.find(v, 0) == 0``                                ``u.starts_with(v)``
 ``u.rfind(v, 0) != 0``                               ``!u.starts_with(v)``
 ``u.compare(0, v.size(), v) == 0``                   ``u.starts_with(v)``
 ``u.substr(0, v.size()) == v``                       ``u.starts_with(v)``
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp
index 8699ca03ba331..4d61709eff463 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp
@@ -236,6 +236,18 @@ void test(std::string s, std::string_view sv, sub_string ss, sub_sub_string sss,
   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
   // CHECK-FIXES: s.ends_with(suffix);
 
+  s.find("a", 0) == 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+  // CHECK-FIXES: s.starts_with("a");
+  
+  s.find(s, ZERO) == 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+  // CHECK-FIXES: s.starts_with(s);
+
+  s.find(s, 0) == ZERO;
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+  // CHECK-FIXES: s.starts_with(s);
+
   struct S {
     std::string s;
   } t;
@@ -261,6 +273,9 @@ void test(std::string s, std::string_view sv, sub_string ss, sub_sub_string sss,
 
   #define STRING s
   if (0 == STRING.find("ala")) { /* do something */}
+
+  s.find("aaa", 0, 3) == 0;
+  s.rfind("aaa", std::string::npos, 3) == 0;
 }
 
 void test_substr() {

@llvmbot
Copy link
Member

llvmbot commented Mar 3, 2025

@llvm/pr-subscribers-clang-tidy

Author: Baranov Victor (vbvictor)

Changes
  1. Fixed false positives where check would match 3-argument calls e.g. s.rfind(v, 0, 3) as if it had only 2 arguments.
  2. Enhanced docs to show that check also matches u.find(v, 0) == 0 case.

Closes #129498.

P.S. In the issue one of the proposed fix to the problem was to transform s.find(v, 0, 3) into s.starts_with({v, 3}). But for me creating a string_view "on the go" is confusing since we create another object, what do others think?
FYI @nicovank

I can implement creation of string_view but for now I ignore those cases.


Full diff: https://github.com/llvm/llvm-project/pull/129564.diff

4 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp (+4-3)
  • (modified) clang-tools-extra/docs/ReleaseNotes.rst (+4)
  • (modified) clang-tools-extra/docs/clang-tidy/checks/modernize/use-starts-ends-with.rst (+1)
  • (modified) clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp (+15)
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
index ab72c6796bb4c..02a537cccc430 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
@@ -113,9 +113,10 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
   const auto OnClassWithEndsWithFunction = ClassTypeWithMethod(
       "ends_with_fun", "ends_with", "endsWith", "endswith", "EndsWith");
 
-  // Case 1: X.find(Y) [!=]= 0 -> starts_with.
+  // Case 1: X.find(Y, [0]) [!=]= 0 -> starts_with.
   const auto FindExpr = cxxMemberCallExpr(
-      anyOf(argumentCountIs(1), hasArgument(1, ZeroLiteral)),
+      anyOf(argumentCountIs(1),
+            allOf(argumentCountIs(2), hasArgument(1, ZeroLiteral))),
       callee(
           cxxMethodDecl(hasName("find"), ofClass(OnClassWithStartsWithFunction))
               .bind("find_fun")),
@@ -123,7 +124,7 @@ void UseStartsEndsWithCheck::registerMatchers(MatchFinder *Finder) {
 
   // Case 2: X.rfind(Y, 0) [!=]= 0 -> starts_with.
   const auto RFindExpr = cxxMemberCallExpr(
-      hasArgument(1, ZeroLiteral),
+      argumentCountIs(2), hasArgument(1, ZeroLiteral),
       callee(cxxMethodDecl(hasName("rfind"),
                            ofClass(OnClassWithStartsWithFunction))
                  .bind("find_fun")),
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 07a79d6bbe807..e29598ccf0b43 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -137,6 +137,10 @@ Changes in existing checks
   <clang-tidy/checks/performance/move-const-arg>` check by fixing false negatives
   on ternary operators calling ``std::move``.
 
+- Improved :doc:`modernize-use-starts-ends-with
+  <clang-tidy/checks/modernize/use-starts-ends-with>` check by fixing false
+  positives on methods ``find`` and ``rfind`` called with three arguments.
+
 Removed checks
 ^^^^^^^^^^^^^^
 
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-starts-ends-with.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-starts-ends-with.rst
index 78cd900885ac3..bad1952db22f9 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-starts-ends-with.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-starts-ends-with.rst
@@ -13,6 +13,7 @@ Covered scenarios:
 Expression                                           Replacement
 ---------------------------------------------------- ---------------------
 ``u.find(v) == 0``                                   ``u.starts_with(v)``
+``u.find(v, 0) == 0``                                ``u.starts_with(v)``
 ``u.rfind(v, 0) != 0``                               ``!u.starts_with(v)``
 ``u.compare(0, v.size(), v) == 0``                   ``u.starts_with(v)``
 ``u.substr(0, v.size()) == v``                       ``u.starts_with(v)``
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp
index 8699ca03ba331..4d61709eff463 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-starts-ends-with.cpp
@@ -236,6 +236,18 @@ void test(std::string s, std::string_view sv, sub_string ss, sub_sub_string sss,
   // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use ends_with
   // CHECK-FIXES: s.ends_with(suffix);
 
+  s.find("a", 0) == 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+  // CHECK-FIXES: s.starts_with("a");
+  
+  s.find(s, ZERO) == 0;
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+  // CHECK-FIXES: s.starts_with(s);
+
+  s.find(s, 0) == ZERO;
+  // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+  // CHECK-FIXES: s.starts_with(s);
+
   struct S {
     std::string s;
   } t;
@@ -261,6 +273,9 @@ void test(std::string s, std::string_view sv, sub_string ss, sub_sub_string sss,
 
   #define STRING s
   if (0 == STRING.find("ala")) { /* do something */}
+
+  s.find("aaa", 0, 3) == 0;
+  s.rfind("aaa", std::string::npos, 3) == 0;
 }
 
 void test_substr() {

Copy link
Contributor

@nicovank nicovank left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!! I think something could also be done incorporating lengthExprForStringNode, with this patch some TPs will be ignored. lengthExprForStringNode will ignore char* unless strlen is used in which case the string has to be null-terminated anyway. What do you think? EDIT: or a char* literal, in which case it's also null terminated.

I'm also okay with landing this ignore now, those FPs are probably exceedingly rare.

Creating string views is more involved and I also think not needed for this change.

@vbvictor vbvictor requested a review from nicovank March 5, 2025 21:37
Copy link
Contributor

@nicovank nicovank left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!!

@vbvictor
Copy link
Contributor Author

Reverted style changes in ReleaseNotes.rst, created new PR for cleanup: #130626.

Copy link
Contributor

@nicovank nicovank left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw you have a pending commit access request -- I'll just let you merge this when you are able to.

@vbvictor
Copy link
Contributor Author

Since commit access request takes time than I expected, could you please merge this PR, @nicovank?
Thank you in advance

@nicovank
Copy link
Contributor

Actually, with the other one merged I feel like there'll be a conflict with GitHub trying to auto-merge. Can you rebase on main? Then I'll click the button.

@EugeneZelenko
Copy link
Contributor

Actually, with the other one merged I feel like there'll be a conflict with GitHub trying to auto-merge. Can you rebase on main? Then I'll click the button.

Please make sure that Release Notes entries is in alphabetical order after rebase. There should be merge conflicts because of changes in Release Notes.

@vbvictor vbvictor force-pushed the modernize-use-starts-ends-with-issue129498 branch from 50c73aa to e72ae0e Compare March 22, 2025 10:30
@vbvictor
Copy link
Contributor Author

Rebase and PR-checks are done, ready to merge

@nicovank nicovank merged commit 2909c42 into llvm:main Mar 22, 2025
12 checks passed
@nicovank
Copy link
Contributor

Thanks!

@vbvictor vbvictor deleted the modernize-use-starts-ends-with-issue129498 branch June 22, 2025 08:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Clang tidy modernize-use-starts-ends-with suggest incorrect fixit if additional count argument is passed to rfind/find
4 participants