[Lldb-commits] [lldb] [lldb] Support MSVC STL/PDB in coroutine_handle formatter (PR #194941)

via lldb-commits lldb-commits at lists.llvm.org
Wed Aug 19 01:16:06 PDT 2026


https://github.com/Nerixyz updated https://github.com/llvm/llvm-project/pull/194941

>From e66e0bdaf11130c382c7f77834ad4a7f07bf29f2 Mon Sep 17 00:00:00 2001
From: Nerixyz <nerixdev at outlook.de>
Date: Wed, 29 Apr 2026 21:37:53 +0200
Subject: [PATCH] [lldb] Support MSVC STL in coroutine_handle formatter

---
 .../Plugins/Language/CPlusPlus/Coroutines.cpp | 47 +++++++++++++++----
 .../generic/coroutine_handle/Makefile         | 10 +---
 .../coroutine_handle/TestCoroutineHandle.py   | 12 ++++-
 .../generic/coroutine_handle/main.cpp         | 12 +----
 4 files changed, 50 insertions(+), 31 deletions(-)

diff --git a/lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp b/lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp
index 4cca7aa46abf9..9f5aa7a209039 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp
@@ -63,7 +63,8 @@ static Function *ExtractDestroyFunction(lldb::TargetSP target_sp,
 // clang generates aritifical `__promise` and `__coro_frame` variables inside
 // the destroy function. Look for those variables and extract their type.
 static CompilerType InferArtificialCoroType(Function *destroy_func,
-                                            ConstString var_name) {
+                                            ConstString var_name,
+                                            bool is_msvc) {
   if (!destroy_func)
     return {};
 
@@ -73,7 +74,7 @@ static CompilerType InferArtificialCoroType(Function *destroy_func,
   auto var = variable_list->FindVariable(var_name);
   if (!var)
     return {};
-  if (!var->IsArtificial())
+  if (!var->IsArtificial() && !is_msvc)
     return {};
 
   Type *promise_type = var->GetType();
@@ -82,6 +83,31 @@ static CompilerType InferArtificialCoroType(Function *destroy_func,
   return promise_type->GetForwardCompilerType();
 }
 
+static CompilerType GetPromiseType(CompilerType handle_type, bool is_msvc) {
+  if (!handle_type)
+    return {};
+
+  CompilerType template_arg = handle_type.GetTypeTemplateArgument(0);
+  if (template_arg || !is_msvc)
+    return template_arg;
+
+  // No template argument, this might be PDB. std::coroutine_handle<Promise>
+  // doesn't have a typedef inside - try to find `Promise& promise()` and use
+  // the return type.
+  size_t n_methods = handle_type.GetNumMemberFunctions();
+  for (size_t i = 0; i < n_methods; ++i) {
+    TypeMemberFunctionImpl fn = handle_type.GetMemberFunctionAtIndex(i);
+    if (fn.GetName() == "promise") {
+      CompilerType return_ty = fn.GetReturnType().GetPointeeType();
+      if (return_ty)
+        return return_ty;
+      break; // Return void here to infer the type from __promise.
+    }
+  }
+
+  return handle_type.GetBasicTypeFromAST(lldb::eBasicTypeVoid);
+}
+
 bool lldb_private::formatters::StdlibCoroutineHandleSummaryProvider(
     ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
   lldb::addr_t frame_ptr_addr =
@@ -138,26 +164,27 @@ lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::Update() {
   if (!ast_ctx)
     return lldb::ChildCacheState::eRefetch;
 
+  bool is_msvc =
+      target_sp->GetArchitecture().GetTriple().isWindowsMSVCEnvironment();
+
   // Determine the coroutine frame type and the promise type. Fall back
   // to `void`, since even the pointer itself might be useful, even if the
   // type inference failed.
   Function *destroy_func = ExtractDestroyFunction(target_sp, frame_ptr_addr);
   CompilerType void_type = ast_ctx->GetBasicType(lldb::eBasicTypeVoid);
-  CompilerType promise_type;
-  if (CompilerType template_arg =
-          valobj_sp->GetCompilerType().GetTypeTemplateArgument(0))
-    promise_type = std::move(template_arg);
+  CompilerType promise_type =
+      GetPromiseType(valobj_sp->GetCompilerType(), is_msvc);
   if (promise_type.IsVoidType()) {
     // Try to infer the promise_type if it was type-erased
     if (destroy_func) {
-      if (CompilerType inferred_type =
-              InferArtificialCoroType(destroy_func, ConstString("__promise"))) {
+      if (CompilerType inferred_type = InferArtificialCoroType(
+              destroy_func, ConstString("__promise"), is_msvc)) {
         promise_type = inferred_type;
       }
     }
   }
-  CompilerType coro_frame_type =
-      InferArtificialCoroType(destroy_func, ConstString("__coro_frame"));
+  CompilerType coro_frame_type = InferArtificialCoroType(
+      destroy_func, ConstString("__coro_frame"), is_msvc);
   if (!coro_frame_type)
     coro_frame_type = void_type;
 
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/Makefile b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/Makefile
index 3e5da0ecae669..4f79c0a900c3a 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/Makefile
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/Makefile
@@ -1,12 +1,4 @@
 CXX_SOURCES := main.cpp
-CFLAGS_EXTRAS := -std=c++20
-
-ifeq "1" "$(USE_LIBSTDCPP)"
-  CFLAGS_EXTRAS += -DUSE_LIBSTDCPP
-endif
-
-ifeq "1" "$(USE_LIBCPP)"
-  CFLAGS_EXTRAS += -DUSE_LIBCPP
-endif
+CXXFLAGS_EXTRAS := -std=c++20
 
 include Makefile.rules
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py
index 21a5c6ec792d9..f24be2550393a 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py
@@ -10,6 +10,7 @@
 
 class TestCoroutineHandle(TestBase):
     SHARED_BUILD_TESTCASE = False
+    TEST_WITH_PDB_DEBUG_INFO = True
 
     def do_test(self):
         """Test std::coroutine_handle is displayed correctly."""
@@ -18,8 +19,11 @@ def do_test(self):
         # Clang <= 20 used to also name the resume/destroy functions
         # as `my_generator_func`.
         # Never versions of clang name the clones as `.resume`/`.destroy`.
+        # On Windows with DWARF, we get:
+        # `struct int_generator my_generator_func(void) at main.cpp:{line}`
+        # and with PDB, we get `my_generator_func at main.cpp:{line}`.
         test_generator_func_ptr_re = re.compile(
-            r"^\(a.out`my_generator_func\(\)( \(\..*\))? at main.cpp:[0-9]*\)$"
+            r"^\(a.out`(struct int_generator )?my_generator_func(\((void)?\))?( \(\..*\))? at main.cpp:[0-9]*\)$"
         )
 
         # Run until the initial suspension point
@@ -172,3 +176,9 @@ def test_libstdcpp(self):
     def test_libcpp(self):
         self.build(dictionary={"USE_LIBCPP": 1})
         self.do_test()
+
+    @add_test_categories(["msvcstl"])
+    def test_msvcstl(self):
+        # No flags, because the "msvcstl" category checks that the MSVC STL is used by default.
+        self.build()
+        self.do_test()
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/main.cpp
index aaf76e0d8d99a..774dfe965d58e 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/main.cpp
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/main.cpp
@@ -1,14 +1,4 @@
-#if defined(USE_LIBSTDCPP)
-#include <bits/c++config.h>
-// glibc++ >= 11 and c++20
-#if defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE >= 11
-#include <coroutine>
-#define HAS_CPP_COROUTINES 1
-#endif
-#endif
-
-// libc++ always has 'coroutine' feature.
-#if defined(USE_LIBCPP)
+#if __has_include(<coroutine>)
 #include <coroutine>
 #define HAS_CPP_COROUTINES 1
 #endif



More information about the lldb-commits mailing list