Skip to content

Commit 26d082d

Browse files
authored
[clang-tidy][performance-unnecessary-value-param] Avoid in coroutines (#140912)
Summary: Replacing by-value parameters with passing by-reference is not safe for coroutines because the caller may be executed in parallel with the callee, which increases the chances of resulting in dangling references and hard-to-find crashes. See for the reference [cppcoreguidelines-avoid-reference-coroutine-parameters](https://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines/avoid-reference-coroutine-parameters.html). Test Plan: check-clang-tools
1 parent 0f8c721 commit 26d082d

File tree

5 files changed

+87
-8
lines changed

5 files changed

+87
-8
lines changed

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ UnnecessaryValueParamCheck::UnnecessaryValueParamCheck(
5050
utils::IncludeSorter::IS_LLVM),
5151
areDiagsSelfContained()),
5252
AllowedTypes(
53-
utils::options::parseStringList(Options.get("AllowedTypes", ""))) {}
53+
utils::options::parseStringList(Options.get("AllowedTypes", ""))),
54+
IgnoreCoroutines(Options.get("IgnoreCoroutines", true)) {}
5455

5556
void UnnecessaryValueParamCheck::registerMatchers(MatchFinder *Finder) {
5657
const auto ExpensiveValueParamDecl = parmVarDecl(
@@ -61,12 +62,14 @@ void UnnecessaryValueParamCheck::registerMatchers(MatchFinder *Finder) {
6162
matchers::matchesAnyListedName(AllowedTypes))))))),
6263
decl().bind("param"));
6364
Finder->addMatcher(
64-
traverse(
65-
TK_AsIs,
66-
functionDecl(hasBody(stmt()), isDefinition(), unless(isImplicit()),
67-
unless(cxxMethodDecl(anyOf(isOverride(), isFinal()))),
68-
has(typeLoc(forEach(ExpensiveValueParamDecl))),
69-
decl().bind("functionDecl"))),
65+
traverse(TK_AsIs,
66+
functionDecl(
67+
hasBody(IgnoreCoroutines ? stmt(unless(coroutineBodyStmt()))
68+
: stmt()),
69+
isDefinition(), unless(isImplicit()),
70+
unless(cxxMethodDecl(anyOf(isOverride(), isFinal()))),
71+
has(typeLoc(forEach(ExpensiveValueParamDecl))),
72+
decl().bind("functionDecl"))),
7073
this);
7174
}
7275

@@ -123,6 +126,7 @@ void UnnecessaryValueParamCheck::storeOptions(
123126
Options.store(Opts, "IncludeStyle", Inserter.getStyle());
124127
Options.store(Opts, "AllowedTypes",
125128
utils::options::serializeStringList(AllowedTypes));
129+
Options.store(Opts, "IgnoreCoroutines", IgnoreCoroutines);
126130
}
127131

