From 24cf7b3359beba96c5079d406ebc124e17bb0288 Mon Sep 17 00:00:00 2001 From: Ebuka Ezike Date: Tue, 18 Nov 2025 19:35:00 +0000 Subject: [PATCH 1/4] [lldb] add libstdcpp span formatter --- .../Plugins/Language/CPlusPlus/CMakeLists.txt | 1 + .../Language/CPlusPlus/CPlusPlusLanguage.cpp | 9 ++ .../Plugins/Language/CPlusPlus/LibStdcpp.h | 4 + .../Language/CPlusPlus/LibStdcppSpan.cpp | 112 ++++++++++++++++++ .../generic/span/TestDataFormatterStdSpan.py | 26 ++-- 5 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp diff --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt index ca4fd3f680484..c52d3bdb31284 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt +++ b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt @@ -31,6 +31,7 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN LibCxxValarray.cpp LibCxxVector.cpp LibStdcpp.cpp + LibStdcppSpan.cpp LibStdcppTuple.cpp LibStdcppUniquePointer.cpp MsvcStl.cpp diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp index 3cbae1e87f3dc..597cf1e06ffe2 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp @@ -1411,6 +1411,10 @@ 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); @@ -1501,6 +1505,11 @@ 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 * diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h b/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h index 429142f63a4bd..8d2c81f2bbcbb 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h +++ b/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h @@ -37,6 +37,10 @@ SyntheticChildrenFrontEnd * LibstdcppMapIteratorSyntheticFrontEndCreator(CXXSyntheticChildren *, lldb::ValueObjectSP); +SyntheticChildrenFrontEnd * +LibStdcppSpanSyntheticFrontEndCreator(CXXSyntheticChildren *, + lldb::ValueObjectSP); + SyntheticChildrenFrontEnd * LibStdcppTupleSyntheticFrontEndCreator(CXXSyntheticChildren *, lldb::ValueObjectSP); diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp new file mode 100644 index 0000000000000..98ea769c78a77 --- /dev/null +++ b/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp @@ -0,0 +1,112 @@ +//===---------------------------------------------------------------------===// +// +// 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 "LibStdcpp.h" + +#include "lldb/DataFormatters/FormattersHelpers.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/ValueObject/ValueObject.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/Support/Error.h" +#include +#include + +using namespace lldb; + +namespace lldb_private::formatters { + +class LibStdcppSpanSyntheticFrontEnd : public SyntheticChildrenFrontEnd { +public: + LibStdcppSpanSyntheticFrontEnd(const lldb::ValueObjectSP &valobj_sp) + : SyntheticChildrenFrontEnd(*valobj_sp) { + if (valobj_sp) + Update(); + } + + ~LibStdcppSpanSyntheticFrontEnd() override = default; + + llvm::Expected CalculateNumChildren() override { + return m_num_elements; + } + + lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override { + if (!m_start) + return {}; + + uint64_t offset = (static_cast(idx) * m_element_size); + offset += m_start->GetValueAsUnsigned(0); + const std::string name = llvm::formatv("[{0}]", idx); + return CreateValueObjectFromAddress( + name, offset, m_backend.GetExecutionContextRef(), m_element_type); + } + + lldb::ChildCacheState Update() override { + const ValueObjectSP element_ptr = + m_backend.GetChildMemberWithName(ConstString("_M_ptr")); + if (!element_ptr) + return lldb::ChildCacheState::eRefetch; + + m_element_type = element_ptr->GetCompilerType().GetPointeeType(); + + // Get element size. + llvm::Expected 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::eReuse; + } + + m_element_size = *size_or_err; + if (m_element_size > 0) { + m_start = element_ptr.get(); + } + + // Get number of elements. + if (auto size_sp = m_backend.GetChildAtNamePath( + {ConstString("_M_extent"), ConstString("_M_extent_value")})) { + m_num_elements = size_sp->GetValueAsUnsigned(0); + } else if (auto arg = + m_backend.GetCompilerType().GetIntegralTemplateArgument(1)) { + + m_num_elements = arg->value.GetAPSInt().getLimitedValue(); + } + + return lldb::ChildCacheState::eReuse; + } + + llvm::Expected GetIndexOfChildWithName(ConstString name) override { + 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; + } + +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. +}; + +SyntheticChildrenFrontEnd * +LibStdcppSpanSyntheticFrontEndCreator(CXXSyntheticChildren * /*unused*/, + lldb::ValueObjectSP valobj_sp) { + if (!valobj_sp) + return nullptr; + const CompilerType type = valobj_sp->GetCompilerType(); + if (!type || type.GetNumTemplateArguments() != 2) + return nullptr; + return new LibStdcppSpanSyntheticFrontEnd(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 a45c0ff551323..f586fb3d698c1 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 @@ -74,7 +74,7 @@ def do_test(self): result_summary="item 0 is 1", ) - self.runCmd("type summary delete span") + self.runCmd("type summary clear") # New span with strings lldbutil.continue_to_breakpoint(process, bkpt) @@ -155,12 +155,6 @@ def do_test(self): ) self.check_size("nested", 2) - @skipIf(compiler="clang", compiler_version=["<", "11.0"]) - @add_test_categories(["libc++"]) - def test_libcxx(self): - self.build(dictionary={"USE_LIBCPP": 1}) - self.do_test() - def do_test_ref_and_ptr(self): """Test that std::span is correctly formatted when passed by ref and ptr""" (self.target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint( @@ -174,8 +168,26 @@ def do_test_ref_and_ptr(self): self.expect("frame variable ptr", patterns=["ptr = 0x[0-9a-f]+ size=5"]) + @skipIf(compiler="clang", compiler_version=["<", "11.0"]) + @add_test_categories(["libc++"]) + def test_libcxx(self): + self.build(dictionary={"USE_LIBCPP": 1}) + self.do_test() + @skipIf(compiler="clang", compiler_version=["<", "11.0"]) @add_test_categories(["libc++"]) def test_ref_and_ptr_libcxx(self): self.build(dictionary={"USE_LIBCPP": 1}) self.do_test_ref_and_ptr() + + @skipIf(compiler="clang", compiler_version=["<", "11.0"]) + @add_test_categories(["libstdcxx"]) + def test_libstdcxx(self): + self.build(dictionary={"USE_LIBSTDCPP": 1}) + self.do_test() + + @skipIf(compiler="clang", compiler_version=["<", "11.0"]) + @add_test_categories(["libstdcxx"]) + def test_ref_and_ptr_libstdcxx(self): + self.build(dictionary={"USE_LIBSTDCPP": 1}) + self.do_test_ref_and_ptr() From 97c2d699328b770f7474af01a408b1d1451467c2 Mon Sep 17 00:00:00 2001 From: Ebuka Ezike Date: Wed, 19 Nov 2025 22:06:00 +0000 Subject: [PATCH 2/4] add review changes. --- .../Language/CPlusPlus/LibStdcppSpan.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp index 98ea769c78a77..8fb3036172eb4 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp @@ -81,21 +81,22 @@ class LibStdcppSpanSyntheticFrontEnd : public SyntheticChildrenFrontEnd { llvm::Expected GetIndexOfChildWithName(ConstString name) override { if (!m_start) - return llvm::createStringError("Type has no child named '%s'", - name.AsCString()); + return llvm::createStringError( + llvm::formatv("Type has no child named {0}", name.AsCString())); + auto optional_idx = formatters::ExtractIndexFromString(name.GetCString()); if (!optional_idx) { - return llvm::createStringError("Type has no child named '%s'", - name.AsCString()); + return llvm::createStringError( + llvm::formatv("Type has no child named {0}", name.AsCString())); } return *optional_idx; } 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. + 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. }; SyntheticChildrenFrontEnd * From 0f80c91a4fa886d0c8486fd6ba09444a8e12c6e4 Mon Sep 17 00:00:00 2001 From: Ebuka Ezike Date: Wed, 19 Nov 2025 22:09:22 +0000 Subject: [PATCH 3/4] add review changes. --- lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp index 8fb3036172eb4..0fcd53ff645fb 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp @@ -82,12 +82,12 @@ class LibStdcppSpanSyntheticFrontEnd : public SyntheticChildrenFrontEnd { llvm::Expected GetIndexOfChildWithName(ConstString name) override { if (!m_start) return llvm::createStringError( - llvm::formatv("Type has no child named {0}", name.AsCString())); + llvm::formatv("Type has no child named {0}", name.GetStringRef())); auto optional_idx = formatters::ExtractIndexFromString(name.GetCString()); if (!optional_idx) { return llvm::createStringError( - llvm::formatv("Type has no child named {0}", name.AsCString())); + llvm::formatv("Type has no child named {0}", name.GetStringRef())); } return *optional_idx; } From e100581f3c8a59a1fb61ca8a8c1035a2e6dbd907 Mon Sep 17 00:00:00 2001 From: Ebuka Ezike Date: Fri, 21 Nov 2025 11:54:47 +0000 Subject: [PATCH 4/4] cleanup. --- .../Plugins/Language/CPlusPlus/LibStdcppSpan.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp index 0fcd53ff645fb..5e69792151c87 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp @@ -46,12 +46,11 @@ class LibStdcppSpanSyntheticFrontEnd : public SyntheticChildrenFrontEnd { } lldb::ChildCacheState Update() override { - const ValueObjectSP element_ptr = - m_backend.GetChildMemberWithName(ConstString("_M_ptr")); - if (!element_ptr) + const ValueObjectSP data_ptr = m_backend.GetChildMemberWithName("_M_ptr"); + if (!data_ptr) return lldb::ChildCacheState::eRefetch; - m_element_type = element_ptr->GetCompilerType().GetPointeeType(); + m_element_type = data_ptr->GetCompilerType().GetPointeeType(); // Get element size. llvm::Expected size_or_err = m_element_type.GetByteSize(nullptr); @@ -63,14 +62,14 @@ class LibStdcppSpanSyntheticFrontEnd : public SyntheticChildrenFrontEnd { m_element_size = *size_or_err; if (m_element_size > 0) { - m_start = element_ptr.get(); + m_start = data_ptr.get(); } // Get number of elements. - if (auto size_sp = m_backend.GetChildAtNamePath( - {ConstString("_M_extent"), ConstString("_M_extent_value")})) { + if (const ValueObjectSP size_sp = + m_backend.GetChildAtNamePath({"_M_extent", "_M_extent_value"})) { m_num_elements = size_sp->GetValueAsUnsigned(0); - } else if (auto arg = + } else if (const auto arg = m_backend.GetCompilerType().GetIntegralTemplateArgument(1)) { m_num_elements = arg->value.GetAPSInt().getLimitedValue();