Create rule S6365: Use symmetric transfer to switch execution between coroutines
This commit is contained in:
parent
a5bc5b1473
commit
b006f130e4
19
rules/S6365/cfamily/metadata.json
Normal file
19
rules/S6365/cfamily/metadata.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"title": "Use symmetric transfer to switch execution between coroutines",
|
||||
"type": "CODE_SMELL",
|
||||
"status": "ready",
|
||||
"remediation": {
|
||||
"func": "Constant\/Issue",
|
||||
"constantCost": "5min"
|
||||
},
|
||||
"tags": [
|
||||
"pitfall",
|
||||
"since-c++20"
|
||||
],
|
||||
"defaultSeverity": "Major",
|
||||
"ruleSpecification": "RSPEC-6365",
|
||||
"sqKey": "S6365",
|
||||
"scope": "All",
|
||||
"defaultQualityProfiles": ["Sonar way"],
|
||||
"quickfix": "unknown"
|
||||
}
|
100
rules/S6365/cfamily/rule.adoc
Normal file
100
rules/S6365/cfamily/rule.adoc
Normal file
@ -0,0 +1,100 @@
|
||||
With {cpp}20 coroutines, the `co_await`/`co_yield` expression suspends the currently executed coroutine and resumes the execution of either the caller or the coroutine function
|
||||
or to some already suspended coroutine (including the current coroutine).
|
||||
|
||||
The resumption of the coroutine represented by the `std::coroutine_handle` object is usually performed by calling the `.resume()` on it.
|
||||
However, performing such an operation during the execution of `await_suspend` (that is part of `co_await` expression evaluation) will preserve the activation frame of the `await_suspend` function and the calling code on the stack.
|
||||
This may lead to stack overflows in a situation where the chain of directly resumed coroutines is deep enough.
|
||||
|
||||
The use of the symmetric transfer may avoid this problem. When the `await_suspend` function returns a `std::coroutine_handle`, the compiler will automatically use this handle to resume its coroutine after `await_suspend` returns (and its activation frame is removed from the stack).
|
||||
Or, when a `std::noop_coroutine_handle` is returned, the execution will be passed to the caller.
|
||||
|
||||
Symmetric transfer solution can also be used to resume the current coroutine (by returning handle passed as the parameter).
|
||||
However, in such cases, conditional suspension can be a more optimal solution.
|
||||
|
||||
This rule raises an issue on `await_suspend` functions that could use symmetric transfer.
|
||||
|
||||
== Noncompliant Code Example
|
||||
|
||||
----
|
||||
struct InvokeOtherAwaiter {
|
||||
/* .... */
|
||||
void await_suspend(std::coroutine_handle<PromiseType> current) {
|
||||
if (auto other = current.promise().other_handle) {
|
||||
other.resume(); // Noncompliant
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct WaitForAwaiter {
|
||||
Event& event;
|
||||
/* .... */
|
||||
void await_suspend(std::coroutine_handle<> current) {
|
||||
if (bool ready = event.register_callback(current)) {
|
||||
current.resume(); // Noncompliant
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct BufferedExecutionAwaiter {
|
||||
std::queue<std::coroutine_handle<>>& taskQueue;
|
||||
/* .... */
|
||||
void await_suspend(std::coroutine_handle<> current) {
|
||||
if (taskQueue.empty()) {
|
||||
current.resume(); // Noncompliant
|
||||
}
|
||||
auto next = taskQueue.front();
|
||||
taskQueue.pop();
|
||||
taskQueue.push(current);
|
||||
next.resume(); // Noncompliant
|
||||
}
|
||||
};
|
||||
----
|
||||
|
||||
== Compliant Solution
|
||||
|
||||
----
|
||||
struct InvokeOtherAwaiter {
|
||||
/* .... */
|
||||
std::coroutine_handle<> await_suspend(std::coroutine_handle<PromiseType> current) {
|
||||
if (auto other = current.promise().other_handle) {
|
||||
return other;
|
||||
} else {
|
||||
return std::noop_coroutine();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct WaitForAwaiter {
|
||||
Event& event;
|
||||
/* .... */
|
||||
std::coroutine_handle<> await_suspend(std::coroutine_handle<> current) {
|
||||
if (bool ready = event.register_callback(current)) {
|
||||
return current;
|
||||
} else {
|
||||
return std::noop_coroutine()
|
||||
}
|
||||
}
|
||||
// Alternatively
|
||||
bool await_suspend(std::coroutine_handle<> current) {
|
||||
return !event.register_callback(current);
|
||||
}
|
||||
};
|
||||
|
||||
struct BufferedExecutionAwaiter {
|
||||
std::queue<std::coroutine_handle<>>& taskQueue;
|
||||
/* .... */
|
||||
std::coroutine_handle<> await_suspend(std::coroutine_handle<> current) {
|
||||
if (taskQueue.empty()) {
|
||||
return current;
|
||||
}
|
||||
auto next = list.front();
|
||||
taskQueue.pop();
|
||||
taskQueue.push(current);
|
||||
return next;
|
||||
}
|
||||
};
|
||||
----
|
||||
|
||||
== See
|
||||
|
||||
S6366 - conditionally suspending current coroutine in optimal way
|
2
rules/S6365/metadata.json
Normal file
2
rules/S6365/metadata.json
Normal file
@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user