https://github.com/Nerixyz created 
https://github.com/llvm/llvm-project/pull/173053

`std::span` didn't have a formatter for MSVC's STL yet. The type is quite 
useful in C++ 20, so this PR adds a formatter for it.

Since the formatter is new, I made it work with both DWARF and PDB from the 
start.

>From fc0b9580fa0f7d8a0730a707b436bac91119209e Mon Sep 17 00:00:00 2001
From: Nerixyz <[email protected]>
Date: Fri, 19 Dec 2025 18:23:08 +0100
Subject: [PATCH] [LLDB] Add MSVC STL span formatter

---
 .../Plugins/Language/CPlusPlus/CMakeLists.txt |   1 +
 .../Language/CPlusPlus/CPlusPlusLanguage.cpp  |  26 ++--
 .../Plugins/Language/CPlusPlus/MsvcStl.h      |   6 +
 .../Language/CPlusPlus/MsvcStlSpan.cpp        | 133 ++++++++++++++++++
 .../generic/span/TestDataFormatterStdSpan.py  |  29 +++-
 5 files changed, 181 insertions(+), 14 deletions(-)
 create mode 100644 lldb/source/Plugins/Language/CPlusPlus/MsvcStlSpan.cpp

diff --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt 
b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
index c52d3bdb31284..79c0cc14ec644 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
+++ b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
@@ -38,6 +38,7 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN
   MsvcStlAtomic.cpp
   MsvcStlDeque.cpp
   MsvcStlSmartPointer.cpp
+  MsvcStlSpan.cpp
   MsvcStlTree.cpp
   MsvcStlTuple.cpp
   MsvcStlUnordered.cpp
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp 
b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index dd3b84e47dec3..c51d71de145a4 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -1416,10 +1416,6 @@ static void 
LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
           stl_synth_flags,
           "lldb.formatters.cpp.gnu_libstdcpp.StdForwardListSynthProvider")));
 
-  AddCXXSynthetic(cpp_category_sp, LibStdcppSpanSyntheticFrontEndCreator,
-                  "libstdc++ std::span synthetic children", "^std::span<.+>$",
-                  stl_deref_flags, true);
-
   stl_summary_flags.SetDontShowChildren(false);
   stl_summary_flags.SetSkipPointers(false);
 
@@ -1510,11 +1506,6 @@ static void 
LoadLibStdcppFormatters(lldb::TypeCategoryImplSP 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::ContainerSizeSummaryProvider,
-                "libstdc++ std::span summary provider", "^std::span<.+>$",
-                stl_summary_flags, true);
 }
 
 static lldb_private::SyntheticChildrenFrontEnd *
@@ -1671,6 +1662,17 @@ 
GenericDequeSyntheticFrontEndCreator(CXXSyntheticChildren *children,
       "lldb.formatters.cpp.gnu_libstdcpp.StdDequeSynthProvider", *valobj_sp);
 }
 
+static SyntheticChildrenFrontEnd *
+GenericSpanSyntheticFrontEndCreator(CXXSyntheticChildren *children,
+                                    ValueObjectSP valobj_sp) {
+  if (!valobj_sp)
+    return nullptr;
+
+  if (IsMsvcStlSpan(*valobj_sp))
+    return MsvcStlSpanSyntheticFrontEndCreator(children, valobj_sp);
+  return LibStdcppSpanSyntheticFrontEndCreator(children, valobj_sp);
+}
+
 /// Load formatters that are formatting types from more than one STL
 static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
   if (!cpp_category_sp)
@@ -1759,6 +1761,9 @@ static void 
LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
   AddCXXSynthetic(cpp_category_sp, GenericDequeSyntheticFrontEndCreator,
                   "std::deque container synthetic children",
                   "^std::deque<.+>(( )?&)?$", stl_deref_flags, true);
+  AddCXXSynthetic(cpp_category_sp, GenericSpanSyntheticFrontEndCreator,
+                  "std::span container synthetic children",
+                  "^std::span<.+>$", stl_deref_flags, true);
 
   AddCXXSynthetic(cpp_category_sp, GenericMapLikeSyntheticFrontEndCreator,
                   "std::(multi)?map/set synthetic children",
@@ -1811,6 +1816,9 @@ static void 
LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
   AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider,
                 "MSVC STL/libstd++ std::deque summary provider",
                 "^std::deque<.+>(( )?&)?$", stl_summary_flags, true);
