This revision was automatically updated to reflect the committed changes.
Closed by commit rG91389000abe8: [LLDB] Add data formatter for 
std::coroutine_handle (authored by avogelsgesang).

Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D132415/new/

https://reviews.llvm.org/D132415

Files:
  clang/docs/tools/clang-formatted-files.txt
  lldb/packages/Python/lldbsuite/test/lldbtest.py
  lldb/packages/Python/lldbsuite/test/lldbutil.py
  lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
  lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
  lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp
  lldb/source/Plugins/Language/CPlusPlus/Coroutines.h
  
lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/Makefile
  
lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py
  
lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/main.cpp

Index: lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/main.cpp
===================================================================
--- /dev/null
+++ lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/main.cpp
@@ -0,0 +1,40 @@
+#include <coroutine>
+
+// `int_generator` is a stripped down, minimal coroutine generator
+// type.
+struct int_generator {
+  struct promise_type {
+    int current_value = -1;
+
+    auto get_return_object() {
+      return std::coroutine_handle<promise_type>::from_promise(*this);
+    }
+    auto initial_suspend() { return std::suspend_always(); }
+    auto final_suspend() noexcept { return std::suspend_always(); }
+    auto return_void() { return std::suspend_always(); }
+    void unhandled_exception() { __builtin_unreachable(); }
+    auto yield_value(int v) {
+      current_value = v;
+      return std::suspend_always();
+    }
+  };
+
+  std::coroutine_handle<promise_type> hdl;
+
+  int_generator(std::coroutine_handle<promise_type> h) : hdl(h) {}
+  ~int_generator() { hdl.destroy(); }
+};
+
+int_generator my_generator_func() { co_yield 42; }
+
+// This is an empty function which we call just so the debugger has
+// a place to reliably set a breakpoint on.
+void empty_function_so_we_can_set_a_breakpoint() {}
+
+int main() {
+  int_generator gen = my_generator_func();
+  std::coroutine_handle<> type_erased_hdl = gen.hdl;
+  gen.hdl.resume();                            // Break at initial_suspend
+  gen.hdl.resume();                            // Break after co_yield
+  empty_function_so_we_can_set_a_breakpoint(); // Break at final_suspend
+}
Index: lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py
===================================================================
--- /dev/null
+++ lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py
@@ -0,0 +1,79 @@
+"""
+Test lldb data formatter subsystem.
+"""
+
+
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+USE_LIBSTDCPP = "USE_LIBSTDCPP"
+USE_LIBCPP = "USE_LIBCPP"
+
+class TestCoroutineHandle(TestBase):
+    def do_test(self, stdlib_type):
+        """Test std::coroutine_handle is displayed correctly."""
+        self.build(dictionary={stdlib_type: "1"})
+
+        test_generator_func_ptr_re = re.compile(
+                r"^\(a.out`my_generator_func\(\) at main.cpp:[0-9]*\)$")
+
+        # Run until the initial suspension point
+        lldbutil.run_to_source_breakpoint(self, '// Break at initial_suspend',
+                lldb.SBFileSpec("main.cpp", False))
+        # Check that we show the correct function pointers and the `promise`. 
+        self.expect_expr("gen.hdl",
+            result_summary=re.compile("^coro frame = 0x[0-9a-f]*$"),
+            result_children=[
+                ValueCheck(name="resume", summary = test_generator_func_ptr_re),
+                ValueCheck(name="destroy", summary = test_generator_func_ptr_re),
+                ValueCheck(name="promise", children=[
+                    ValueCheck(name="current_value", value = "-1"),
+                ])
+            ])
+        # For type-erased `coroutine_handle<>` we are missing the `promise`
+        # but still show `resume` and `destroy`.
+        self.expect_expr("type_erased_hdl",
+            result_summary=re.compile("^coro frame = 0x[0-9a-f]*$"),
+            result_children=[
+                ValueCheck(name="resume", summary = test_generator_func_ptr_re),
+                ValueCheck(name="destroy", summary = test_generator_func_ptr_re),
+            ])
+
+        # Run until after the `co_yield`
+        process = self.process()
+        lldbutil.continue_to_source_breakpoint(self, process,
+                '// Break after co_yield', lldb.SBFileSpec("main.cpp", False))
+        # We correctly show the updated value inside `prommise.current_value`.
+        self.expect_expr("gen.hdl",
+            result_children=[
+                ValueCheck(name="resume", summary = test_generator_func_ptr_re),
+                ValueCheck(name="destroy", summary = test_generator_func_ptr_re),
+                ValueCheck(name="promise", children=[
+                    ValueCheck(name="current_value", value = "42"),
+                ])
+            ])
+        
+        # Run until the `final_suspend`
+        lldbutil.continue_to_source_breakpoint(self, process,
+                '// Break at final_suspend', lldb.SBFileSpec("main.cpp", False))
+        # At the final suspension point, `resume` is set to a nullptr.
+        # Check that we still show the remaining data correctly.
+        self.expect_expr("gen.hdl",
+            result_children=[
+                ValueCheck(name="resume", value = "0x0000000000000000"),
+                ValueCheck(name="destroy", summary = test_generator_func_ptr_re),
+                ValueCheck(name="promise", children=[
+                    ValueCheck(name="current_value", value = "42"),
+                ])
+            ])
+
+    @add_test_categories(["libstdcxx"])
+    def test_libstdcpp(self):
+        self.do_test(USE_LIBSTDCPP)
+
+    @add_test_categories(["libc++"])
+    def test_libcpp(self):
+        self.do_test(USE_LIBCPP)
Index: lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/Makefile
===================================================================
--- /dev/null
+++ lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/Makefile
@@ -0,0 +1,4 @@
+CXX_SOURCES := main.cpp
+CFLAGS_EXTRAS := -std=c++20
+
+include Makefile.rules
Index: lldb/source/Plugins/Language/CPlusPlus/Coroutines.h
===================================================================
--- /dev/null
+++ lldb/source/Plugins/Language/CPlusPlus/Coroutines.h
@@ -0,0 +1,57 @@
+//===-- Coroutines.h --------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_PLUGINS_LANGUAGE_CPLUSPLUS_COROUTINES_H
+#define LLDB_SOURCE_PLUGINS_LANGUAGE_CPLUSPLUS_COROUTINES_H
+
+#include "lldb/Core/ValueObject.h"
+#include "lldb/DataFormatters/TypeSummary.h"
+#include "lldb/DataFormatters/TypeSynthetic.h"
+#include "lldb/Utility/Stream.h"
+
+namespace lldb_private {
+namespace formatters {
+
+/// Summary provider for `std::coroutine_handle<T>` from  libc++, libstdc++ and
+/// MSVC STL.
+bool StdlibCoroutineHandleSummaryProvider(ValueObject &valobj, Stream &stream,
+                                          const TypeSummaryOptions &options);
+
+/// Synthetic children frontend for `std::coroutine_handle<promise_type>` from
+/// libc++, libstdc++ and MSVC STL. Shows the compiler-generated `resume` and
+/// `destroy` function pointers as well as the `promise`, if the promise type
+/// is `promise_type != void`.
+class StdlibCoroutineHandleSyntheticFrontEnd
+    : public SyntheticChildrenFrontEnd {
+public:
+  StdlibCoroutineHandleSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
+
+  ~StdlibCoroutineHandleSyntheticFrontEnd() override;
+
+  size_t CalculateNumChildren() override;
+
+  lldb::ValueObjectSP GetChildAtIndex(size_t idx) override;
+
+  bool Update() override;
+
+  bool MightHaveChildren() override;
+
+  size_t GetIndexOfChildWithName(ConstString name) override;
+
+private:
+  lldb::ValueObjectSP m_frame_ptr_sp;
+};
+
+SyntheticChildrenFrontEnd *
+StdlibCoroutineHandleSyntheticFrontEndCreator(CXXSyntheticChildren *,
+                                              lldb::ValueObjectSP);
+
+} // namespace formatters
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_LANGUAGE_CPLUSPLUS_COROUTINES_H
Index: lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp
===================================================================
--- /dev/null
+++ lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp
@@ -0,0 +1,137 @@
+//===-- Coroutines.cpp ----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Coroutines.h"
+
+#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::formatters;
+
+static ValueObjectSP GetCoroFramePtrFromHandle(ValueObject &valobj) {
+  ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue());
+  if (!valobj_sp)
+    return nullptr;
+
+  // We expect a single pointer in the `coroutine_handle` class.
+  // We don't care about its name.
+  if (valobj_sp->GetNumChildren() != 1)
+    return nullptr;
+  ValueObjectSP ptr_sp(valobj_sp->GetChildAtIndex(0, true));
+  if (!ptr_sp)
+    return nullptr;
+  if (!ptr_sp->GetCompilerType().IsPointerType())
+    return nullptr;
+
+  return ptr_sp;
+}
+
+static CompilerType GetCoroutineFrameType(TypeSystemClang &ast_ctx,
+                                          CompilerType promise_type) {
+  CompilerType void_type = ast_ctx.GetBasicType(lldb::eBasicTypeVoid);
+  CompilerType coro_func_type = ast_ctx.CreateFunctionType(
+      /*result_type=*/void_type, /*args=*/&void_type, /*num_args=*/1,
+      /*is_variadic=*/false, /*qualifiers=*/0);
+  CompilerType coro_abi_type;
+  if (promise_type.IsVoidType()) {
+    coro_abi_type = ast_ctx.CreateStructForIdentifier(
+        ConstString(), {{"resume", coro_func_type.GetPointerType()},
+                        {"destroy", coro_func_type.GetPointerType()}});
+  } else {
+    coro_abi_type = ast_ctx.CreateStructForIdentifier(
+        ConstString(), {{"resume", coro_func_type.GetPointerType()},
+                        {"destroy", coro_func_type.GetPointerType()},
+                        {"promise", promise_type}});
+  }
+  return coro_abi_type;
+}
+
+bool lldb_private::formatters::StdlibCoroutineHandleSummaryProvider(
+    ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
+  ValueObjectSP ptr_sp(GetCoroFramePtrFromHandle(valobj));
+  if (!ptr_sp)
+    return false;
+
+  stream.Printf("coro frame = 0x%" PRIx64, ptr_sp->GetValueAsUnsigned(0));
+  return true;
+}
+
+lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
+    StdlibCoroutineHandleSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
+    : SyntheticChildrenFrontEnd(*valobj_sp) {
+  if (valobj_sp)
+    Update();
+}
+
+lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
+    ~StdlibCoroutineHandleSyntheticFrontEnd() = default;
+
+size_t lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
+    CalculateNumChildren() {
+  if (!m_frame_ptr_sp)
+    return 0;
+
+  return m_frame_ptr_sp->GetNumChildren();
+}
+
+lldb::ValueObjectSP lldb_private::formatters::
+    StdlibCoroutineHandleSyntheticFrontEnd::GetChildAtIndex(size_t idx) {
+  if (!m_frame_ptr_sp)
+    return lldb::ValueObjectSP();
+
+  return m_frame_ptr_sp->GetChildAtIndex(idx, true);
+}
+
+bool lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
+    Update() {
+  m_frame_ptr_sp.reset();
+
+  ValueObjectSP valobj_sp = m_backend.GetSP();
+  if (!valobj_sp)
+    return false;
+
+  ValueObjectSP ptr_sp(GetCoroFramePtrFromHandle(m_backend));
+  if (!ptr_sp)
+    return false;
+
+  TypeSystemClang *ast_ctx = llvm::dyn_cast_or_null<TypeSystemClang>(
+      valobj_sp->GetCompilerType().GetTypeSystem());
+  if (!ast_ctx)
+    return false;
+
+  CompilerType promise_type(
+      valobj_sp->GetCompilerType().GetTypeTemplateArgument(0));
+  if (!promise_type)
+    return false;
+  CompilerType coro_frame_type = GetCoroutineFrameType(*ast_ctx, promise_type);
+
+  m_frame_ptr_sp = ptr_sp->Cast(coro_frame_type.GetPointerType());
+
+  return false;
+}
+
+bool lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
+    MightHaveChildren() {
+  return true;
+}
+
+size_t StdlibCoroutineHandleSyntheticFrontEnd::GetIndexOfChildWithName(
+    ConstString name) {
+  if (!m_frame_ptr_sp)
+    return UINT32_MAX;
+
+  return m_frame_ptr_sp->GetIndexOfChildWithName(name);
+}
+
+SyntheticChildrenFrontEnd *
+lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEndCreator(
+    CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
+  return (valobj_sp ? new StdlibCoroutineHandleSyntheticFrontEnd(valobj_sp)
+                    : nullptr);
+}
Index: lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
===================================================================
--- lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -35,6 +35,7 @@
 
 #include "BlockPointer.h"
 #include "CPlusPlusNameParser.h"
