Skip to content

Commit b63dd11

Browse files
committed
readability-string-compare: check std::string_view, allow custom string-like classes
1 parent fad3752 commit b63dd11

File tree

7 files changed

+163
-27
lines changed

7 files changed

+163
-27
lines changed

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

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,70 @@
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"
16+
#include <vector>
1417

1518
using namespace clang::ast_matchers;
19+
namespace optutils = clang::tidy::utils::options;
1620

1721
namespace clang::tidy::readability {
1822

1923
static const StringRef CompareMessage = "do not use 'compare' to test equality "
2024
"of strings; use the string equality "
2125
"operator instead";
2226

27+
static const std::vector<StringRef> StringClasses = {
28+
"::std::basic_string", "::std::basic_string_view"};
29+
30+
StringCompareCheck::StringCompareCheck(StringRef Name,
31+
ClangTidyContext *Context)
32+
: ClangTidyCheck(Name, Context),
33+
StringLikeClasses(
34+
optutils::parseStringList(Options.get("StringLikeClasses", ""))) {}
35+
2336
void StringCompareCheck::registerMatchers(MatchFinder *Finder) {
24-
const auto StrCompare = cxxMemberCallExpr(
25-
callee(cxxMethodDecl(hasName("compare"),
26-
ofClass(classTemplateSpecializationDecl(
27-
hasName("::std::basic_string"))))),
28-
hasArgument(0, expr().bind("str2")), argumentCountIs(1),
29-
callee(memberExpr().bind("str1")));
30-
31-
// First and second case: cast str.compare(str) to boolean.
32-
Finder->addMatcher(
33-
traverse(TK_AsIs,
34-
implicitCastExpr(hasImplicitDestinationType(booleanType()),
35-
has(StrCompare))
36-
.bind("match1")),
37-
this);
38-
39-
// Third and fourth case: str.compare(str) == 0 and str.compare(str) != 0.
40-
Finder->addMatcher(
41-
binaryOperator(hasAnyOperatorName("==", "!="),
42-
hasOperands(StrCompare.bind("compare"),
43-
integerLiteral(equals(0)).bind("zero")))
44-
.bind("match2"),
45-
this);
37+
const auto RegisterForClasses = [&, this](const auto &StringClassMatcher) {
38+
const auto StrCompare = cxxMemberCallExpr(
39+
callee(cxxMethodDecl(hasName("compare"), ofClass(StringClassMatcher))),
40+
hasArgument(0, expr().bind("str2")), argumentCountIs(1),
41+
callee(memberExpr().bind("str1")));
42+
43+
// First and second case: cast str.compare(str) to boolean.
44+
Finder->addMatcher(
45+
traverse(TK_AsIs,
46+
implicitCastExpr(hasImplicitDestinationType(booleanType()),
47+
has(StrCompare))
48+
.bind("match1")),
49+
this);
50+
51+
// Third and fourth case: str.compare(str) == 0
52+
// and str.compare(str) != 0.
53+
Finder->addMatcher(
54+
binaryOperator(hasAnyOperatorName("==", "!="),
55+
hasOperands(StrCompare.bind("compare"),
56+
integerLiteral(equals(0)).bind("zero")))
57+
.bind("match2"),
58+
this);
59+
};
60+
if (StringLikeClasses.empty()) {
61+
RegisterForClasses(
62+
classTemplateSpecializationDecl(hasAnyName(StringClasses)));
63+
} else {
64+
// StringLikeClasses may or may not be templates, so we need to match both
65+
// template and non-template classes.
66+
std::vector<StringRef> PossiblyTemplateClasses = StringClasses;
67+
PossiblyTemplateClasses.insert(PossiblyTemplateClasses.end(),
68+
StringLikeClasses.begin(),
69+
StringLikeClasses.end());
70+
RegisterForClasses(anyOf(
71+
classTemplateSpecializationDecl(hasAnyName(PossiblyTemplateClasses)),
72+
cxxRecordDecl(hasAnyName(StringLikeClasses))));
73+
}
4674
}
4775

4876
void StringCompareCheck::check(const MatchFinder::MatchResult &Result) {

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

Lines changed: 7 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,17 @@ 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+
33+
private:
34+
const std::vector<StringRef> StringLikeClasses;
3035
};
3136

3237
} // namespace clang::tidy::readability

clang-tools-extra/docs/ReleaseNotes.rst

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

289+
- Improved :doc:`readability-string-compare
290+
<clang-tidy/checks/readability/string-compare>` check to also detect
291+
usages of `std::string_view::compare`. Added a `StringLikeClasses` option
292+
to detect usages of `compare` method in custom string-like classes.
293+
289294
Removed checks
290295
^^^^^^^^^^^^^^
291296

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

Lines changed: 31 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,32 @@ 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 comma-separated names of string-like classes. Default is an empty string.
64+
If a class from this list has a ``compare`` method similar to ``std::string``, it will be checked in the same way.
65+
66+
Example
67+
^^^^^^^
68+
69+
.. code-block:: c++
70+
71+
struct CustomString {
72+
public:
73+
int compare (const CustomString& other) const;
74+
}
75+
76+
CustomString str1;
77+
CustomString str2;
78+
79+
// use str1 != str2 instead.
80+
if (str1.compare(str2)) {
81+
}
82+
83+
If `StringLikeClasses` contains ``CustomString``, the check will suggest 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
@@ -106,6 +106,8 @@ struct basic_string_view {
106106
constexpr bool starts_with(C ch) const noexcept;
107107
constexpr bool starts_with(const C* s) const;
108108

109+
constexpr int compare(basic_string_view sv) const noexcept;
110+
109111
static constexpr size_t npos = -1;
110112
};
111113

@@ -129,6 +131,14 @@ bool operator!=(const char*, const std::string&);
129131
bool operator==(const std::wstring&, const std::wstring&);
130132
bool operator==(const std::wstring&, const wchar_t*);
131133
bool operator==(const wchar_t*, const std::wstring&);
134+
135+
bool operator==(const std::string_view&, const std::string_view&);
136+
bool operator==(const std::string_view&, const char*);
137+
bool operator==(const char*, const std::string_view&);
138+
139+
bool operator!=(const std::string_view&, const std::string_view&);
140+
bool operator!=(const std::string_view&, const char*);
141+
bool operator!=(const char*, const std::string_view&);
132142
}
133143

134144
#endif // _STRING_
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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)) { // Must still be detected with non-empty StringLikeClasses
24+
}
25+
// CHECK-MESSAGES: [[@LINE-2]]:7: warning: do not use 'compare' to test equality of strings; use the string equality operator instead [readability-string-compare]
26+
27+
CustomString1 custom1;
28+
if (custom1.compare(custom1)) {
29+
}
30+
// CHECK-MESSAGES: [[@LINE-2]]:7: warning: do not use 'compare' to test equality of strings; use the string equality operator instead [readability-string-compare]
31+
32+
CustomString2 custom2;
33+
if (custom2.compare(custom2)) {
34+
}
35+
// CHECK-MESSAGES: [[@LINE-2]]:7: warning: do not use 'compare' to test equality of strings; use the string equality operator instead [readability-string-compare]
36+
}

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)