+  AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider,
+                "MSVC STL/libstd++ std::span summary provider",
+                "^std::span<.+>$", stl_summary_flags, true);
 }
 
 static void LoadMsvcStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
diff --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h 
b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
index e818b88e202ef..58e155b45d781 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
@@ -119,6 +119,12 @@ SyntheticChildrenFrontEnd *
 MsvcStlDequeSyntheticFrontEndCreator(CXXSyntheticChildren *,
                                      lldb::ValueObjectSP valobj_sp);
 
+// MSVC STL std::span<>
+bool IsMsvcStlSpan(ValueObject &valobj);
+SyntheticChildrenFrontEnd *
+MsvcStlSpanSyntheticFrontEndCreator(CXXSyntheticChildren *,
+                                    lldb::ValueObjectSP valobj_sp);
+
 } // namespace formatters
 } // namespace lldb_private
 
diff --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlSpan.cpp 
b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlSpan.cpp
new file mode 100644
index 0000000000000..8d1cfdb8986a0
--- /dev/null
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlSpan.cpp
@@ -0,0 +1,133 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "MsvcStl.h"
+
+#include "lldb/DataFormatters/FormattersHelpers.h"
+#include "lldb/Utility/ConstString.h"
+#include "lldb/ValueObject/ValueObject.h"
+#include <optional>
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::formatters;
+
+namespace lldb_private::formatters {
+
+class MsvcStlSpanSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
+public:
+  MsvcStlSpanSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
+
+  ~MsvcStlSpanSyntheticFrontEnd() override = default;
+
+  llvm::Expected<uint32_t> CalculateNumChildren() override {
+    return m_num_elements;
+  }
+
+  lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override;
+
+  lldb::ChildCacheState Update() override;
+
+  llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override;
+
+private:
+  ValueObject *m_start = nullptr; ///< First element of span. Held, not owned.
+  CompilerType m_element_type{};  ///< Type of span elements.
+  size_t m_num_elements = 0;      ///< Number of elements in span.
+  uint32_t m_element_size = 0;    ///< Size in bytes of each span element.
+};
+
+lldb_private::formatters::MsvcStlSpanSyntheticFrontEnd::
+    MsvcStlSpanSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
+    : SyntheticChildrenFrontEnd(*valobj_sp) {
+  if (valobj_sp)
+    Update();
+}
+
+lldb::ValueObjectSP
+lldb_private::formatters::MsvcStlSpanSyntheticFrontEnd::GetChildAtIndex(
+    uint32_t idx) {
+  if (!m_start)
+    return {};
+
+  uint64_t offset = idx * m_element_size;
+  offset = offset + m_start->GetValueAsUnsigned(0);
+  StreamString name;
+  name.Printf("[%" PRIu64 "]", (uint64_t)idx);
+  return CreateValueObjectFromAddress(name.GetString(), offset,
+                                      m_backend.GetExecutionContextRef(),
+                                      m_element_type);
+}
+
+lldb::ChildCacheState
+lldb_private::formatters::MsvcStlSpanSyntheticFrontEnd::Update() {
+  m_start = nullptr;
+  m_element_type = CompilerType();
+  m_num_elements = 0;
+  m_element_size = 0;
+
+  ValueObjectSP data_sp = m_backend.GetChildMemberWithName("_Mydata");
+  if (!data_sp)
+    return lldb::ChildCacheState::eRefetch;
+
+  m_element_type = data_sp->GetCompilerType().GetPointeeType();
+
+  // Get element size.
+  llvm::Expected<uint64_t> size_or_err = m_element_type.GetByteSize(nullptr);
+  if (!size_or_err) {
+    LLDB_LOG_ERRORV(GetLog(LLDBLog::DataFormatters), size_or_err.takeError(),
+                    "{0}");
+    return lldb::ChildCacheState::eRefetch;
+  }
+
+  m_element_size = *size_or_err;
+
+  // Get data.
+  if (m_element_size > 0)
+    m_start = data_sp.get();
+
+  // Get number of elements.
+  if (auto size_sp = m_backend.GetChildMemberWithName("_Mysize"))
+    m_num_elements = size_sp->GetValueAsUnsigned(0);
+  else if (auto field = m_backend.GetCompilerType()
+                         .GetDirectBaseClassAtIndex(0, nullptr) // 
_Span_extent_type
+                         .GetStaticFieldWithName("_Mysize"))
+      m_num_elements = field.GetConstantValue().ULongLong(0);
+
+  return lldb::ChildCacheState::eRefetch;
+}
+
+llvm::Expected<size_t>
+lldb_private::formatters::MsvcStlSpanSyntheticFrontEnd::GetIndexOfChildWithName(
+    ConstString name) {
+  if (!m_start)
+    return llvm::createStringError("Type has no child named '%s'",
+                                   name.AsCString());
+
+  auto optional_idx = formatters::ExtractIndexFromString(name.GetCString());
+  if (!optional_idx)
+    return llvm::createStringError("Type has no child named '%s'",
+                                   name.AsCString());
+  return *optional_idx;
+}
+
+bool IsMsvcStlSpan(ValueObject &valobj) {
+  if (auto valobj_sp = valobj.GetNonSyntheticValue())
+    return valobj_sp->GetChildMemberWithName("_Mydata") != nullptr;
+  return false;
+}
+
+lldb_private::SyntheticChildrenFrontEnd *
+MsvcStlSpanSyntheticFrontEndCreator(CXXSyntheticChildren *,
+                                    lldb::ValueObjectSP valobj_sp) {
+  if (!valobj_sp)
+    return nullptr;
+  return new MsvcStlSpanSyntheticFrontEnd(valobj_sp);
+}
+
+} // namespace lldb_private::formatters
diff --git 
a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/span/TestDataFormatterStdSpan.py
 
