[clang-tools-extra] [clang-tidy] check `std::string_view` and custom string-like classes in `readability-string-compare` (PR #88636)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Apr 13 15:21:40 PDT 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-tidy
Author: Vadim D. (vvd170501)
<details>
<summary>Changes</summary>
This PR aims to expand the list of classes that are considered to be "strings" by `readability-string-compare` check.
1. Currently only `std::string;:compare` is checked, but `std::string_view` has a similar `compare` method. This PR enables checking of `std::string_view::compare` by default.
2. Some codebases use custom string-like classes that have public interfaces similar to `std::string` or `std::string_view`. Example: [TStringBase](https://github.com/yandex/yatool/blob/main/util/generic/strbase.h#L38), A new option, `readability-string-compare.StringClassNames`, is added to allow specifying a custom list of string-like classes.
Related to, but does not solve #<!-- -->28396 (only adds support for custom string-like classes, not custom functions)
---
Full diff: https://github.com/llvm/llvm-project/pull/88636.diff
7 Files Affected:
- (modified) clang-tools-extra/clang-tidy/readability/StringCompareCheck.cpp (+51-23)
- (modified) clang-tools-extra/clang-tidy/readability/StringCompareCheck.h (+7-2)
- (modified) clang-tools-extra/docs/ReleaseNotes.rst (+5)
- (modified) clang-tools-extra/docs/clang-tidy/checks/readability/string-compare.rst (+31-2)
- (modified) clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string (+10)
- (added) clang-tools-extra/test/clang-tidy/checkers/readability/string-compare-custom-string-classes.cpp (+38)
- (modified) clang-tools-extra/test/clang-tidy/checkers/readability/string-compare.cpp (+23)
``````````diff
diff --git a/clang-tools-extra/clang-tidy/readability/StringCompareCheck.cpp b/clang-tools-extra/clang-tidy/readability/StringCompareCheck.cpp
index 3b5d89c8c64719..905e5b156ef864 100644
--- a/clang-tools-extra/clang-tidy/readability/StringCompareCheck.cpp
+++ b/clang-tools-extra/clang-tidy/readability/StringCompareCheck.cpp
@@ -7,12 +7,16 @@
//===----------------------------------------------------------------------===//
#include "StringCompareCheck.h"
-#include "../utils/FixItHintUtils.h"
+#include "../utils/OptionsUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Tooling/FixIt.h"
+#include "llvm/ADT/StringRef.h"
+#include <vector>
using namespace clang::ast_matchers;
+namespace optutils = clang::tidy::utils::options;
namespace clang::tidy::readability {
@@ -20,29 +24,53 @@ static const StringRef CompareMessage = "do not use 'compare' to test equality "
"of strings; use the string equality "
"operator instead";
+static const std::vector<StringRef> StringClasses = {
+ "::std::basic_string", "::std::basic_string_view"};
+
+StringCompareCheck::StringCompareCheck(StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ StringLikeClasses(
+ optutils::parseStringList(Options.get("StringLikeClasses", ""))) {}
+
void StringCompareCheck::registerMatchers(MatchFinder *Finder) {
- const auto StrCompare = cxxMemberCallExpr(
- callee(cxxMethodDecl(hasName("compare"),
- ofClass(classTemplateSpecializationDecl(
- hasName("::std::basic_string"))))),
- hasArgument(0, expr().bind("str2")), argumentCountIs(1),
- callee(memberExpr().bind("str1")));
-
- // First and second case: cast str.compare(str) to boolean.
- Finder->addMatcher(
- traverse(TK_AsIs,
- implicitCastExpr(hasImplicitDestinationType(booleanType()),
- has(StrCompare))
- .bind("match1")),
- this);
-
- // Third and fourth case: str.compare(str) == 0 and str.compare(str) != 0.
- Finder->addMatcher(
- binaryOperator(hasAnyOperatorName("==", "!="),
- hasOperands(StrCompare.bind("compare"),
- integerLiteral(equals(0)).bind("zero")))
- .bind("match2"),
- this);
+ const auto RegisterForClasses = [&, this](const auto &StringClassMatcher) {
+ const auto StrCompare = cxxMemberCallExpr(
+ callee(cxxMethodDecl(hasName("compare"), ofClass(StringClassMatcher))),
+ hasArgument(0, expr().bind("str2")), argumentCountIs(1),
+ callee(memberExpr().bind("str1")));
+
+ // First and second case: cast str.compare(str) to boolean.
+ Finder->addMatcher(
+ traverse(TK_AsIs,
+ implicitCastExpr(hasImplicitDestinationType(booleanType()),
+ has(StrCompare))
+ .bind("match1")),
+ this);
+
+ // Third and fourth case: str.compare(str) == 0
+ // and str.compare(str) != 0.
+ Finder->addMatcher(
+ binaryOperator(hasAnyOperatorName("==", "!="),
+ hasOperands(StrCompare.bind("compare"),
+ integerLiteral(equals(0)).bind("zero")))
+ .bind("match2"),
+ this);
+ };
+ if (StringLikeClasses.empty()) {
+ RegisterForClasses(
+ classTemplateSpecializationDecl(hasAnyName(StringClasses)));
+ } else {
+ // StringLikeClasses may or may not be templates, so we need to match both
+ // template and non-template classes.
+ std::vector<StringRef> PossiblyTemplateClasses = StringClasses;
+ PossiblyTemplateClasses.insert(PossiblyTemplateClasses.end(),
+ StringLikeClasses.begin(),
+ StringLikeClasses.end());
+ RegisterForClasses(anyOf(
+ classTemplateSpecializationDecl(hasAnyName(PossiblyTemplateClasses)),
+ cxxRecordDecl(hasAnyName(StringLikeClasses))));
+ }
}
void StringCompareCheck::check(const MatchFinder::MatchResult &Result) {
diff --git a/clang-tools-extra/clang-tidy/readability/StringCompareCheck.h b/clang-tools-extra/clang-tidy/readability/StringCompareCheck.h
index 812736d806b71d..b7bb5a0097645b 100644
--- a/clang-tools-extra/clang-tidy/readability/StringCompareCheck.h
+++ b/clang-tools-extra/clang-tidy/readability/StringCompareCheck.h
@@ -10,6 +10,7 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STRINGCOMPARECHECK_H
#include "../ClangTidyCheck.h"
+#include <vector>
namespace clang::tidy::readability {
@@ -20,13 +21,17 @@ namespace clang::tidy::readability {
/// http://clang.llvm.org/extra/clang-tidy/checks/readability/string-compare.html
class StringCompareCheck : public ClangTidyCheck {
public:
- StringCompareCheck(StringRef Name, ClangTidyContext *Context)
- : ClangTidyCheck(Name, Context) {}
+ StringCompareCheck(StringRef Name, ClangTidyContext *Context);
+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus;
}
+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+ const std::vector<StringRef> StringLikeClasses;
};
} // namespace clang::tidy::readability
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 1405fb0df1f8dd..cbafff01b58592 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -286,6 +286,11 @@ Changes in existing checks
check by resolving fix-it overlaps in template code by disregarding implicit
instances.
+- Improved :doc:`readability-string-compare
+ <clang-tidy/checks/readability/string-compare>` check to also detect
+ usages of `std::string_view::compare`. Added a `StringLikeClasses` option
+ to detect usages of `compare` method in custom string-like classes.
+
Removed checks
^^^^^^^^^^^^^^
diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/string-compare.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/string-compare.rst
index 268632eee61a27..f86ea6704ad56e 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/readability/string-compare.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/readability/string-compare.rst
@@ -14,10 +14,12 @@ recommended to avoid the risk of incorrect interpretation of the return value
and to simplify the code. The string equality and inequality operators can
also be faster than the ``compare`` method due to early termination.
-Examples:
+Example
+-------
.. code-block:: c++
+ // The same rules apply to std::string_view.
std::string str1{"a"};
std::string str2{"b"};
@@ -50,5 +52,32 @@ Examples:
}
The above code examples show the list of if-statements that this check will
-give a warning for. All of them uses ``compare`` to check if equality or
+give a warning for. All of them use ``compare`` to check equality or
inequality of two strings instead of using the correct operators.
+
+Options
+-------
+
+.. option:: StringLikeClasses
+
+ A string containing comma-separated names of string-like classes. Default is an empty string.
+ If a class from this list has a ``compare`` method similar to ``std::string``, it will be checked in the same way.
+
+Example
+^^^^^^^
+
+.. code-block:: c++
+
+ struct CustomString {
+ public:
+ int compare (const CustomString& other) const;
+ }
+
+ CustomString str1;
+ CustomString str2;
+
+ // use str1 != str2 instead.
+ if (str1.compare(str2)) {
+ }
+
+If `StringLikeClasses` contains ``CustomString``, the check will suggest replacing ``compare`` with equality operator.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string
index 28e2b4a231e52e..bc58fdad3fd44e 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string
@@ -106,6 +106,8 @@ struct basic_string_view {
constexpr bool starts_with(C ch) const noexcept;
constexpr bool starts_with(const C* s) const;
+ constexpr int compare(basic_string_view sv) const noexcept;
+
static constexpr size_t npos = -1;
};
@@ -129,6 +131,14 @@ bool operator!=(const char*, const std::string&);
bool operator==(const std::wstring&, const std::wstring&);
bool operator==(const std::wstring&, const wchar_t*);
bool operator==(const wchar_t*, const std::wstring&);
+
+bool operator==(const std::string_view&, const std::string_view&);
+bool operator==(const std::string_view&, const char*);
+bool operator==(const char*, const std::string_view&);
+
+bool operator!=(const std::string_view&, const std::string_view&);
+bool operator!=(const std::string_view&, const char*);
+bool operator!=(const char*, const std::string_view&);
}
#endif // _STRING_
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/string-compare-custom-string-classes.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/string-compare-custom-string-classes.cpp
new file mode 100644
index 00000000000000..2bb47d33da389d
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/string-compare-custom-string-classes.cpp
@@ -0,0 +1,38 @@
+// RUN: %check_clang_tidy %s readability-string-compare %t -- -config='{CheckOptions: {readability-string-compare.StringLikeClasses: "CustomStringTemplateBase;CustomStringNonTemplateBase"}}' -- -isystem %clang_tidy_headers
+#include <string>
+
+struct CustomStringNonTemplateBase {
+ int compare(const CustomStringNonTemplateBase& Other) const {
+ return 123; // value is not important for check
+ }
+};
+
+template <typename T>
+struct CustomStringTemplateBase {
+ int compare(const CustomStringTemplateBase& Other) const {
+ return 123;
+ }
+};
+
+struct CustomString1 : CustomStringNonTemplateBase {};
+struct CustomString2 : CustomStringTemplateBase<char> {};
+
+void StdClassesAreStillDetected() {
+ std::string_view sv1("a");
+ std::string_view sv2("b");
+ if (sv1.compare(sv2)) {
+ }
+ // CHECK-MESSAGES: [[@LINE-2]]:7: warning: do not use 'compare' to test equality of strings; use the string equality operator instead [readability-string-compare]
+}
+
+void CustomStringClasses() {
+ CustomString1 custom1;
+ if (custom1.compare(custom1)) {
+ }
+ // CHECK-MESSAGES: [[@LINE-2]]:7: warning: do not use 'compare' to test equality of strings; use the string equality operator instead [readability-string-compare]
+
+ CustomString2 custom2;
+ if (custom2.compare(custom2)) {
+ }
+ // CHECK-MESSAGES: [[@LINE-2]]:7: warning: do not use 'compare' to test equality of strings; use the string equality operator instead [readability-string-compare]
+}
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/string-compare.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/string-compare.cpp
index 2c08b86cf72fa0..c4fea4341617b8 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/readability/string-compare.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/string-compare.cpp
@@ -67,11 +67,27 @@ void Test() {
if (str1.compare(comp())) {
}
// CHECK-MESSAGES: [[@LINE-2]]:7: warning: do not use 'compare' to test equality of strings;
+
+ std::string_view sv1("a");
+ std::string_view sv2("b");
+ if (sv1.compare(sv2)) {
+ }
+ // CHECK-MESSAGES: [[@LINE-2]]:7: warning: do not use 'compare' to test equality of strings; use the string equality operator instead [readability-string-compare]
+}
+
+struct DerivedFromStdString : std::string {};
+
+void TestDerivedClass() {
+ DerivedFromStdString derived;
+ if (derived.compare(derived)) {
+ }
+ // CHECK-MESSAGES: [[@LINE-2]]:7: warning: do not use 'compare' to test equality of strings; use the string equality operator instead [readability-string-compare]
}
void Valid() {
std::string str1("a", 1);
std::string str2("b", 1);
+
if (str1 == str2) {
}
if (str1 != str2) {
@@ -96,4 +112,11 @@ void Valid() {
}
if (str1.compare(str2) == -1) {
}
+
+ std::string_view sv1("a");
+ std::string_view sv2("b");
+ if (sv1 == sv2) {
+ }
+ if (sv1.compare(sv2) > 0) {
+ }
}
``````````
</details>
https://github.com/llvm/llvm-project/pull/88636
More information about the cfe-commits
mailing list