[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:
@@ -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")));
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user