+#include "Coroutines.h"
 #include "CxxStringTypes.h"
 #include "Generic.h"
 #include "LibCxx.h"
@@ -796,6 +797,14 @@
                 ConstString("^std::__[[:alnum:]]+::function<.+>$"),
                 stl_summary_flags, true);
 
+  ConstString libcxx_std_coroutine_handle_regex(
+      "^std::__[[:alnum:]]+::coroutine_handle<.+>(( )?&)?$");
+  AddCXXSynthetic(
+      cpp_category_sp,
+      lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEndCreator,
+      "coroutine_handle synthetic children", libcxx_std_coroutine_handle_regex,
+      stl_deref_flags, true);
+
   stl_summary_flags.SetDontShowChildren(false);
   stl_summary_flags.SetSkipPointers(false);
   AddCXXSummary(cpp_category_sp,
@@ -898,6 +907,11 @@
                 "libc++ std::unique_ptr summary provider",
                 libcxx_std_unique_ptr_regex, stl_summary_flags, true);
 
+  AddCXXSummary(cpp_category_sp,
+                lldb_private::formatters::StdlibCoroutineHandleSummaryProvider,
+                "libc++ std::coroutine_handle summary provider",
+                libcxx_std_coroutine_handle_regex, stl_summary_flags, true);
+
   AddCXXSynthetic(
       cpp_category_sp,
       lldb_private::formatters::LibCxxVectorIteratorSyntheticFrontEndCreator,
@@ -1122,6 +1136,14 @@
       "std::tuple synthetic children", ConstString("^std::tuple<.+>(( )?&)?$"),
       stl_synth_flags, true);
 
