Skip to content

Commit febd89c

Browse files
authored
[clang-tidy] check std::string_view and custom string-like classes in readability-string-compare (#88636)
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)
1 parent 443377a commit febd89c

File tree

7 files changed

+139
-8
lines changed

7 files changed

+139
-8
lines changed

clang-tools-extra/clang-tidy/readability/StringCompareCheck.cpp

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,43 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "StringCompareCheck.h"
10-
#include "../utils/FixItHintUtils.h"
10+
#include "../utils/OptionsUtils.h"
1111
#include "clang/AST/ASTContext.h"
1212
#include "clang/ASTMatchers/ASTMatchFinder.h"
13+
#include "clang/ASTMatchers/ASTMatchers.h"
1314
#include "clang/Tooling/FixIt.h"
15+
#include "llvm/ADT/StringRef.h"
1416

1517
using namespace clang::ast_matchers;
18+
namespace optutils = clang::tidy::utils::options;
1619

1720
namespace clang::tidy::readability {
1821

1922
static const StringRef CompareMessage = "do not use 'compare' to test equality "
2023
"of strings; use the string equality "
2124
"operator instead";
2225

26+
static const StringRef DefaultStringLikeClasses = "::std::basic_string;"
27+
"::std::basic_string_view";
28+
29+
StringCompareCheck::StringCompareCheck(StringRef Name,
30+
ClangTidyContext *Context)
31+
: ClangTidyCheck(Name, Context),
32+
StringLikeClasses(optutils::parseStringList(
33+
Options.get("StringLikeClasses", DefaultStringLikeClasses))) {}
34+
35+
void StringCompareCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
36+
Options.store(Opts, "StringLikeClasses",
37+
optutils::serializeStringList(StringLikeClasses));
38+
}
39+
2340
void StringCompareCheck::registerMatchers(MatchFinder *Finder) {
41+
if (StringLikeClasses.empty()) {
42+
return;
43+
}
2444
const auto StrCompare = cxxMemberCallExpr(
25-
callee(cxxMethodDecl(hasName("compare"),
26-
ofClass(classTemplateSpecializationDecl(
27-
hasName("::std::basic_string"))))),
45+
callee(cxxMethodDecl(hasName("compare"), ofClass(cxxRecordDecl(hasAnyName(
46+
StringLikeClasses))))),
2847
hasArgument(0, expr().bind("str2")), argumentCountIs(1),
2948
callee(memberExpr().bind("str1")));
3049

clang-tools-extra/clang-tidy/readability/StringCompareCheck.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STRINGCOMPARECHECK_H
1111

1212
#include "../ClangTidyCheck.h"
13+
#include <vector>
1314

1415
namespace clang::tidy::readability {
1516

@@ -20,13 +21,18 @@ namespace clang::tidy::readability {
2021
/// http://clang.llvm.org/extra/clang-tidy/checks/readability/string-compare.html
2122
class StringCompareCheck : public ClangTidyCheck {
2223
public:
23-
StringCompareCheck(StringRef Name, ClangTidyContext *Context)
24-
: ClangTidyCheck(Name, Context) {}
24+
StringCompareCheck(StringRef Name, ClangTidyContext *Context);
25+
2526
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
2627
return LangOpts.CPlusPlus;
2728
}
29+
2830
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
2931
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
32+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
33+
34+
private:
35+
const std::vector<StringRef> StringLikeClasses;
3036
};
3137

3238
} // namespace clang::tidy::readability

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,11 @@ Changes in existing checks
362362
check by resolving fix-it overlaps in template code by disregarding implicit
363363
instances.
364364

365+
- Improved :doc:`readability-string-compare
366+
<clang-tidy/checks/readability/string-compare>` check to also detect
367+
usages of ``std::string_view::compare``. Added a `StringLikeClasses` option
368+
to detect usages of ``compare`` method in custom string-like classes.
369+
365370
Removed checks
366371
^^^^^^^^^^^^^^
367372

clang-tools-extra/docs/clang-tidy/checks/readability/string-compare.rst

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ recommended to avoid the risk of incorrect interpretation of the return value
1414
and to simplify the code. The string equality and inequality operators can
1515
also be faster than the ``compare`` method due to early termination.
1616

17-
Examples:
17+
Example
18+
-------
1819

1920
.. code-block:: c++
2021

22+
// The same rules apply to std::string_view.
2123
std::string str1{"a"};
2224
std::string str2{"b"};
2325

@@ -50,5 +52,36 @@ Examples:
5052
}
5153

