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

Reply via email to