128132
void UnnecessaryValueParamCheck::onEndOfTranslationUnit() {

clang-tools-extra/clang-tidy/performance/UnnecessaryValueParamCheck.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class UnnecessaryValueParamCheck : public ClangTidyCheck {
4646
ExprMutationAnalyzer::Memoized MutationAnalyzerCache;
4747
utils::IncludeInserter Inserter;
4848
const std::vector<StringRef> AllowedTypes;
49+
bool IgnoreCoroutines;
4950
};
5051

5152
} // namespace clang::tidy::performance

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,8 @@ Changes in existing checks
265265
<clang-tidy/checks/performance/unnecessary-value-param>` check performance by
266266
tolerating fix-it breaking compilation when functions is used as pointers
267267
to avoid matching usage of functions within the current compilation unit.
268+
Added an option `IgnoreCoroutines` with the default value `true` to
269+
suppress this check for coroutines where passing by reference may be unsafe.
268270

269271
- Improved :doc:`readability-convert-member-functions-to-static
270272
<clang-tidy/checks/readability/convert-member-functions-to-static>` check by

clang-tools-extra/docs/clang-tidy/checks/performance/unnecessary-value-param.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Will become:
5656

5757
Because the fix-it needs to change the signature of the function, it may break
5858
builds if the function is used in multiple translation units or some codes
59-
depends on funcion signatures.
59+
depends on function signatures.
6060

6161
Options
6262
-------
@@ -74,3 +74,10 @@ Options
7474
default is empty. If a name in the list contains the sequence `::`, it is
7575
matched against the qualified type name (i.e. ``namespace::Type``),
7676
otherwise it is matched against only the type name (i.e. ``Type``).
77+
78+
.. option:: IgnoreCoroutines
79+
80+
A boolean specifying whether the check should suggest passing parameters by
81+
reference in coroutines. Passing parameters by reference in coroutines may
82+
not be safe, please see :doc:`cppcoreguidelines-avoid-reference-coroutine-parameters <../cppcoreguidelines/avoid-reference-coroutine-parameters>`
83+
for more information. Default is `true`.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// RUN: %check_clang_tidy -std=c++20-or-later %s performance-unnecessary-value-param %t -- -fix-errors
2+
// RUN: %check_clang_tidy -std=c++20-or-later %s performance-unnecessary-value-param %t -- \
3+
// RUN: -config='{CheckOptions: {performance-unnecessary-value-param.IgnoreCoroutines: true}}' -fix-errors
4+
// RUN: %check_clang_tidy -check-suffix=ALLOWED -std=c++20-or-later %s performance-unnecessary-value-param %t -- \
5+
// RUN: -config='{CheckOptions: {performance-unnecessary-value-param.IgnoreCoroutines: false}}' -fix-errors
6+
7+
namespace std {
8+
9+
template <class Ret, typename... T> struct coroutine_traits {
10+
using promise_type = typename Ret::promise_type;
11+
};
12+
13+
template <class Promise = void> struct coroutine_handle {
14+
static coroutine_handle from_address(void *) noexcept;
15+
static coroutine_handle from_promise(Promise &promise);
16+
constexpr void *address() const noexcept;
17+
};
18+
19+
template <> struct coroutine_handle<void> {
20+
template <class PromiseType>
21+
coroutine_handle(coroutine_handle<PromiseType>) noexcept;
22+
static coroutine_handle from_address(void *);
23+
constexpr void *address() const noexcept;
24+
};
25+
26+
struct suspend_always {
27+
bool await_ready() noexcept { return false; }
28+
void await_suspend(coroutine_handle<>) noexcept {}
29+
void await_resume() noexcept {}
30+
};
31+
32+
struct suspend_never {
33+
bool await_ready() noexcept { return true; }
34+
void await_suspend(coroutine_handle<>) noexcept {}
35+
void await_resume() noexcept {}
36+
};
37+
38+
} // namespace std
39+
40+
struct ReturnObject {
41+
struct promise_type {
42+
ReturnObject get_return_object() { return {}; }
43+
ReturnObject return_void() { return {}; }
44+
std::suspend_always initial_suspend() { return {}; }
45+
std::suspend_always final_suspend() noexcept { return {}; }
46+
void unhandled_exception() {}
47+
std::suspend_always yield_value(int value) { return {}; }
48+
};
49+
};
50+
51+
struct A {
52+
A(const A&);
53+
};
54+
55+
ReturnObject foo_coroutine(const A a) {
56+
// CHECK-MESSAGES-ALLOWED: [[@LINE-1]]:36: warning: the const qualified parameter 'a'
57+
// CHECK-FIXES: ReturnObject foo_coroutine(const A a) {
58+
co_return;
59+
}
60+
61+
ReturnObject foo_not_coroutine(const A a) {
62+
// CHECK-MESSAGES: [[@LINE-1]]:40: warning: the const qualified parameter 'a'
63+
// CHECK-MESSAGES-ALLOWED: [[@LINE-2]]:40: warning: the const qualified parameter 'a'
64+
return ReturnObject{};
65+
}

0 commit comments

Comments
 (0)