[clang] [RFC] Perform lifetime bound checks for arguments to coroutine (PR #69360)
Utkarsh Saxena via cfe-commits
cfe-commits at lists.llvm.org
Mon Oct 23 23:36:26 PDT 2023
https://github.com/usx95 updated https://github.com/llvm/llvm-project/pull/69360
>From be3f5faa6cd17d76f26fb1bc6d6b59a8a78ffe82 Mon Sep 17 00:00:00 2001
From: Utkarsh Saxena <usx at google.com>
Date: Tue, 17 Oct 2023 19:37:28 +0200
Subject: [PATCH] Lifetime bound check for coroutine
---
clang/lib/Sema/SemaInit.cpp | 16 +-
.../SemaCXX/coroutine-lifetimebound-args.cpp | 184 ++++++++++++++++++
2 files changed, 198 insertions(+), 2 deletions(-)
create mode 100644 clang/test/SemaCXX/coroutine-lifetimebound-args.cpp
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index fd95b16b84b6e3a..54e72b220bc47a4 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -7607,11 +7607,23 @@ static void visitLifetimeBoundArguments(IndirectLocalPath &Path, Expr *Call,
if (ObjectArg && implicitObjectParamIsLifetimeBound(Callee))
VisitLifetimeBoundArg(Callee, ObjectArg);
-
+ bool checkCoroCall = false;
+ if (const auto *RD = Callee->getReturnType()->getAsRecordDecl()) {
+ for (const auto &attr :
+ RD->getUnderlyingDecl()->specific_attrs<clang::AnnotateAttr>()) {
+ // Only for demonstration: Get feedback and add a clang annotation as an
+ // extension.
+ if (attr->getAnnotation() == "coro_type") {
+
+ checkCoroCall = true;
+ break;
+ }
+ }
+ }
for (unsigned I = 0,
N = std::min<unsigned>(Callee->getNumParams(), Args.size());
I != N; ++I) {
- if (Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
+ if (checkCoroCall || Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
VisitLifetimeBoundArg(Callee->getParamDecl(I), Args[I]);
}
}
diff --git a/clang/test/SemaCXX/coroutine-lifetimebound-args.cpp b/clang/test/SemaCXX/coroutine-lifetimebound-args.cpp
new file mode 100644
index 000000000000000..4cc5730ef896581
--- /dev/null
+++ b/clang/test/SemaCXX/coroutine-lifetimebound-args.cpp
@@ -0,0 +1,184 @@
+// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++20 -fsyntax-only -verify -Wall -Wextra -Wno-error=unreachable-code -Wno-unused
+
+#include "Inputs/std-coroutine.h"
+
+using std::suspend_always;
+using std::suspend_never;
+
+
+#define CORO_TYPE [[clang::annotate("coro_type")]]
+#define CORO_UNSAFE [[clang::annotate("coro_unsafe")]]
+
+template <typename T> struct CORO_TYPE Gen {
+ struct promise_type {
+ Gen<T> get_return_object() {
+ return {};
+ }
+ suspend_always initial_suspend();
+ suspend_always final_suspend() noexcept;
+ void unhandled_exception();
+ void return_value(const T &t);
+
+ template <typename U>
+ auto await_transform(const Gen<U> &) {
+ struct awaitable {
+ bool await_ready() noexcept { return false; }
+ void await_suspend(std::coroutine_handle<>) noexcept {}
+ U await_resume() noexcept { return {}; }
+ };
+ return awaitable{};
+ }
+ };
+};
+
+template <typename T> using Co = Gen<T>;
+
+Gen<int> foo_coro(const int& b);
+
+Gen<int> plain_return_foo_decl(int b) {
+ return foo_coro(b); // expected-warning {{address of stack memory associated with parameter}}
+}
+
+Gen<int> foo_coro(const int& b) {
+ if (b > 0)
+ co_return 1;
+ co_return 2;
+}
+
+int getInt() { return 0; }
+
+Co<int> bar_coro(const int &b, int c) {
+ int x = co_await foo_coro(b);
+ int y = co_await foo_coro(1);
+ int z = co_await foo_coro(getInt());
+ auto unsafe1 = foo_coro(1); // expected-warning {{temporary whose address is used as value of local variable}}
+ auto unsafe2 = foo_coro(getInt()); // expected-warning {{temporary whose address is used as value of local variable}}
+ auto safe1 = foo_coro(b);
+ auto safe2 = foo_coro(c);
+ co_return co_await foo_coro(co_await foo_coro(1));
+}
+
+Gen<int> plain_return_co(int b) {
+ return foo_coro(b); // expected-warning {{address of stack memory associated with parameter}}
+}
+
+Gen<int> safe_forwarding(const int& b) {
+ return foo_coro(b);
+}
+
+Gen<int> unsafe_wrapper(int b) {
+ return safe_forwarding(b); // expected-warning {{address of stack memory associated with parameter}}
+}
+
+Co<int> complex_plain_return(int b) {
+ return b > 0
+ ? foo_coro(1) // expected-warning {{returning address of local temporary object}}
+ : bar_coro(0, 1); // expected-warning {{returning address of local temporary object}}
+}
+
+void lambdas() {
+ auto unsafe_lambda = [](int b) {
+ return foo_coro(b); // expected-warning {{address of stack memory associated with parameter}}
+ };
+ auto safe_lambda = [](int b) -> Co<int> {
+ int x = co_await foo_coro(1);
+ co_return x + co_await foo_coro(b);
+ };
+}
+// =============================================================================
+// Safe usage when parameters are value
+// =============================================================================
+namespace by_value {
+Gen<int> value_coro(int b) { co_return co_await foo_coro(b); }
+
+Gen<int> wrapper1(int b) { return value_coro(b); }
+Gen<int> wrapper2(const int& b) { return value_coro(b); }
+}
+
+// =============================================================================
+// std::function like wrappers. (Eg: https://godbolt.org/z/x3PfG3Gfb)
+// =============================================================================
+namespace std {
+
+template <class T>
+T&& forward(typename remove_reference<T>::type& t) noexcept;
+template <class T>
+T&& forward(typename remove_reference<T>::type&& t) noexcept;
+
+template <bool, typename>
+class function;
+
+template <bool UseFp, typename ReturnValue, typename... Args>
+class function<UseFp, ReturnValue(Args...)> {
+ public:
+ class Callable {
+ public:
+ ReturnValue operator()(Args&&...) const { return {}; }
+ };
+ Callable* callable_;
+ ReturnValue operator()(Args... args) const
+ requires (!UseFp)
+ {
+ return (*callable_)(std::forward<Args>(args)...); // expected-warning 3 {{address of stack memory}}
+ }
+
+ // Callable can also be a function pointer type.
+ using FpCallableType = ReturnValue(Args&&...);
+ FpCallableType* fp_callable_;
+ ReturnValue operator()(Args... args) const
+ requires(UseFp)
+ {
+ return fp_callable_(std::forward<Args>(args)...);
+ }
+
+ template <typename T>
+ function& operator=(T) {}
+ template <typename T>
+ function(T) {}
+ function() {}
+};
+} // namespace std
+
+namespace without_function_pointers {
+template <typename T>
+using fn = std::function<false, T>;
+
+void use_std_function() {
+ fn<Co<int>(const int&, const int&)> pass;
+ pass(1, 1);
+ // Lifetime issue with one parameter.
+ fn<Co<int>(const int&, int)> fail;
+ fail(1, 1); // expected-note {{in instantiation of}}
+ // Lifetime issue with both parameters.
+ fn<Co<int>(int, int)> fail_twice;
+ fail_twice(1, 1); // expected-note {{in instantiation of}}
+}
+} // namespace without_function_pointers
+
+// =============================================================================
+// Future work: Function pointers needs to be fixed.
+// =============================================================================
+namespace with_function_pointers {
+template <typename T>
+using fn = std::function<true, T>;
+
+void use_std_function() {
+ fn<Co<int>(int, int)> fail;
+ fail(1, 1); // FIXME: Must error.
+}
+} // namespace function_pointers
+
+// =============================================================================
+// Future work: Reference wrappers needs to be fixed.
+// =============================================================================
+namespace with_reference_wrappers {
+struct RefWrapper {
+ RefWrapper(const int &a): b(a) {}
+ const int &b;
+};
+Co<int> RefWrapperCoro(RefWrapper a) { co_return a.b; }
+
+Co<int> UnsafeWrapper(int a) {
+ return RefWrapperCoro(a); // FIXME: Must error.
+}
+}
\ No newline at end of file
More information about the cfe-commits
mailing list