[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)
This commit is contained in:
Vadim D
2024-05-09 11:25:26 +03:00
committed by GitHub
parent 443377a9d1
commit febd89cafe
7 changed files with 139 additions and 8 deletions

View File

@@ -7,12 +7,15 @@
//===----------------------------------------------------------------------===//
#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"
using namespace clang::ast_matchers;
namespace optutils = clang::tidy::utils::options;
namespace clang::tidy::readability {
@@ -20,11 +23,27 @@ static const StringRef CompareMessage = "do not use 'compare' to test equality "
"of strings; use the string equality "
"operator instead";
static const StringRef DefaultStringLikeClasses = "::std::basic_string;"
"::std::basic_string_view";
StringCompareCheck::StringCompareCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
StringLikeClasses(optutils::parseStringList(
Options.get("StringLikeClasses", DefaultStringLikeClasses))) {}
void StringCompareCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "StringLikeClasses",
optutils::serializeStringList(StringLikeClasses));
}
void StringCompareCheck::registerMatchers(MatchFinder *Finder) {
if (StringLikeClasses.empty()) {
return;
}
const auto StrCompare = cxxMemberCallExpr(
callee(cxxMethodDecl(hasName("compare"),
ofClass(classTemplateSpecializationDecl(
hasName("::std::basic_string"))))),
callee(cxxMethodDecl(hasName("compare"), ofClass(cxxRecordDecl(hasAnyName(
StringLikeClasses))))),
hasArgument(0, expr().bind("str2")), argumentCountIs(1),
callee(memberExpr().bind("str1")));

View File

@@ -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,18 @@ 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;
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
private:
const std::vector<StringRef> StringLikeClasses;
};
} // namespace clang::tidy::readability

View File

@@ -362,6 +362,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
^^^^^^^^^^^^^^

View File

@@ -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,36 @@ 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 semicolon-separated names of string-like classes.
By default contains only ``::std::basic_string``
and ``::std::basic_string_view``. If a class from this list has
a ``compare`` method similar to that of ``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.

View File

@@ -108,6 +108,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;
};
@@ -132,6 +134,14 @@ 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&);
size_t strlen(const char* str);
}

View File

@@ -0,0 +1,35 @@
// 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 CustomStringClasses() {
std::string_view sv1("a");
std::string_view sv2("b");
if (sv1.compare(sv2)) { // No warning - if a std class is not listed in StringLikeClasses, it won't be checked.
}
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]
}

View File

@@ -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) {
}
}