Skip to content

[clang-tidy] check std::string_view and custom string-like classes in readability-string-compare #88636

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions clang-tools-extra/clang-tidy/readability/StringCompareCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,43 @@
//===----------------------------------------------------------------------===//

#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 {

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")));

Expand Down
10 changes: 8 additions & 2 deletions clang-tools-extra/clang-tidy/readability/StringCompareCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STRINGCOMPARECHECK_H

#include "../ClangTidyCheck.h"
#include <vector>

namespace clang::tidy::readability {

Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,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
^^^^^^^^^^^^^^

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"};

Expand Down Expand Up @@ -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.
10 changes: 10 additions & 0 deletions clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/string
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
}
}