[clang-tools-extra] [clang-tidy] 'modernize-use-starts-ends-with': fixed false positives on `find` and `rfind` (PR #129564)
Baranov Victor via cfe-commits
cfe-commits at lists.llvm.org
Sat Mar 22 03:30:25 PDT 2025
https://github.com/vbvictor updated https://github.com/llvm/llvm-project/pull/129564
>From e72ae0e2ee12265ec69b0c5ff726261eb7466caa Mon Sep 17 00:00:00 2001
From: Victor Baranov <bar.victor.2002 at gmail.com>
Date: Sat, 22 Mar 2025 13:29:20 +0300
Subject: [PATCH] [clang-tidy] fixed false positives on find and rfind
---
.../modernize/UseStartsEndsWithCheck.cpp | 26 ++++++++----
clang-tools-extra/docs/ReleaseNotes.rst | 5 +++
.../checks/modernize/use-starts-ends-with.rst | 3 ++
.../modernize/use-starts-ends-with.cpp | 42 +++++++++++++++++++
4 files changed, 69 insertions(+), 7 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
index ab72c6796bb4c..2e059f24d47b6 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseStartsEndsWithCheck.cpp
@@ -113,21 +113,33 @@ 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], [LEN(Y)]) [!=]= 0 -> starts_with.
const auto FindExpr = cxxMemberCallExpr(
- anyOf(argumentCountIs(1), hasArgument(1, ZeroLiteral)),
callee(
cxxMethodDecl(hasName("find"), ofClass(OnClassWithStartsWithFunction))
.bind("find_fun")),
- hasArgument(0, expr().bind("needle")));
-
- // Case 2: X.rfind(Y, 0) [!=]= 0 -> starts_with.
+ hasArgument(0, expr().bind("needle")),
+ anyOf(
+ // Detect the expression: X.find(Y);
+ argumentCountIs(1),
+ // Detect the expression: X.find(Y, 0);
+ allOf(argumentCountIs(2), hasArgument(1, ZeroLiteral)),
+ // Detect the expression: X.find(Y, 0, LEN(Y));
+ allOf(argumentCountIs(3), hasArgument(1, ZeroLiteral),
+ hasArgument(2, lengthExprForStringNode("needle")))));
+
+ // Case 2: X.rfind(Y, 0, [LEN(Y)]) [!=]= 0 -> starts_with.
const auto RFindExpr = cxxMemberCallExpr(
- hasArgument(1, ZeroLiteral),
callee(cxxMethodDecl(hasName("rfind"),
ofClass(OnClassWithStartsWithFunction))
.bind("find_fun")),
- hasArgument(0, expr().bind("needle")));
+ hasArgument(0, expr().bind("needle")),
+ anyOf(
+ // Detect the expression: X.rfind(Y, 0);
+ allOf(argumentCountIs(2), hasArgument(1, ZeroLiteral)),
+ // Detect the expression: X.rfind(Y, 0, LEN(Y));
+ allOf(argumentCountIs(3), hasArgument(1, ZeroLiteral),
+ hasArgument(2, lengthExprForStringNode("needle")))));
// Case 3: X.compare(0, LEN(Y), Y) [!=]= 0 -> starts_with.
const auto CompareExpr = cxxMemberCallExpr(
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 72aa05eb4dcd1..ed7da975f3de7 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -175,6 +175,11 @@ Changes in existing checks
<clang-tidy/checks/modernize/use-ranges>` check by updating suppress
warnings logic for ``nullptr`` in ``std::find``.
+- Improved :doc:`modernize-use-starts-ends-with
+ <clang-tidy/checks/modernize/use-starts-ends-with>` check by adding more
+ matched scenarios of ``find`` and ``rfind`` methods and fixing false
+ positives when those methods were called with 3 arguments.
+
- Improved :doc:`modernize-use-std-numbers
<clang-tidy/checks/modernize/use-std-numbers>` check to support math
functions of different precisions.
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..1babc2d1660ec 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,7 +13,10 @@ Covered scenarios:
Expression Replacement
---------------------------------------------------- ---------------------
``u.find(v) == 0`` ``u.starts_with(v)``
+``u.find(v, 0) == 0`` ``u.starts_with(v)``
+``u.find(v, 0, v.size()) == 0`` ``u.starts_with(v)``
``u.rfind(v, 0) != 0`` ``!u.starts_with(v)``
+``u.rfind(v, 0, v.size()) != 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)``
``v != u.substr(0, v.size())`` ``!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..cd8463401e45a 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,42 @@ 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);
+
+ s.find("aaa", 0, 3) == 0;
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-FIXES: s.starts_with("aaa");
+
+ s.find("aaa", ZERO, 3) == 0;
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-FIXES: s.starts_with("aaa");
+
+ s.find("aaa", ZERO, strlen(("aaa"))) == ZERO;
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-FIXES: s.starts_with("aaa");
+
+ s.rfind("aaa", 0, 3) != 0;
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-FIXES: !s.starts_with("aaa");
+
+ s.rfind("aaa", ZERO, 3) != 0;
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-FIXES: !s.starts_with("aaa");
+
+ s.rfind("aaa", ZERO, strlen(("aaa"))) == ZERO;
+ // CHECK-MESSAGES: :[[@LINE-1]]:{{[0-9]+}}: warning: use starts_with
+ // CHECK-FIXES: s.starts_with("aaa");
+
struct S {
std::string s;
} t;
@@ -261,6 +297,12 @@ 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 */}
+
+ // Cases when literal-size and size parameters are different are not being matched.
+ s.find("aaa", 0, 2) == 0;
+ s.find("aaa", 0, strlen("aa")) == 0;
+ s.rfind("aaa", 0, 2) == 0;
+ s.rfind("aaa", 0, strlen("aa")) == 0;
}
void test_substr() {
More information about the cfe-commits
mailing list