[clang] [Clang][Coroutines] Insert coroutine param fake uses before llvm.coro.free (PR #194690)

Stephen Tozer via cfe-commits cfe-commits at lists.llvm.org
Tue Apr 28 10:53:22 PDT 2026


https://github.com/SLTozer updated https://github.com/llvm/llvm-project/pull/194690

>From 3148f5b70e4e1e78d1571fde23bce0d6d92589a1 Mon Sep 17 00:00:00 2001
From: Stephen Tozer <stephen.tozer at sony.com>
Date: Tue, 28 Apr 2026 18:25:04 +0100
Subject: [PATCH 1/2] [Clang][Coroutines] Insert coroutine param fake uses
 before llvm.coro.free

Fixes issue: https://github.com/llvm/llvm-project/issues/192351

The combination of coroutines with -fextend-variable-liveness has resulted
in use-after-free, caused by the fact that we insert fake uses of coroutine
parameters at the end of the coroutine. While this is fine for normal
functions, in coroutines these variables are stored in the coroutine frame,
which is freed before the end of the function; this results in us loading
from the deleted frame.

This patch fixes this by emitting fake uses for coroutine parameters before
the call to llvm.coro.free (which immediately precedes the call to
`operator delete`). As fake uses are emitted via an EHScopeStack::Cleanup,
we have to push them to the EH stack after CallCoroEnd is pushed. This means
that we have to determine whether we are emitting a coroutine and skip
emitting fake uses if so in EmitParmDecl, and later emit them in
EmitCoroutineBody instead. The exception to this is the `this` pointer,
which will outlive the coroutine frame.
---
 clang/lib/CodeGen/CGCoroutine.cpp             |  8 +++
 clang/lib/CodeGen/CGDecl.cpp                  | 17 +++++--
 clang/lib/CodeGen/CodeGenFunction.h           |  6 +++
 .../CodeGenCoroutines/coro-param-fake-use.cpp | 49 +++++++++++++++++++
 4 files changed, 75 insertions(+), 5 deletions(-)
 create mode 100644 clang/test/CodeGenCoroutines/coro-param-fake-use.cpp

diff --git a/clang/lib/CodeGen/CGCoroutine.cpp b/clang/lib/CodeGen/CGCoroutine.cpp
index 2972f015aa893..a287363d58ee3 100644
--- a/clang/lib/CodeGen/CGCoroutine.cpp
+++ b/clang/lib/CodeGen/CGCoroutine.cpp
@@ -966,6 +966,14 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
         ParmAlloca->setMetadata(llvm::LLVMContext::MD_coro_outside_frame,
                                 llvm::MDNode::get(CGM.getLLVMContext(), {}));
       }
