[Lldb-commits] [lldb] [LLDB] Add formatters for MSVC STL std::variant (PR #148554)
via lldb-commits
lldb-commits at lists.llvm.org
Thu Jul 17 06:29:22 PDT 2025
https://github.com/Nerixyz updated https://github.com/llvm/llvm-project/pull/148554
>From 2ae71bc96e19575037e7e200f317e714f79bcd98 Mon Sep 17 00:00:00 2001
From: Nerixyz <nerixdev at outlook.de>
Date: Mon, 14 Jul 2025 00:30:48 +0200
Subject: [PATCH 1/2] [LLDB] Add formatters for MSVC STL std::variant
---
.../Plugins/Language/CPlusPlus/CMakeLists.txt | 1 +
.../Language/CPlusPlus/CPlusPlusLanguage.cpp | 33 ++-
.../Plugins/Language/CPlusPlus/LibStdcpp.h | 3 +
.../Plugins/Language/CPlusPlus/MsvcStl.h | 8 +
.../Language/CPlusPlus/MsvcStlVariant.cpp | 221 ++++++++++++++++++
.../variant/TestDataFormatterStdVariant.py | 6 +
6 files changed, 264 insertions(+), 8 deletions(-)
create mode 100644 lldb/source/Plugins/Language/CPlusPlus/MsvcStlVariant.cpp
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
index 5905d9b9a6d03..e1dd5bf84d7eb 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
+++ b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
@@ -36,6 +36,7 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN
MsvcStl.cpp
MsvcStlSmartPointer.cpp
MsvcStlTuple.cpp
+ MsvcStlVariant.cpp
MsvcStlVector.cpp
MSVCUndecoratedNameParser.cpp
diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
index a8ebde0b55815..2a06422e86d0c 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -1449,11 +1449,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);
@@ -1509,9 +1504,6 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
TypeSummaryImplSP(new ScriptSummaryFormat(
stl_summary_flags,
"lldb.formatters.cpp.gnu_libstdcpp.ForwardListSummaryProvider")));
- AddCXXSummary(cpp_category_sp, LibStdcppVariantSummaryProvider,
- "libstdc++ std::variant summary provider", "^std::variant<.+>$",
- stl_summary_flags, true);
AddCXXSynthetic(
cpp_category_sp,
@@ -1648,6 +1640,25 @@ GenericForwardListSyntheticFrontEndCreator(CXXSyntheticChildren *children,
*valobj_sp);
}
+static 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)
@@ -1712,6 +1723,9 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
AddCXXSynthetic(cpp_category_sp, GenericForwardListSyntheticFrontEndCreator,
"std::forward_list synthetic children",
"^std::forward_list<.+>(( )?&)?$", stl_synth_flags, true);
+ AddCXXSynthetic(cpp_category_sp, GenericVariantSyntheticFrontEndCreator,
+ "std::variant synthetic children", "^std::variant<.*>$",
+ stl_synth_flags, true);
AddCXXSummary(cpp_category_sp, GenericSmartPointerSummaryProvider,
"MSVC STL/libstdc++ std::shared_ptr summary provider",
@@ -1739,6 +1753,9 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
TypeSummaryImplSP(new ScriptSummaryFormat(
stl_summary_flags,
"lldb.formatters.cpp.gnu_libstdcpp.ForwardListSummaryProvider")));
+ 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.h b/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h
index 8d2025e940ead..429142f63a4bd 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h
@@ -61,6 +61,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 0f3db4b50eeaf..c262df644b387 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h
@@ -65,6 +65,14 @@ SyntheticChildrenFrontEnd *
MsvcStlListSyntheticFrontEndCreator(CXXSyntheticChildren *,
lldb::ValueObjectSP valobj_sp);
+// MSVC STL std::variant<>
+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()
>From 13ecdbae9dfdca47b4c30568ea102ef7f066a033 Mon Sep 17 00:00:00 2001
From: Nerixyz <nerixdev at outlook.de>
Date: Thu, 17 Jul 2025 15:29:08 +0200
Subject: [PATCH 2/2] fix: remove PDB workarounds
---
.../Language/CPlusPlus/MsvcStlVariant.cpp | 67 +++++--------------
1 file changed, 17 insertions(+), 50 deletions(-)
diff --git a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlVariant.cpp b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlVariant.cpp
index e8a6b0e930c2c..52a3d98d2af4b 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/MsvcStlVariant.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/MsvcStlVariant.cpp
@@ -40,42 +40,20 @@ namespace {
// _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 GetStorageMember(ValueObject &valobj, llvm::StringRef name) {
+ // Find the union
ValueObjectSP union_sp = valobj.GetChildAtIndex(0);
if (!union_sp)
return nullptr;
- return union_sp->GetChildAtIndex(index);
+ return union_sp->GetChildMemberWithName(name);
}
ValueObjectSP GetHead(ValueObject &valobj) {
- return GetStorageAtIndex(valobj, 0);
+ return GetStorageMember(valobj, "_Head");
}
ValueObjectSP GetTail(ValueObject &valobj) {
- return GetStorageAtIndex(valobj, 1);
+ return GetStorageMember(valobj, "_Tail");
}
std::optional<int64_t> GetIndexValue(ValueObject &valobj) {
@@ -87,20 +65,16 @@ std::optional<int64_t> GetIndexValue(ValueObject &valobj) {
}
ValueObjectSP GetNthStorage(ValueObject &outer, int64_t index) {
- ValueObjectSP container_sp = outer.GetSP();
+ // We need to find the std::_Variant_storage base class.
- // 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;
- }
+ // -> std::_SMF_control (typedef to std::_Variant_base)
+ ValueObjectSP container_sp = outer.GetSP()->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);
@@ -140,20 +114,13 @@ bool formatters::MsvcStlVariantSummaryProvider(
CompilerType storage_type = storage->GetCompilerType();
if (!storage_type)
return false;
- // With DWARF, it's a typedef, with PDB, it's not
+ // Resolve the typedef
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;
- }
+ if (!active_type)
+ return false;
stream << " Active Type = " << active_type.GetDisplayTypeName() << " ";
return true;
More information about the lldb-commits
mailing list