+  ConstString libstdcpp_std_coroutine_handle_regex(
+      "^std::coroutine_handle<.+>(( )?&)?$");
+  AddCXXSynthetic(
+      cpp_category_sp,
+      lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEndCreator,
+      "std::coroutine_handle synthetic children",
+      libstdcpp_std_coroutine_handle_regex, stl_deref_flags, true);
+
   AddCXXSynthetic(
       cpp_category_sp,
       lldb_private::formatters::LibStdcppBitsetSyntheticFrontEndCreator,
@@ -1149,6 +1171,10 @@
                 "libstdc++ std::weak_ptr summary provider",
                 ConstString("^std::weak_ptr<.+>(( )?&)?$"), stl_summary_flags,
                 true);
+  AddCXXSummary(cpp_category_sp,
+                lldb_private::formatters::StdlibCoroutineHandleSummaryProvider,
+                "libstdc++ std::coroutine_handle summary provider",
+                libstdcpp_std_coroutine_handle_regex, stl_summary_flags, true);
   AddCXXSummary(
       cpp_category_sp, lldb_private::formatters::GenericOptionalSummaryProvider,
       "libstd++ std::optional summary provider",
Index: lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
===================================================================
--- lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
+++ lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
@@ -1,5 +1,6 @@
 add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN
   BlockPointer.cpp
+  Coroutines.cpp
   CPlusPlusLanguage.cpp
   CPlusPlusNameParser.cpp
   CxxStringTypes.cpp
Index: lldb/packages/Python/lldbsuite/test/lldbutil.py
===================================================================
--- lldb/packages/Python/lldbsuite/test/lldbutil.py
+++ lldb/packages/Python/lldbsuite/test/lldbutil.py
@@ -988,6 +988,21 @@
         return get_threads_stopped_at_breakpoint(process, bkpt)
 
 
+def continue_to_source_breakpoint(test, process, bkpt_pattern, source_spec):
+    """
+    Sets a breakpoint set by source regex bkpt_pattern, continues the process, and deletes the breakpoint again.
+    Otherwise the same as `continue_to_breakpoint`
+    """
+    breakpoint = process.target.BreakpointCreateBySourceRegex(
+            bkpt_pattern, source_spec, None)
+    test.assertTrue(breakpoint.GetNumLocations() > 0,
+                    'No locations found for source breakpoint: "%s", file: "%s", dir: "%s"'
+                    %(bkpt_pattern, source_spec.GetFilename(), source_spec.GetDirectory()))
+    stopped_threads = continue_to_breakpoint(process, breakpoint)
+    process.target.BreakpointDelete(breakpoint.GetID())
+    return stopped_threads
+
+
 def get_caller_symbol(thread):
     """
     Returns the symbol name for the call site of the leaf function.
Index: lldb/packages/Python/lldbsuite/test/lldbtest.py
===================================================================
--- lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -292,8 +292,12 @@
             test_base.assertEqual(self.expect_type, val.GetDisplayTypeName(),
                                   this_error_msg)
         if self.expect_summary:
-            test_base.assertEqual(self.expect_summary, val.GetSummary(),
-                                  this_error_msg)
+            if isinstance(self.expect_summary, re.Pattern):
+                test_base.assertRegex(val.GetSummary(), self.expect_summary,
+                                      this_error_msg)
+            else:
+                test_base.assertEqual(self.expect_summary, val.GetSummary(),
+                                      this_error_msg)
         if self.children is not None:
             self.check_value_children(test_base, val, error_msg)
 
Index: clang/docs/tools/clang-formatted-files.txt
===================================================================
--- clang/docs/tools/clang-formatted-files.txt
+++ clang/docs/tools/clang-formatted-files.txt
@@ -4180,6 +4180,8 @@
 lldb/source/Plugins/Language/ClangCommon/ClangHighlighter.h
 lldb/source/Plugins/Language/CPlusPlus/BlockPointer.cpp
 lldb/source/Plugins/Language/CPlusPlus/BlockPointer.h
+lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp
+lldb/source/Plugins/Language/CPlusPlus/Coroutines.h
 lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
 lldb/source/Plugins/Language/CPlusPlus/CPlusPlusNameParser.h
 lldb/source/Plugins/Language/CPlusPlus/CxxStringTypes.h
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to