+      // Push a FakeUse 'cleanup' object onto the EHStack for coroutine
+      // parameters, which will emit a fake.use call with the parameter as
+      // an argument before we free the coroutine frame.
+      if (CGM.getCodeGenOpts().getExtendVariableLiveness() ==
+          CodeGenOptions::ExtendVariableLivenessKind::All) {
+        if (shouldExtendLifetime(getContext(), CurCodeDecl, *Parm, CXXABIThisDecl))
+          EHStack.pushCleanup<FakeUse>(NormalFakeUse, ParmAddr);
+      }
     }
     for (auto *PM : S.getParamMoves()) {
       EmitStmt(PM);
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 419b3c477e7b2..f24555f0a2cac 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -1452,9 +1452,9 @@ static uint64_t maxFakeUseAggregateSize(const ASTContext &C) {
 
 // Helper function to determine whether a variable's or parameter's lifetime
 // should be extended.
-static bool shouldExtendLifetime(const ASTContext &Context,
-                                 const Decl *FuncDecl, const VarDecl &D,
-                                 ImplicitParamDecl *CXXABIThisDecl) {
+bool CodeGenFunction::shouldExtendLifetime(const ASTContext &Context,
+                                           const Decl *FuncDecl, const VarDecl &D,
+                                           ImplicitParamDecl *CXXABIThisDecl) {
   // When we're not inside a valid function it is unlikely that any
   // lifetime extension is useful.
   if (!FuncDecl)
@@ -2855,8 +2855,15 @@ void CodeGenFunction::EmitParmDecl(const VarDecl &D, ParamValue Arg,
       (CGM.getCodeGenOpts().getExtendVariableLiveness() ==
            CodeGenOptions::ExtendVariableLivenessKind::This &&
        &D == CXXABIThisDecl)) {
-    if (shouldExtendLifetime(getContext(), CurCodeDecl, D, CXXABIThisDecl))
-      EHStack.pushCleanup<FakeUse>(NormalFakeUse, DeclPtr);
+    // If the current code context is a coroutine, then we defer pushing the
+    // FakeUse cleanups until after we've pushed the CallCoroEnd cleanup.
+    if (&D == CXXABIThisDecl ||
+        !llvm::isa_and_nonnull<FunctionDecl>(CurCodeDecl) ||
+        cast<FunctionDecl>(CurCodeDecl)->getBody()->getStmtClass() !=
+            Stmt::CoroutineBodyStmtClass) {
+      if (shouldExtendLifetime(getContext(), CurCodeDecl, D, CXXABIThisDecl))
+        EHStack.pushCleanup<FakeUse>(NormalFakeUse, DeclPtr);
+    }
   }
 
   // Emit debug info for param declarations in non-thunk functions.
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 29b87a0616992..064a8a46eeb94 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -3466,6 +3466,12 @@ class CodeGenFunction : public CodeGenTypeCache {
   /// This function can be called with a null (unreachable) insert point.
   void EmitAutoVarDecl(const VarDecl &D);
 
+  /// Helper function to determine whether a variable's or parameter's lifetime
+  /// should be extended.
+  static bool shouldExtendLifetime(const ASTContext &Context,
+                                   const Decl *FuncDecl, const VarDecl &D,
+                                   ImplicitParamDecl *CXXABIThisDecl);
+
   class AutoVarEmission {
     friend class CodeGenFunction;
 
diff --git a/clang/test/CodeGenCoroutines/coro-param-fake-use.cpp b/clang/test/CodeGenCoroutines/coro-param-fake-use.cpp
new file mode 100644
index 0000000000000..838c9bee9a2d6
--- /dev/null
+++ b/clang/test/CodeGenCoroutines/coro-param-fake-use.cpp
@@ -0,0 +1,49 @@
+// RUN: %clang_cc1 -std=c++20 -triple=x86_64-unknown-linux-gnu -emit-llvm -fextend-variable-liveness -o - %s -disable-llvm-passes -fexceptions | FileCheck %s
+
+// See issue #192351
+// Tests that when parameters to a coroutine have fake uses inserted for them by
+// -fextend-variable-liveness, all such parameters (except `this`, which is not
+// stored in the coroutine frame) have their fake uses inserted before the
+// coroutine frame that they are contained in is freed.
+
+#include "Inputs/coroutine.h"
+
+struct task {
+    struct promise_type {
+        task get_return_object() noexcept { return {}; }
+        std::suspend_never initial_suspend() noexcept { return {}; }
+        std::suspend_never final_suspend() noexcept { return {}; }
+        void return_void() noexcept {}
+        void unhandled_exception() noexcept {}
+    };
+};
+
+class C {
+public:
+    C() {}
+
+    // CHECK-LABEL: void @_ZN1C1fEb(ptr noundef{{.*}} %this, i1 noundef{{.*}} %b)
+    task f(bool b) {
+        // CHECK: store ptr %this, ptr %[[THIS_ADDR:.+]]
+        // CHECK: %[[B_EXT:.+]] = zext i1 %b to i8
+        // CHECK: store i8 %[[B_EXT]], ptr %[[B_ADDR:.+]]
+
+        // CHECK: coro.cleanup:
+        // CHECK: %[[B_FAKEUSE:.+]] = load i8, ptr %[[B_ADDR]]
+        // CHECK: call void (...) @llvm.fake.use(i8 %[[B_FAKEUSE]])
+        // CHECK: call ptr @llvm.coro.free(
+
+        // CHECK: coro.ret:
+        // CHECK: call void @llvm.coro.end(
+        // CHECK: %[[THIS_FAKE_USE:.+]] = load ptr, ptr %[[THIS_ADDR]]
+        // CHECK: notail call void (...) @llvm.fake.use(ptr %[[THIS_FAKE_USE]])
+        // CHECK: ret void
+        if (b) {
+            co_await std::suspend_always{};
+        }
+    }
+};
+
+void foo() {
+    C().f(false);
+}

>From 6c16518a73e9a66b9456bd4ecc45755ba0d310ce Mon Sep 17 00:00:00 2001
From: Stephen Tozer <stephen.tozer at sony.com>
Date: Tue, 28 Apr 2026 18:53:07 +0100
Subject: [PATCH 2/2] clang-format

---
 clang/lib/CodeGen/CGCoroutine.cpp | 3 ++-
 clang/lib/CodeGen/CGDecl.cpp      | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/clang/lib/CodeGen/CGCoroutine.cpp b/clang/lib/CodeGen/CGCoroutine.cpp
index a287363d58ee3..b8ce32d57706c 100644
--- a/clang/lib/CodeGen/CGCoroutine.cpp
+++ b/clang/lib/CodeGen/CGCoroutine.cpp
@@ -971,7 +971,8 @@ void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) {
       // an argument before we free the coroutine frame.
       if (CGM.getCodeGenOpts().getExtendVariableLiveness() ==
           CodeGenOptions::ExtendVariableLivenessKind::All) {
-        if (shouldExtendLifetime(getContext(), CurCodeDecl, *Parm, CXXABIThisDecl))
+        if (shouldExtendLifetime(getContext(), CurCodeDecl, *Parm,
+                                 CXXABIThisDecl))
           EHStack.pushCleanup<FakeUse>(NormalFakeUse, ParmAddr);
       }
     }
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index f24555f0a2cac..2498b8a01f33e 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -1453,7 +1453,8 @@ static uint64_t maxFakeUseAggregateSize(const ASTContext &C) {
 // Helper function to determine whether a variable's or parameter's lifetime
 // should be extended.
 bool CodeGenFunction::shouldExtendLifetime(const ASTContext &Context,
-                                           const Decl *FuncDecl, const VarDecl &D,
+                                           const Decl *FuncDecl,
+                                           const VarDecl &D,
                                            ImplicitParamDecl *CXXABIThisDecl) {
   // When we're not inside a valid function it is unlikely that any
   // lifetime extension is useful.



More information about the cfe-commits mailing list