5254
The above code examples show the list of if-statements that this check will
53-
give a warning for. All of them uses ``compare`` to check if equality or
55+
give a warning for. All of them use ``compare`` to check equality or
5456
inequality of two strings instead of using the correct operators.
57+
58+
Options
59+
-------
60+
61+
.. option:: StringLikeClasses
62+
63+
A string containing semicolon-separated names of string-like classes.
64+
By default contains only ``::std::basic_string``
65+
and ``::std::basic_string_view``. If a class from this list has
66+
a ``compare`` method similar to that of ``std::string``, it will be checked
67+
in the same way.
68+
69+
Example
70+
^^^^^^^
71+
72+
.. code-block:: c++
73+
74+
struct CustomString {
75+
public:
76+
int compare (const CustomString& other) const;
77+
}
78+
79+
CustomString str1;
80+
CustomString str2;
81+
82+
// use str1 != str2 instead.
83+
if (str1.compare(str2)) {
84+
}
85+
86+
If `StringLikeClasses` contains ``CustomString``, the check will suggest
87+
replacing ``compare`` with equality operator.

clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ struct basic_string_view {
108108
constexpr bool starts_with(C ch) const noexcept;
109109
constexpr bool starts_with(const C* s) const;
110110

111+
constexpr int compare(basic_string_view sv) const noexcept;
112+
111113
static constexpr size_t npos = -1;
112114
};
113115

@@ -132,6 +134,14 @@ bool operator==(const std::wstring&, const std::wstring&);
132134
bool operator==(const std::wstring&, const wchar_t*);
133135
bool operator==(const wchar_t*, const std::wstring&);
134136

137+
bool operator==(const std::string_view&, const std::string_view&);
138+
bool operator==(const std::string_view&, const char*);
139+
bool operator==(const char*, const std::string_view&);
140+
141+
bool operator!=(const std::string_view&, const std::string_view&);
142+
bool operator!=(const std::string_view&, const char*);
143+
bool operator!=(const char*, const std::string_view&);
144+
135145
size_t strlen(const char* str);
136146
}
137147

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// RUN: %check_clang_tidy %s readability-string-compare %t -- -config='{CheckOptions: {readability-string-compare.StringLikeClasses: "CustomStringTemplateBase;CustomStringNonTemplateBase"}}' -- -isystem %clang_tidy_headers
2+
#include <string>
3+
4+
struct CustomStringNonTemplateBase {
5+
int compare(const CustomStringNonTemplateBase& Other) const {
6+
return 123; // value is not important for check
7+
}
8+
};
9+
10+
template <typename T>
11+
struct CustomStringTemplateBase {
12+
int compare(const CustomStringTemplateBase& Other) const {
13+
return 123;
14+
}
15+
};
16+
17+
struct CustomString1 : CustomStringNonTemplateBase {};
18+
struct CustomString2 : CustomStringTemplateBase<char> {};
19+
20+
void CustomStringClasses() {
21+
std::string_view sv1("a");
22+
std::string_view sv2("b");
23+
if (sv1.compare(sv2)) { // No warning - if a std class is not listed in StringLikeClasses, it won't be checked.
24+
}
25+
26+
CustomString1 custom1;
27+
if (custom1.compare(custom1)) {
28+
}
29+
// CHECK-MESSAGES: [[@LINE-2]]:7: warning: do not use 'compare' to test equality of strings; use the string equality operator instead [readability-string-compare]
30+
31+
CustomString2 custom2;
32+
if (custom2.compare(custom2)) {
33+
}
34+
// CHECK-MESSAGES: [[@LINE-2]]:7: warning: do not use 'compare' to test equality of strings; use the string equality operator instead [readability-string-compare]
35+
}

clang-tools-extra/test/clang-tidy/checkers/readability/string-compare.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,27 @@ void Test() {
6767
if (str1.compare(comp())) {
6868
}
6969
// CHECK-MESSAGES: [[@LINE-2]]:7: warning: do not use 'compare' to test equality of strings;
70+
71+
std::string_view sv1("a");
72+
std::string_view sv2("b");
73+
if (sv1.compare(sv2)) {
74+
}
75+
// CHECK-MESSAGES: [[@LINE-2]]:7: warning: do not use 'compare' to test equality of strings; use the string equality operator instead [readability-string-compare]
76+
}
77+
78+
struct DerivedFromStdString : std::string {};
79+
80+
void TestDerivedClass() {
81+
DerivedFromStdString derived;
82+
if (derived.compare(derived)) {
83+
}
84+
// CHECK-MESSAGES: [[@LINE-2]]:7: warning: do not use 'compare' to test equality of strings; use the string equality operator instead [readability-string-compare]
7085
}
7186

7287
void Valid() {
7388
std::string str1("a", 1);
7489
std::string str2("b", 1);
90+
7591
if (str1 == str2) {
7692
}
7793
if (str1 != str2) {
@@ -96,4 +112,11 @@ void Valid() {
96112
}
97113
if (str1.compare(str2) == -1) {
98114
}
115+
116+
std::string_view sv1("a");
117+
std::string_view sv2("b");
118+
if (sv1 == sv2) {
119+
}
120+
if (sv1.compare(sv2) > 0) {
121+
}
99122
}

0 commit comments

Comments
 (0)