[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