b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/span/TestDataFormatterStdSpan.py
index f586fb3d698c1..4cf447e49c848 100644
--- 
a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/span/TestDataFormatterStdSpan.py
+++ 
b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/span/TestDataFormatterStdSpan.py
@@ -9,6 +9,8 @@
 
 
 class StdSpanDataFormatterTestCase(TestBase):
+    TEST_WITH_PDB_DEBUG_INFO = True
+
     def findVariable(self, name):
         var = self.frame().FindVariable(name)
         self.assertTrue(var.IsValid())
@@ -89,21 +91,26 @@ def do_test(self):
         )
 
         # check access to synthetic children for dynamic spans
+        dynamic_string_span = (
+            "std::span<std::basic_string<char, std::char_traits<char>, 
std::allocator<char>>, -1>"
+            if self.getDebugInfo() == "pdb"
+            else "dynamic_string_span"
+        )
         self.runCmd(
-            'type summary add --summary-string "item 0 is ${var[0]}" 
dynamic_string_span'
+            f'type summary add --summary-string "item 0 is ${{var[0]}}" 
"{dynamic_string_span}"'
         )
         self.expect_var_path("strings_span", summary='item 0 is "smart"')
 
         self.runCmd(
-            'type summary add --summary-string "item 0 is ${svar[0]}" 
dynamic_string_span'
+            f'type summary add --summary-string "item 0 is ${{svar[0]}}" 
"{dynamic_string_span}"'
         )
         self.expect_var_path("strings_span", summary='item 0 is "smart"')
 
-        self.runCmd("type summary delete dynamic_string_span")
+        self.runCmd(f'type summary delete "{dynamic_string_span}"')
 
         # test summaries based on synthetic children
         self.runCmd(
-            'type summary add --summary-string "span has ${svar%#} items" -e 
dynamic_string_span'
+            f'type summary add --summary-string "span has ${{svar%#}} items" 
-e "{dynamic_string_span}"'
         )
 
         self.expect_var_path("strings_span", summary="span has 2 items")
@@ -127,7 +134,7 @@ def do_test(self):
             result_children=expectedStringSpanChildren,
         )
 
-        self.runCmd("type summary delete dynamic_string_span")
+        self.runCmd(f'type summary delete "{dynamic_string_span}"')
 
         lldbutil.continue_to_breakpoint(process, bkpt)
 
@@ -191,3 +198,15 @@ def test_libstdcxx(self):
     def test_ref_and_ptr_libstdcxx(self):
         self.build(dictionary={"USE_LIBSTDCPP": 1})
         self.do_test_ref_and_ptr()
+
+    @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()
+
+    @add_test_categories(["msvcstl"])
+    def test_ref_and_ptr_msvcstl(self):
+        # No flags, because the "msvcstl" category checks that the MSVC STL is 
used by default.
+        self.build()
+        self.do_test_ref_and_ptr()

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to