[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
This commit is contained in:
parent
0f8c72160e
commit
26d082d330
@ -50,7 +50,8 @@ UnnecessaryValueParamCheck::UnnecessaryValueParamCheck(
|
||||
utils::IncludeSorter::IS_LLVM),
|
||||
areDiagsSelfContained()),
|
||||
AllowedTypes(
|
||||
utils::options::parseStringList(Options.get("AllowedTypes", ""))) {}
|
||||
utils::options::parseStringList(Options.get("AllowedTypes", ""))),
|
||||
IgnoreCoroutines(Options.get("IgnoreCoroutines", true)) {}
|
||||
|
||||
void UnnecessaryValueParamCheck::registerMatchers(MatchFinder *Finder) {
|
||||
const auto ExpensiveValueParamDecl = parmVarDecl(
|
||||
@ -61,12 +62,14 @@ void UnnecessaryValueParamCheck::registerMatchers(MatchFinder *Finder) {
|
||||
matchers::matchesAnyListedName(AllowedTypes))))))),
|
||||
decl().bind("param"));
|
||||
Finder->addMatcher(
|
||||
traverse(
|
||||
TK_AsIs,
|
||||
functionDecl(hasBody(stmt()), isDefinition(), unless(isImplicit()),
|
||||
unless(cxxMethodDecl(anyOf(isOverride(), isFinal()))),
|
||||
has(typeLoc(forEach(ExpensiveValueParamDecl))),
|
||||
decl().bind("functionDecl"))),
|
||||
traverse(TK_AsIs,
|
||||
functionDecl(
|
||||
hasBody(IgnoreCoroutines ? stmt(unless(coroutineBodyStmt()))
|
||||
: stmt()),
|
||||
isDefinition(), unless(isImplicit()),
|
||||
unless(cxxMethodDecl(anyOf(isOverride(), isFinal()))),
|
||||
has(typeLoc(forEach(ExpensiveValueParamDecl))),
|
||||
decl().bind("functionDecl"))),
|
||||
this);
|
||||
}
|
||||
|
||||
@ -123,6 +126,7 @@ void UnnecessaryValueParamCheck::storeOptions(
|
||||
Options.store(Opts, "IncludeStyle", Inserter.getStyle());
|
||||
Options.store(Opts, "AllowedTypes",
|
||||
utils::options::serializeStringList(AllowedTypes));
|
||||
Options.store(Opts, "IgnoreCoroutines", IgnoreCoroutines);
|
||||
}
|
||||
|
||||
void UnnecessaryValueParamCheck::onEndOfTranslationUnit() {
|
||||
|
@ -46,6 +46,7 @@ private:
|
||||
ExprMutationAnalyzer::Memoized MutationAnalyzerCache;
|
||||
utils::IncludeInserter Inserter;
|
||||
const std::vector<StringRef> AllowedTypes;
|
||||
bool IgnoreCoroutines;
|
||||
};
|
||||
|
||||
} // namespace clang::tidy::performance
|
||||
|
@ -265,6 +265,8 @@ Changes in existing checks
|
||||
<clang-tidy/checks/performance/unnecessary-value-param>` check performance by
|
||||
tolerating fix-it breaking compilation when functions is used as pointers
|
||||
to avoid matching usage of functions within the current compilation unit.
|
||||
Added an option `IgnoreCoroutines` with the default value `true` to
|
||||
suppress this check for coroutines where passing by reference may be unsafe.
|
||||
|
||||
- Improved :doc:`readability-convert-member-functions-to-static
|
||||
<clang-tidy/checks/readability/convert-member-functions-to-static>` check by
|
||||
|
@ -56,7 +56,7 @@ Will become:
|
||||
|
||||
Because the fix-it needs to change the signature of the function, it may break
|
||||
builds if the function is used in multiple translation units or some codes
|
||||
depends on funcion signatures.
|
||||
depends on function signatures.
|
||||
|
||||
Options
|
||||
-------
|
||||
@ -74,3 +74,10 @@ Options
|
||||
default is empty. If a name in the list contains the sequence `::`, it is
|
||||
matched against the qualified type name (i.e. ``namespace::Type``),
|
||||
otherwise it is matched against only the type name (i.e. ``Type``).
|
||||
|
||||
.. option:: IgnoreCoroutines
|
||||
|
||||
A boolean specifying whether the check should suggest passing parameters by
|
||||
reference in coroutines. Passing parameters by reference in coroutines may
|
||||
not be safe, please see :doc:`cppcoreguidelines-avoid-reference-coroutine-parameters <../cppcoreguidelines/avoid-reference-coroutine-parameters>`
|
||||
for more information. Default is `true`.
|
||||
|
@ -0,0 +1,65 @@
|
||||
// RUN: %check_clang_tidy -std=c++20-or-later %s performance-unnecessary-value-param %t -- -fix-errors
|
||||
// RUN: %check_clang_tidy -std=c++20-or-later %s performance-unnecessary-value-param %t -- \
|
||||
// RUN: -config='{CheckOptions: {performance-unnecessary-value-param.IgnoreCoroutines: true}}' -fix-errors
|
||||
// RUN: %check_clang_tidy -check-suffix=ALLOWED -std=c++20-or-later %s performance-unnecessary-value-param %t -- \
|
||||
// RUN: -config='{CheckOptions: {performance-unnecessary-value-param.IgnoreCoroutines: false}}' -fix-errors
|
||||
|
||||
namespace std {
|
||||
|
||||
template <class Ret, typename... T> struct coroutine_traits {
|
||||
using promise_type = typename Ret::promise_type;
|
||||
};
|
||||
|
||||
template <class Promise = void> struct coroutine_handle {
|
||||
static coroutine_handle from_address(void *) noexcept;
|
||||
static coroutine_handle from_promise(Promise &promise);
|
||||
constexpr void *address() const noexcept;
|
||||
};
|
||||
|
||||
template <> struct coroutine_handle<void> {
|
||||
template <class PromiseType>
|
||||
coroutine_handle(coroutine_handle<PromiseType>) noexcept;
|
||||
static coroutine_handle from_address(void *);
|
||||
constexpr void *address() const noexcept;
|
||||
};
|
||||
|
||||
struct suspend_always {
|
||||
bool await_ready() noexcept { return false; }
|
||||
void await_suspend(coroutine_handle<>) noexcept {}
|
||||
void await_resume() noexcept {}
|
||||
};
|
||||
|
||||
struct suspend_never {
|
||||
bool await_ready() noexcept { return true; }
|
||||
void await_suspend(coroutine_handle<>) noexcept {}
|
||||
void await_resume() noexcept {}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
struct ReturnObject {
|
||||
struct promise_type {
|
||||
ReturnObject get_return_object() { return {}; }
|
||||
ReturnObject return_void() { return {}; }
|
||||
std::suspend_always initial_suspend() { return {}; }
|
||||
std::suspend_always final_suspend() noexcept { return {}; }
|
||||
void unhandled_exception() {}
|
||||
std::suspend_always yield_value(int value) { return {}; }
|
||||
};
|
||||
};
|
||||
|
||||
struct A {
|
||||
A(const A&);
|
||||
};
|
||||
|
||||
ReturnObject foo_coroutine(const A a) {
|
||||
// CHECK-MESSAGES-ALLOWED: [[@LINE-1]]:36: warning: the const qualified parameter 'a'
|
||||
// CHECK-FIXES: ReturnObject foo_coroutine(const A a) {
|
||||
co_return;
|
||||
}
|
||||
|
||||
ReturnObject foo_not_coroutine(const A a) {
|
||||
// CHECK-MESSAGES: [[@LINE-1]]:40: warning: the const qualified parameter 'a'
|
||||
// CHECK-MESSAGES-ALLOWED: [[@LINE-2]]:40: warning: the const qualified parameter 'a'
|
||||
return ReturnObject{};
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user