[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