Skip to content

Commit fc2bccf

Browse files
committed
[clang-tidy] Add a new check 'replace-with-string-view'
Looks for functions returning `std::[w|u8|u16|u32]string` and suggests to change it to `std::[...]string_view` if possible and profitable. Example: ```cpp std::string foo(int i) { // <---- can be replaced to `std::string_view foo(...) {` switch(i) { case 1: return "case1"; case 2: return "case2"; default: return {}; } } ```
1 parent dd33690 commit fc2bccf

File tree

8 files changed

+354
-0
lines changed

8 files changed

+354
-0
lines changed

clang-tools-extra/clang-tidy/performance/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ add_clang_library(clangTidyPerformanceModule STATIC
2121
NoexceptMoveConstructorCheck.cpp
2222
NoexceptSwapCheck.cpp
2323
PerformanceTidyModule.cpp
24+
ReplaceWithStringViewCheck.cpp
2425
TriviallyDestructibleCheck.cpp
2526
TypePromotionInMathFnCheck.cpp
2627
UnnecessaryCopyInitializationCheck.cpp

clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "NoexceptDestructorCheck.h"
2525
#include "NoexceptMoveConstructorCheck.h"
2626
#include "NoexceptSwapCheck.h"
27+
#include "ReplaceWithStringViewCheck.h"
2728
#include "TriviallyDestructibleCheck.h"
2829
#include "TypePromotionInMathFnCheck.h"
2930
#include "UnnecessaryCopyInitializationCheck.h"
@@ -62,6 +63,8 @@ class PerformanceModule : public ClangTidyModule {
6263
"performance-noexcept-move-constructor");
6364
CheckFactories.registerCheck<NoexceptSwapCheck>(
6465
"performance-noexcept-swap");
66+
CheckFactories.registerCheck<ReplaceWithStringViewCheck>(
67+
"performance-replace-with-string-view");
6568
CheckFactories.registerCheck<TriviallyDestructibleCheck>(
6669
"performance-trivially-destructible");
6770
CheckFactories.registerCheck<TypePromotionInMathFnCheck>(
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "ReplaceWithStringViewCheck.h"
10+
#include "clang/ASTMatchers/ASTMatchFinder.h"
11+
12+
using namespace clang::ast_matchers;
13+
14+
namespace clang::tidy::performance {
15+
16+
static llvm::StringLiteral toStringViewTypeStr(StringRef Type) {
17+
if (Type.contains("wchar_t"))
18+
return "std::wstring_view";
19+
if (Type.contains("wchar_t"))
20+
return "std::wstring_view";
21+
if (Type.contains("char8_t"))
22+
return "std::u8string_view";
23+
if (Type.contains("char16_t"))
24+
return "std::u16string_view";
25+
if (Type.contains("char32_t"))
26+
return "std::u32string_view";
27+
return "std::string_view";
28+
}
29+
30+
void ReplaceWithStringViewCheck::registerMatchers(MatchFinder *Finder) {
31+
const auto IsStdString = hasCanonicalType(
32+
hasDeclaration(cxxRecordDecl(hasName("::std::basic_string"))));
33+
const auto IsStdStringView = expr(hasType(hasCanonicalType(
34+
hasDeclaration(cxxRecordDecl(hasName("::std::basic_string_view"))))));
35+
36+
Finder->addMatcher(
37+
functionDecl(
38+
isDefinition(), returns(IsStdString), hasDescendant(returnStmt()),
39+
unless(hasDescendant(
40+
returnStmt(hasReturnValue(unless(ignoringImplicit(anyOf(
41+
stringLiteral(), IsStdStringView,
42+
cxxConstructExpr(
43+
hasType(IsStdString),
44+
anyOf(argumentCountIs(0),
45+
hasArgument(0, ignoringParenImpCasts(anyOf(
46+
stringLiteral(),
47+
IsStdStringView)))))))))))))
48+
.bind("func"),
49+
this);
50+
}
51+
52+
void ReplaceWithStringViewCheck::check(const MatchFinder::MatchResult &Result) {
53+
if (const auto *MatchedDecl = Result.Nodes.getNodeAs<FunctionDecl>("func")) {
54+
const llvm::StringLiteral DestReturnTypeStr =
55+
toStringViewTypeStr(MatchedDecl->getReturnType()
56+
.getDesugaredType(*Result.Context)
57+
.getAsString());
58+
59+
auto Diag = diag(MatchedDecl->getReturnTypeSourceRange().getBegin(),
60+
"consider using `%0' to avoid unnecessary "
61+
"copying and allocations")
62+
<< DestReturnTypeStr << MatchedDecl;
63+
64+
for (const auto *FuncDecl = MatchedDecl; FuncDecl != nullptr;
65+
FuncDecl = FuncDecl->getPreviousDecl()) {
66+
Diag << FixItHint::CreateReplacement(FuncDecl->getReturnTypeSourceRange(),
67+
DestReturnTypeStr);
68+
}
69+
}
70+
}
71+
72+
} // namespace clang::tidy::performance
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_REPLACEWITHSTRINGVIEWCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_REPLACEWITHSTRINGVIEWCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::performance {
15+
16+
/// Looks for functions returning `std::[w|u8|u16|u32]string` and suggests to
17+
/// change it to `std::[...]string_view` if possible and profitable.
18+
///
19+
/// Example:
20+
///
21+
/// std::string foo(int i) { // <---- can be replaced to std::string_view f(...)
22+
/// switch(i) {
23+
/// case 1:
24+
/// return "case1";
25+
/// case 2:
26+
/// return "case2";
27+
/// default:
28+
/// return {};
29+
/// }
30+
/// }
31+
///
32+
/// For the user-facing documentation see:
33+
/// https://clang.llvm.org/extra/clang-tidy/checks/performance/replace-with-string-view.html
34+
class ReplaceWithStringViewCheck : public ClangTidyCheck {
35+
public:
36+
ReplaceWithStringViewCheck(StringRef Name, ClangTidyContext *Context)
37+
: ClangTidyCheck(Name, Context) {}
38+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
39+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
40+
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
41+
return LangOpts.CPlusPlus17;
42+
}
43+
};
44+
45+
} // namespace clang::tidy::performance
46+
47+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_REPLACEWITHSTRINGVIEWCHECK_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,12 @@ New checks
249249
Finds virtual function overrides with different visibility than the function
250250
in the base class.
251251

252+
- New :doc:`performance-replace-with-string-view
253+
<clang-tidy/checks/performance/replace-with-string-view>` check.
254+
255+
Looks for functions returning ``std::[w|u8|u16|u32]string`` and suggests to
256+
change it to ``std::[...]string_view`` for performance reasons if possible.
257+
252258
- New :doc:`readability-redundant-parentheses
253259
<clang-tidy/checks/readability/redundant-parentheses>` check.
254260

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ Clang-Tidy Checks
362362
:doc:`performance-noexcept-destructor <performance/noexcept-destructor>`, "Yes"
363363
:doc:`performance-noexcept-move-constructor <performance/noexcept-move-constructor>`, "Yes"
364364
:doc:`performance-noexcept-swap <performance/noexcept-swap>`, "Yes"
365+
:doc:`performance-replace-with-string-view <performance/replace-with-string-view>`, "Yes"
365366
:doc:`performance-trivially-destructible <performance/trivially-destructible>`, "Yes"
366367
:doc:`performance-type-promotion-in-math-fn <performance/type-promotion-in-math-fn>`, "Yes"
367368
:doc:`performance-unnecessary-copy-initialization <performance/unnecessary-copy-initialization>`, "Yes"
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
.. title:: clang-tidy - performance-replace-with-string-view
2+
3+
performance-replace-with-string-view
4+
====================================
5+
6+
Looks for functions returning ``std::[w|u8|u16|u32]string`` and suggests to
7+
change it to ``std::[...]string_view`` for performance reasons if possible.
8+
9+
Rationale:
10+
11+
Each time a new ``std::string`` is created from a literal, a copy of that
12+
literal is allocated either in ``std::string``'s internal buffer
13+
(for short literals) or in a heap.
14+
15+
For the cases where ``std::string`` is returned from a function,
16+
such allocations can be eliminated sometimes by using ``std::string_view``
17+
as a return type.
18+
19+
This check looks for such functions returning ``std::string``
20+
baked from the literals and suggests replacing the return type to ``std::string_view``.
21+
22+
It handles ``std::string``, ``std::wstring``, ``std::u8string``,
23+
``std::u16string`` and ``std::u32string`` along with their aliases and selects
24+
the proper kind of ``std::string_view`` to return.
25+
26+
Example:
27+
28+
Consider the following code:
29+
30+
.. code-block:: c++
31+
32+
std::string foo(int i) {
33+
switch(i)
34+
{
35+
case 1:
36+
return "case 1";
37+
case 2:
38+
return "case 2";
39+
case 3:
40+
return "case 3";
41+
default:
42+
return "default";
43+
}
44+
}
45+
46+
In the code above a new ``std::string`` object is created on each
47+
function invocation, making a copy of a string literal and possibly
48+
allocating a memory in a heap.
49+
50+
The check gets this code transformed into:
51+
52+
.. code-block:: c++
53+
54+
std::string_view foo(int i) {
55+
switch(i)
56+
{
57+
case 1:
58+
return "case 1";
59+
case 2:
60+
return "case 2";
61+
case 3:
62+
return "case 3";
63+
default:
64+
return "default";
65+
}
66+
}
67+
68+
New version re-uses statically allocated literals without additional overhead.
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
// RUN: %check_clang_tidy -std=c++17-or-later %s performance-replace-with-string-view %t -- -- -I %S/Inputs
2+
3+
namespace std {
4+
template <typename CharT>
5+
class basic_string_view {
6+
public:
7+
basic_string_view(const CharT *);
8+
basic_string_view();
9+
};
10+
using string_view = basic_string_view<char>;
11+
using wstring_view = basic_string_view<wchar_t>;
12+
using u16string_view = basic_string_view<char16_t>;
13+
14+
template <typename CharT>
15+
class basic_string {
16+
public:
17+
basic_string();
18+
basic_string(const CharT *);
19+
basic_string(basic_string_view<CharT>);
20+
21+
basic_string operator+(const basic_string &) const;
22+
};
23+
using string = basic_string<char>;
24+
using wstring = basic_string<wchar_t>;
25+
using u16string = basic_string<char16_t>;
26+
}
27+
28+
// ==========================================================
29+
// Positive tests
30+
// ==========================================================
31+
32+
std::string simpleLiteral() {
33+
// CHECK-MESSAGES:[[@LINE-1]]:1: {{.*}}[performance-replace-with-string-view]{{.*}}
34+
// CHECK-FIXES: std::string_view{{.*}}
35+
return "simpleLiteral";
36+
}
37+
38+
std::string implicitView(std::string_view sv) {
39+
// CHECK-MESSAGES:[[@LINE-1]]:1: {{.*}}[performance-replace-with-string-view]{{.*}}
40+
// CHECK-FIXES: std::string_view{{.*}}
41+
return sv;
42+
}
43+
44+
std::string emptyReturn() {
45+
// CHECK-MESSAGES:[[@LINE-1]]:1: {{.*}}[performance-replace-with-string-view]{{.*}}
46+
// CHECK-FIXES: std::string_view{{.*}}
47+
return {};
48+
}
49+
50+
std::string switchCaseTest(int i) {
51+
// CHECK-MESSAGES:[[@LINE-1]]:1: {{.*}}[performance-replace-with-string-view]{{.*}}
52+
// CHECK-FIXES: std::string_view{{.*}}
53+
switch (i) {
54+
case 1:
55+
return "case1";
56+
case 2:
57+
return "case2";
58+
case 3:
59+
return {};
60+
default:
61+
return "default";
62+
}
63+
}
64+
65+
std::string ifElseTest(bool flag) {
66+
// CHECK-MESSAGES:[[@LINE-1]]:1: {{.*}}[performance-replace-with-string-view]{{.*}}
67+
// CHECK-FIXES: std::string_view{{.*}}
68+
if (flag)
69+
return "true";
70+
return "false";
71+
}
72+
73+
std::wstring wideStringTest() {
74+
// CHECK-MESSAGES:[[@LINE-1]]:1: {{.*}}[performance-replace-with-string-view]{{.*}}
75+
// CHECK-FIXES: std::wstring_view{{.*}}
76+
return L"wide literal";
77+
}
78+
79+
std::u16string u16StringTest() {
80+
// CHECK-MESSAGES:[[@LINE-1]]:1: {{.*}}[performance-replace-with-string-view]{{.*}}
81+
// CHECK-FIXES: std::u16string_view{{.*}}
82+
return u"u16 literal";
83+
}
84+
85+
class A {
86+
std::string classMethodInt() { return "internal"; }
87+
// CHECK-MESSAGES:[[@LINE-1]]:3: {{.*}}[performance-replace-with-string-view]{{.*}}
88+
// CHECK-FIXES: std::string_view{{.*}}
89+
90+
std::string classMethodExt();
91+
// CHECK-MESSAGES:[[@LINE+4]]:1: {{.*}}[performance-replace-with-string-view]{{.*}}
92+
// CHECK-FIXES: std::string_view{{.*}}
93+
};
94+
95+
std::string A::classMethodExt() { return "external"; }
96+
// CHECK-FIXES: std::string_view{{.*}}
97+
98+
// ==========================================================
99+
// Negative tests
100+
// ==========================================================
101+
102+
std::string localVariable() {
103+
std::string s = "local variable";
104+
// TODO: extract and return literal
105+
return s;
106+
}
107+
108+
std::string dynamicCalculation() {
109+
std::string s1 = "hello ";
110+
return s1 + "world";
111+
}
112+
113+
std::string mixedReturns(bool flag) {
114+
if (flag) {
115+
return "safe static literal";
116+
}
117+
std::string s = "unsafe dynamic";
118+
return s;
119+
}
120+
121+
std::string_view alreadyGood() {
122+
return "alreadyGood";
123+
}
124+
125+
std::string returnArgCopy(std::string s) {
126+
// Must not be converted to string_view because of use-after-free on stack
127+
return s;
128+
}
129+
130+
std::string returnIndirection(const char* ptr) {
131+
// Can be unsafe or intentional, like converting string_view into string
132+
return ptr;
133+
}
134+
135+
std::string localBuffer() {
136+
char buf[] = "local buffer";
137+
// Must not be converted to string_view because of use-after-free on stack
138+
return buf;
139+
}
140+
141+
std::string returnConstVar() {
142+
// TODO: seems safe
143+
constexpr auto kVar = "const string";
144+
return kVar;
145+
}
146+
147+
std::string passStringView(std::string_view sv) {
148+
// Can be unsafe or intentional, like converting string_view into string
149+
return std::string(sv);
150+
}
151+
152+
std::string explicitConstruction() {
153+
// Cannot be std::string_view: returning address of local temporary object
154+
// TODO: extract and return literal
155+
return std::string("explicitConstruction");
156+
}

0 commit comments

Comments
 (0)