https://github.com/Nerixyz created https://github.com/llvm/llvm-project/pull/148554
Adds a summary and synthetic children for MSVC STL's `std::variant`. This one is a bit complicated because of DWARF vs PDB differences. I put the representations in comments. Being able to `GetChildMemberWithName` a member in an anonymous union would make this a lot simpler (`std::optional` will have something similar iirc). Draft, because I'll split up the libstdc++ and Makefile changes. >From ccd7869f4602fb514e8dacaf17370f972d13351d Mon Sep 17 00:00:00 2001 From: Nerixyz <nerix...@outlook.de> Date: Mon, 14 Jul 2025 00:30:48 +0200 Subject: [PATCH] [LLDB] Add formatters for MSVC STL std::variant --- lldb/examples/synthetic/gnu_libstdcpp.py | 32 --- .../Python/lldbsuite/test/make/Makefile.rules | 4 - .../Plugins/Language/CPlusPlus/CMakeLists.txt | 1 + .../Language/CPlusPlus/CPlusPlusLanguage.cpp | 38 ++- .../Plugins/Language/CPlusPlus/LibStdcpp.cpp | 46 ++++ .../Plugins/Language/CPlusPlus/LibStdcpp.h | 3 + .../Plugins/Language/CPlusPlus/MsvcStl.h | 7 + .../Language/CPlusPlus/MsvcStlVariant.cpp | 221 ++++++++++++++++++ .../variant/TestDataFormatterStdVariant.py | 6 + 9 files changed, 312 insertions(+), 46 deletions(-) create mode 100644 lldb/source/Plugins/Language/CPlusPlus/MsvcStlVariant.cpp diff --git a/lldb/examples/synthetic/gnu_libstdcpp.py b/lldb/examples/synthetic/gnu_libstdcpp.py index 20b9488af5597..f42a009c21f48 100644 --- a/lldb/examples/synthetic/gnu_libstdcpp.py +++ b/lldb/examples/synthetic/gnu_libstdcpp.py @@ -882,38 +882,6 @@ def update(self): return False -def VariantSummaryProvider(valobj, dict): - raw_obj = valobj.GetNonSyntheticValue() - index_obj = raw_obj.GetChildMemberWithName("_M_index") - data_obj = raw_obj.GetChildMemberWithName("_M_u") - if not (index_obj and index_obj.IsValid() and data_obj and data_obj.IsValid()): - return "<Can't find _M_index or _M_u>" - - def get_variant_npos_value(index_byte_size): - if index_byte_size == 1: - return 0xFF - elif index_byte_size == 2: - return 0xFFFF - else: - return 0xFFFFFFFF - - npos_value = get_variant_npos_value(index_obj.GetByteSize()) - index = index_obj.GetValueAsUnsigned(0) - if index == npos_value: - return " No Value" - - # Strip references and typedefs. - variant_type = raw_obj.GetType().GetCanonicalType().GetDereferencedType() - template_arg_count = variant_type.GetNumberOfTemplateArguments() - - # Invalid index can happen when the variant is not initialized yet. - if index >= template_arg_count: - return " <Invalid>" - - active_type = variant_type.GetTemplateArgumentType(index) - return f" Active Type = {active_type.GetDisplayTypeName()} " - - class VariantSynthProvider: def __init__(self, valobj, dict): self.raw_obj = valobj.GetNonSyntheticValue() diff --git a/lldb/packages/Python/lldbsuite/test/make/Makefile.rules b/lldb/packages/Python/lldbsuite/test/make/Makefile.rules index 58833e1b0cc78..8521ca508a479 100644 --- a/lldb/packages/Python/lldbsuite/test/make/Makefile.rules +++ b/lldb/packages/Python/lldbsuite/test/make/Makefile.rules @@ -344,10 +344,6 @@ endif #---------------------------------------------------------------------- ifeq "$(OS)" "Windows_NT" ifeq ($(CC_TYPE), clang) - # Clang for Windows doesn't support C++ Exceptions - CXXFLAGS += -fno-exceptions - CXXFLAGS += -D_HAS_EXCEPTIONS=0 - # MSVC 2015 or higher is required, which depends on c++14, so # append these values unconditionally. CXXFLAGS += -fms-compatibility-version=19.0 diff --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt index 296159ea28407..462c40ec93a0b 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 + MsvcStlVariant.cpp MSVCUndecoratedNameParser.cpp LINK_COMPONENTS diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp index 2db3e6f0ca315..3feee55f487dc 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp @@ -1451,11 +1451,6 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { SyntheticChildrenSP(new ScriptedSyntheticChildren( stl_synth_flags, "lldb.formatters.cpp.gnu_libstdcpp.StdForwardListSynthProvider"))); - cpp_category_sp->AddTypeSynthetic( - "^std::variant<.+>$", eFormatterMatchRegex, - SyntheticChildrenSP(new ScriptedSyntheticChildren( - stl_synth_flags, - "lldb.formatters.cpp.gnu_libstdcpp.VariantSynthProvider"))); stl_summary_flags.SetDontShowChildren(false); stl_summary_flags.SetSkipPointers(false); @@ -1517,11 +1512,6 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { TypeSummaryImplSP(new ScriptSummaryFormat( stl_summary_flags, "lldb.formatters.cpp.gnu_libstdcpp.ForwardListSummaryProvider"))); - cpp_category_sp->AddTypeSummary( - "^std::variant<.+>$", eFormatterMatchRegex, - TypeSummaryImplSP(new ScriptSummaryFormat( - stl_summary_flags, - "lldb.formatters.cpp.gnu_libstdcpp.VariantSummaryProvider"))); AddCXXSynthetic( cpp_category_sp, @@ -1599,6 +1589,25 @@ GenericSmartPointerSummaryProvider(ValueObject &valobj, Stream &stream, return LibStdcppSmartPointerSummaryProvider(valobj, stream, options); } +static lldb_private::SyntheticChildrenFrontEnd * +GenericVariantSyntheticFrontEndCreator(CXXSyntheticChildren *children, + lldb::ValueObjectSP valobj_sp) { + if (!valobj_sp) + return nullptr; + + if (IsMsvcStlVariant(*valobj_sp)) + return MsvcStlVariantSyntheticFrontEndCreator(children, valobj_sp); + return new ScriptedSyntheticChildren::FrontEnd( + "lldb.formatters.cpp.gnu_libstdcpp.VariantSynthProvider", *valobj_sp); +} + +static bool GenericVariantSummaryProvider(ValueObject &valobj, Stream &stream, + const TypeSummaryOptions &options) { + if (IsMsvcStlVariant(valobj)) + return MsvcStlVariantSummaryProvider(valobj, stream, options); + return LibStdcppVariantSummaryProvider(valobj, stream, options); +} + /// Load formatters that are formatting types from more than one STL static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { if (!cpp_category_sp) @@ -1648,6 +1657,12 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { AddCXXSynthetic(cpp_category_sp, GenericSmartPointerSyntheticFrontEndCreator, "std::weak_ptr synthetic children", "^std::weak_ptr<.+>(( )?&)?$", stl_synth_flags, true); + AddCXXSynthetic(cpp_category_sp, GenericVariantSyntheticFrontEndCreator, + "std::variant synthetic children", "^std::variant<.*>$", + stl_synth_flags, true); + + stl_summary_flags.SetDontShowChildren(false); + stl_summary_flags.SetSkipPointers(false); AddCXXSummary(cpp_category_sp, GenericSmartPointerSummaryProvider, "MSVC STL/libstdc++ std::shared_ptr summary provider", @@ -1655,6 +1670,9 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { AddCXXSummary(cpp_category_sp, GenericSmartPointerSummaryProvider, "MSVC STL/libstdc++ std::weak_ptr summary provider", "^std::weak_ptr<.+>(( )?&)?$", stl_summary_flags, true); + AddCXXSummary(cpp_category_sp, GenericVariantSummaryProvider, + "MSVC STL/libstdc++ std::variant summary provider", + "^std::variant<.*>$", stl_summary_flags, true); } static void LoadMsvcStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.cpp index c80a52d0f9ed6..595e835b37df9 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.cpp @@ -366,3 +366,49 @@ bool lldb_private::formatters::LibStdcppSmartPointerSummaryProvider( return true; } + +static uint64_t LibStdcppVariantNposValue(size_t index_byte_size) { + switch (index_byte_size) { + case 1: + return 0xff; + case 2: + return 0xffff; + default: + return 0xffff'ffff; + } +} + +bool formatters::LibStdcppVariantSummaryProvider( + ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { + ValueObjectSP valobj_sp = valobj.GetNonSyntheticValue(); + if (!valobj_sp) + return false; + + ValueObjectSP index_obj = valobj_sp->GetChildMemberWithName("_M_index"); + ValueObjectSP data_obj = valobj_sp->GetChildMemberWithName("_M_u"); + if (!index_obj || !data_obj) + return false; + + auto index_bytes = index_obj->GetByteSize(); + if (!index_bytes) + return false; + auto npos_value = LibStdcppVariantNposValue(*index_bytes); + auto index = index_obj->GetValueAsUnsigned(0); + if (index == npos_value) { + stream.Printf(" No Value"); + return true; + } + + auto variant_type = + valobj_sp->GetCompilerType().GetCanonicalType().GetNonReferenceType(); + if (!variant_type) + return false; + if (index >= variant_type.GetNumTemplateArguments(true)) { + stream.Printf(" <Invalid>"); + return true; + } + + auto active_type = variant_type.GetTypeTemplateArgument(index, true); + stream << " Active Type = " << active_type.GetDisplayTypeName() << " "; + return true; +} diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h b/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h index 8d4d777edee88..707f255954727 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h +++ b/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h @@ -57,6 +57,9 @@ SyntheticChildrenFrontEnd * LibStdcppUniquePtrSyntheticFrontEndCreator(CXXSyntheticChildren *, lldb::ValueObjectSP); +bool LibStdcppVariantSummaryProvider(ValueObject &valobj, Stream &stream, + const TypeSummaryOptions &options); + } // namespace formatters } // namespace lldb_private diff --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h index edf3f4e8a5387..26bf01d9866bd 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h +++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h @@ -37,6 +37,13 @@ bool MsvcStlSmartPointerSummaryProvider(ValueObject &valobj, Stream &stream, lldb_private::SyntheticChildrenFrontEnd * MsvcStlSmartPointerSyntheticFrontEndCreator(lldb::ValueObjectSP valobj_sp); +bool IsMsvcStlVariant(ValueObject &valobj); +bool MsvcStlVariantSummaryProvider(ValueObject &valobj, Stream &stream, + const TypeSummaryOptions &options); +SyntheticChildrenFrontEnd * +MsvcStlVariantSyntheticFrontEndCreator(CXXSyntheticChildren *, + lldb::ValueObjectSP valobj_sp); + } // namespace formatters } // namespace lldb_private diff --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlVariant.cpp b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlVariant.cpp new file mode 100644 index 0000000000000..e8a6b0e930c2c --- /dev/null +++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlVariant.cpp @@ -0,0 +1,221 @@ +//===-- MsvcStlVariant.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" +#include "lldb/Symbol/CompilerType.h" +#include <optional> + +using namespace lldb; +using namespace lldb_private; + +namespace { + +// A variant when using DWARF looks as follows: +// (lldb) fr v -R v1 +// (std::variant<int, double, char>) v1 = { +// std::_SMF_control<std::_Variant_base<int, double, char>, int, double, char> +// = { +// std::_Variant_storage<int, double, char> = { +// = { +// _Head = 0 +// _Tail = { +// = { +// _Head = 2 +// _Tail = { +// = { +// _Head = '\0' +// _Tail = {} +// } +// } +// } +// } +// } +// } +// _Which = '\x01' +// } +// } +// +// ... when using PDB, it looks like this: +// (lldb) fr v -R v1 +// (std::variant<int,double,char>) v1 = { +// std::_Variant_base<int,double,char> = { +// std::_Variant_storage_<1,int,double,char> = { +// _Head = 0 +// _Tail = { +// _Head = 2 +// _Tail = { +// _Head = '\0' +// _Tail = {} +// } +// } +// } +// _Which = '\x01' +// } +// } + +ValueObjectSP GetStorageAtIndex(ValueObject &valobj, size_t index) { + // PDB flattens the members on unions to the parent + if (valobj.GetCompilerType().GetNumFields() == 2) + return valobj.GetChildAtIndex(index); + + // DWARF keeps the union + ValueObjectSP union_sp = valobj.GetChildAtIndex(0); + if (!union_sp) + return nullptr; + return union_sp->GetChildAtIndex(index); +} + +ValueObjectSP GetHead(ValueObject &valobj) { + return GetStorageAtIndex(valobj, 0); +} +ValueObjectSP GetTail(ValueObject &valobj) { + return GetStorageAtIndex(valobj, 1); +} + +std::optional<int64_t> GetIndexValue(ValueObject &valobj) { + ValueObjectSP index_sp = valobj.GetChildMemberWithName("_Which"); + if (!index_sp) + return std::nullopt; + + return {index_sp->GetValueAsSigned(-1)}; +} + +ValueObjectSP GetNthStorage(ValueObject &outer, int64_t index) { + ValueObjectSP container_sp = outer.GetSP(); + + // When using DWARF, we need to find the std::_Variant_storage base class. + // There, the top level type doesn't have any fields. + if (container_sp->GetCompilerType().GetNumFields() == 0) { + // -> std::_SMF_control (typedef to std::_Variant_base) + container_sp = container_sp->GetChildAtIndex(0); + if (!container_sp) + return nullptr; + // -> std::_Variant_storage + container_sp = container_sp->GetChildAtIndex(0); + if (!container_sp) + return nullptr; + } + + for (int64_t i = 0; i < index; i++) { + container_sp = GetTail(*container_sp); + if (!container_sp) + return nullptr; + } + return container_sp; +} + +} // namespace + +bool formatters::IsMsvcStlVariant(ValueObject &valobj) { + if (auto valobj_sp = valobj.GetNonSyntheticValue()) { + return valobj_sp->GetChildMemberWithName("_Which") != nullptr; + } + return false; +} + +bool formatters::MsvcStlVariantSummaryProvider( + ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { + ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue()); + if (!valobj_sp) + return false; + + auto index = GetIndexValue(*valobj_sp); + if (!index) + return false; + + if (*index < 0) { + stream.Printf(" No Value"); + return true; + } + + ValueObjectSP storage = GetNthStorage(*valobj_sp, *index); + if (!storage) + return false; + CompilerType storage_type = storage->GetCompilerType(); + if (!storage_type) + return false; + // With DWARF, it's a typedef, with PDB, it's not + if (storage_type.IsTypedefType()) + storage_type = storage_type.GetTypedefedType(); + + CompilerType active_type = storage_type.GetTypeTemplateArgument(1, true); + if (!active_type) { + // not enough debug info, try the type of _Head + ValueObjectSP head_sp = GetHead(*storage); + if (!head_sp) + return false; + active_type = head_sp->GetCompilerType(); + if (!active_type) + return false; + } + + stream << " Active Type = " << active_type.GetDisplayTypeName() << " "; + return true; +} + +namespace { +class VariantFrontEnd : public SyntheticChildrenFrontEnd { +public: + VariantFrontEnd(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_size; } + ValueObjectSP GetChildAtIndex(uint32_t idx) override; + +private: + size_t m_size = 0; +}; +} // namespace + +lldb::ChildCacheState VariantFrontEnd::Update() { + m_size = 0; + + auto index = GetIndexValue(m_backend); + if (index && *index >= 0) + m_size = 1; + + return lldb::ChildCacheState::eRefetch; +} + +ValueObjectSP VariantFrontEnd::GetChildAtIndex(uint32_t idx) { + if (idx >= m_size) + return nullptr; + + auto index = GetIndexValue(m_backend); + if (!index) + return nullptr; + + ValueObjectSP storage_sp = GetNthStorage(m_backend, *index); + if (!storage_sp) + return nullptr; + + ValueObjectSP head_sp = GetHead(*storage_sp); + if (!head_sp) + return nullptr; + + return head_sp->Clone(ConstString("Value")); +} + +SyntheticChildrenFrontEnd *formatters::MsvcStlVariantSyntheticFrontEndCreator( + CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { + if (valobj_sp) + return new VariantFrontEnd(*valobj_sp); + return nullptr; +} diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/variant/TestDataFormatterStdVariant.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/variant/TestDataFormatterStdVariant.py index 9365cfc96783e..9f32ad97c1f0a 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/variant/TestDataFormatterStdVariant.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/variant/TestDataFormatterStdVariant.py @@ -83,3 +83,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() _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits