Author: nerix Date: 2025-07-15T17:17:28+01:00 New Revision: eec98bef84b8a68533d9176468c1d2010f26717f
URL: https://github.com/llvm/llvm-project/commit/eec98bef84b8a68533d9176468c1d2010f26717f DIFF: https://github.com/llvm/llvm-project/commit/eec98bef84b8a68533d9176468c1d2010f26717f.diff LOG: [LLDB] Add formatters for MSVC STL std::tuple (#148548) Adds synthetic children for MSVC STL's [`std::tuple`](https://github.com/microsoft/STL/blob/313964b78a8fd5a52e7965e13781f735bcce13c5/stl/inc/tuple). A `tuple` is a chain of base classes: ```cpp template <> class tuple<> {}; template <class _This, class... _Rest> class tuple<_This, _Rest...> : private tuple<_Rest...> { _Tuple_val<_This> _Myfirst; }; ``` So the provider walks the base classes to the desired one. The implementation makes it hard to detect if the empty tuple is from this STL. Fortunately, libstdc++'s synthetic children provider works for empty MSVC STL tuples as well. Towards #24834. Added: lldb/source/Plugins/Language/CPlusPlus/MsvcStlTuple.cpp Modified: lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/tuple/TestDataFormatterStdTuple.py lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/tuple/main.cpp Removed: ################################################################################ diff --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt index 296159ea28407..ea86b6b4327be 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt +++ b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt @@ -35,6 +35,7 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN LibStdcppUniquePointer.cpp MsvcStl.cpp MsvcStlSmartPointer.cpp + MsvcStlTuple.cpp MSVCUndecoratedNameParser.cpp LINK_COMPONENTS diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp index 9a869f3ea0289..bf4139119a76b 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp @@ -1507,10 +1507,6 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { "^std::((__debug::)?|(__cxx11::)?)list<.+>(( )?&)?$", stl_summary_flags, true); - AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider, - "libstdc++ std::tuple summary provider", - "^std::tuple<.*>(( )?&)?$", stl_summary_flags, true); - cpp_category_sp->AddTypeSummary( "^std::((__debug::)?|(__cxx11::)?)forward_list<.+>(( )?&)?$", eFormatterMatchRegex, @@ -1540,11 +1536,6 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { lldb_private::formatters::LibStdcppUniquePtrSyntheticFrontEndCreator, "std::unique_ptr synthetic children", "^std::unique_ptr<.+>(( )?&)?$", stl_synth_flags, true); - AddCXXSynthetic( - cpp_category_sp, - lldb_private::formatters::LibStdcppTupleSyntheticFrontEndCreator, - "std::tuple synthetic children", "^std::tuple<.*>(( )?&)?$", - stl_synth_flags, true); static constexpr const char *const libstdcpp_std_coroutine_handle_regex = "^std::coroutine_handle<.+>(( )?&)?$"; @@ -1613,6 +1604,17 @@ static bool GenericUniquePtrSummaryProvider(ValueObject &valobj, Stream &stream, return LibStdcppUniquePointerSummaryProvider(valobj, stream, options); } +static SyntheticChildrenFrontEnd * +GenericTupleSyntheticFrontEndCreator(CXXSyntheticChildren *children, + lldb::ValueObjectSP valobj_sp) { + if (!valobj_sp) + return nullptr; + + if (IsMsvcStlTuple(*valobj_sp)) + return MsvcStlTupleSyntheticFrontEndCreator(children, valobj_sp); + return LibStdcppTupleSyntheticFrontEndCreator(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) @@ -1668,6 +1670,9 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { AddCXXSynthetic(cpp_category_sp, GenericUniquePtrSyntheticFrontEndCreator, "std::unique_ptr synthetic children", "^std::unique_ptr<.+>(( )?&)?$", stl_synth_flags, true); + AddCXXSynthetic(cpp_category_sp, GenericTupleSyntheticFrontEndCreator, + "std::tuple synthetic children", "^std::tuple<.*>(( )?&)?$", + stl_synth_flags, true); AddCXXSummary(cpp_category_sp, GenericSmartPointerSummaryProvider, "MSVC STL/libstdc++ std::shared_ptr summary provider", @@ -1678,6 +1683,9 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { AddCXXSummary(cpp_category_sp, GenericUniquePtrSummaryProvider, "MSVC STL/libstdc++ std::unique_ptr summary provider", "^std::unique_ptr<.+>(( )?&)?$", stl_summary_flags, true); + AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider, + "MSVC STL/libstdc++ std::tuple summary provider", + "^std::tuple<.*>(( )?&)?$", 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 fe75bf275f8e2..bad47701904bb 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h +++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h @@ -45,6 +45,12 @@ bool MsvcStlUniquePtrSummaryProvider(ValueObject &valobj, Stream &stream, lldb_private::SyntheticChildrenFrontEnd * MsvcStlUniquePtrSyntheticFrontEndCreator(lldb::ValueObjectSP valobj_sp); +// MSVC STL std::tuple<> +bool IsMsvcStlTuple(ValueObject &valobj); +SyntheticChildrenFrontEnd * +MsvcStlTupleSyntheticFrontEndCreator(CXXSyntheticChildren *, + lldb::ValueObjectSP valobj_sp); + } // namespace formatters } // namespace lldb_private diff --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlTuple.cpp b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlTuple.cpp new file mode 100644 index 0000000000000..fe20b4c141a65 --- /dev/null +++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlTuple.cpp @@ -0,0 +1,105 @@ +//===-- MsvcStlTuple.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 "MsvcStl.h" +#include "lldb/DataFormatters/FormattersHelpers.h" + +using namespace lldb; +using namespace lldb_private; + +namespace { + +class TupleFrontEnd : public SyntheticChildrenFrontEnd { +public: + TupleFrontEnd(ValueObject &valobj) : SyntheticChildrenFrontEnd(valobj) { + Update(); + } + + llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override { + auto optional_idx = formatters::ExtractIndexFromString(name.GetCString()); + if (!optional_idx) { + return llvm::createStringError("Type has no child named '%s'", + name.AsCString()); + } + return *optional_idx; + } + + lldb::ChildCacheState Update() override; + llvm::Expected<uint32_t> CalculateNumChildren() override { + return m_elements.size(); + } + ValueObjectSP GetChildAtIndex(uint32_t idx) override; + +private: + // The lifetime of a ValueObject and all its derivative ValueObjects + // (children, clones, etc.) is managed by a ClusterManager. These + // objects are only destroyed when every shared pointer to any of them + // is destroyed, so we must not store a shared pointer to any ValueObject + // derived from our backend ValueObject (since we're in the same cluster). + std::vector<ValueObject *> m_elements; +}; + +} // namespace + +lldb::ChildCacheState TupleFrontEnd::Update() { + m_elements.clear(); + + size_t n_elements = 0; + for (CompilerType ty = m_backend.GetCompilerType(); + ty.GetNumDirectBaseClasses() > 0; + ty = ty.GetDirectBaseClassAtIndex(0, nullptr)) + ++n_elements; + + m_elements.assign(n_elements, nullptr); + return lldb::ChildCacheState::eRefetch; +} + +ValueObjectSP TupleFrontEnd::GetChildAtIndex(uint32_t idx) { + if (idx >= m_elements.size()) + return nullptr; + if (m_elements[idx]) + return m_elements[idx]->GetSP(); + + CompilerType holder_ty = m_backend.GetCompilerType(); + for (uint32_t i = 0; i < idx; i++) { + holder_ty = holder_ty.GetDirectBaseClassAtIndex(0, nullptr); + if (!holder_ty.IsValid()) + return nullptr; + } + + ValueObjectSP holder_sp = m_backend.Cast(holder_ty); + if (!holder_sp) + return nullptr; + holder_sp = holder_sp->GetChildMemberWithName("_Myfirst"); + + if (!holder_sp) + return nullptr; + + ValueObjectSP val_sp = holder_sp->GetChildMemberWithName("_Val"); + if (!val_sp) + return nullptr; + + m_elements[idx] = + val_sp->Clone(ConstString(llvm::formatv("[{0}]", idx).str())).get(); + return m_elements[idx]->GetSP(); +} + +bool formatters::IsMsvcStlTuple(ValueObject &valobj) { + // This returns false for empty tuples, but the libstdc++ formatter handles + // this correctly. + if (auto valobj_sp = valobj.GetNonSyntheticValue()) + return valobj_sp->GetChildMemberWithName("_Myfirst") != nullptr; + return false; +} + +SyntheticChildrenFrontEnd *formatters::MsvcStlTupleSyntheticFrontEndCreator( + CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { + if (valobj_sp) + return new TupleFrontEnd(*valobj_sp); + return nullptr; +} diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/tuple/TestDataFormatterStdTuple.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/tuple/TestDataFormatterStdTuple.py index ade502e12b928..b23d549fe4c18 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/tuple/TestDataFormatterStdTuple.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/tuple/TestDataFormatterStdTuple.py @@ -74,3 +74,9 @@ def test_libcxx(self): def test_libstdcxx(self): self.build(dictionary={"USE_LIBSTDCPP": 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/tuple/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/tuple/main.cpp index d49dbe8a5f1af..53d0a4efd3865 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/tuple/main.cpp +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/tuple/main.cpp @@ -6,5 +6,6 @@ int main() { std::tuple<int> one_elt{47}; std::tuple<std::string> string_elt{"foobar"}; std::tuple<int, long, std::string> three_elts{1, 47l, "foo"}; + auto *foo = ∅ // needed with MSVC STL to keep the variable return 0; // break here } _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits