[Lldb-commits] [lldb] [LLDB] Add MSVC STL span formatter (PR #173053)
via lldb-commits
lldb-commits at lists.llvm.org
Fri Dec 19 09:39:22 PST 2025
https://github.com/Nerixyz updated https://github.com/llvm/llvm-project/pull/173053
>From fc0b9580fa0f7d8a0730a707b436bac91119209e Mon Sep 17 00:00:00 2001
From: Nerixyz <nerixdev at outlook.de>
Date: Fri, 19 Dec 2025 18:23:08 +0100
Subject: [PATCH 1/2] [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()
>From e711733a1852a2e9e2c60973b7349944ab3f6497 Mon Sep 17 00:00:00 2001
From: Nerixyz <nerixdev at outlook.de>
Date: Fri, 19 Dec 2025 18:39:08 +0100
Subject: [PATCH 2/2] fix: formatting
---
lldb/source/Plugins/Language/CPlusPlus/MsvcStlSpan.cpp | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlSpan.cpp b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlSpan.cpp
index 8d1cfdb8986a0..6d99aeb42a407 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlSpan.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlSpan.cpp
@@ -94,10 +94,11 @@ lldb_private::formatters::MsvcStlSpanSyntheticFrontEnd::Update() {
// 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);
+ 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;
}
More information about the lldb-commits
mailing list