Skip to content

Commit fe30b28

Browse files
committed
[LLDB] Add formatters for MSVC STL std::variant
1 parent 5c7c855 commit fe30b28

File tree

6 files changed

+264
-8
lines changed

6 files changed

+264
-8
lines changed

lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN
3636
MsvcStl.cpp
3737
MsvcStlSmartPointer.cpp
3838
MsvcStlTuple.cpp
39+
MsvcStlVariant.cpp
3940
MsvcStlVector.cpp
4041
MSVCUndecoratedNameParser.cpp
4142

lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,11 +1449,6 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
14491449
SyntheticChildrenSP(new ScriptedSyntheticChildren(
14501450
stl_synth_flags,
14511451
"lldb.formatters.cpp.gnu_libstdcpp.StdForwardListSynthProvider")));
1452-
cpp_category_sp->AddTypeSynthetic(
1453-
"^std::variant<.+>$", eFormatterMatchRegex,
1454-
SyntheticChildrenSP(new ScriptedSyntheticChildren(
1455-
stl_synth_flags,
1456-
"lldb.formatters.cpp.gnu_libstdcpp.VariantSynthProvider")));
14571452

14581453
stl_summary_flags.SetDontShowChildren(false);
14591454
stl_summary_flags.SetSkipPointers(false);
@@ -1509,9 +1504,6 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
15091504
TypeSummaryImplSP(new ScriptSummaryFormat(
15101505
stl_summary_flags,
15111506
"lldb.formatters.cpp.gnu_libstdcpp.ForwardListSummaryProvider")));
1512-
AddCXXSummary(cpp_category_sp, LibStdcppVariantSummaryProvider,
1513-
"libstdc++ std::variant summary provider", "^std::variant<.+>$",
1514-
stl_summary_flags, true);
15151507

15161508
AddCXXSynthetic(
15171509
cpp_category_sp,
@@ -1649,6 +1641,25 @@ GenericOptionalSyntheticFrontEndCreator(CXXSyntheticChildren *children,
16491641
return LibStdcppOptionalSyntheticFrontEndCreator(children, valobj_sp);
16501642
}
16511643

1644+
static SyntheticChildrenFrontEnd *
1645+
GenericVariantSyntheticFrontEndCreator(CXXSyntheticChildren *children,
1646+
lldb::ValueObjectSP valobj_sp) {
1647+
if (!valobj_sp)
1648+
return nullptr;
1649+
1650+
if (IsMsvcStlVariant(*valobj_sp))
1651+
return MsvcStlVariantSyntheticFrontEndCreator(children, valobj_sp);
1652+
return new ScriptedSyntheticChildren::FrontEnd(
1653+
"lldb.formatters.cpp.gnu_libstdcpp.VariantSynthProvider", *valobj_sp);
1654+
}
1655+
1656+
static bool GenericVariantSummaryProvider(ValueObject &valobj, Stream &stream,
1657+
const TypeSummaryOptions &options) {
1658+
if (IsMsvcStlVariant(valobj))
1659+
return MsvcStlVariantSummaryProvider(valobj, stream, options);
1660+
return LibStdcppVariantSummaryProvider(valobj, stream, options);
1661+
}
1662+
16521663
/// Load formatters that are formatting types from more than one STL
16531664
static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
16541665
if (!cpp_category_sp)
@@ -1713,6 +1724,9 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
17131724
AddCXXSynthetic(cpp_category_sp, GenericForwardListSyntheticFrontEndCreator,
17141725
"std::forward_list synthetic children",
17151726
"^std::forward_list<.+>(( )?&)?$", stl_synth_flags, true);
1727+
AddCXXSynthetic(cpp_category_sp, GenericVariantSyntheticFrontEndCreator,
1728+
"std::variant synthetic children", "^std::variant<.*>$",
1729+
stl_synth_flags, true);
17161730

17171731
SyntheticChildren::Flags stl_deref_flags = stl_synth_flags;
17181732
stl_deref_flags.SetFrontEndWantsDereference();
@@ -1749,6 +1763,9 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
17491763
AddCXXSummary(cpp_category_sp, GenericOptionalSummaryProvider,
17501764
"MSVC STL/libstd++ std::optional summary provider",
17511765
"^std::optional<.+>(( )?&)?$", stl_summary_flags, true);
1766+
AddCXXSummary(cpp_category_sp, GenericVariantSummaryProvider,
1767+
"MSVC STL/libstdc++ std::variant summary provider",
1768+
"^std::variant<.*>$", stl_summary_flags, true);
17521769
}
17531770

17541771
static void LoadMsvcStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {

lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ SyntheticChildrenFrontEnd *
6161
LibStdcppUniquePtrSyntheticFrontEndCreator(CXXSyntheticChildren *,
6262
lldb::ValueObjectSP);
6363

64+
bool LibStdcppVariantSummaryProvider(ValueObject &valobj, Stream &stream,
65+
const TypeSummaryOptions &options);
66+
6467
} // namespace formatters
6568
} // namespace lldb_private
6669

lldb/source/Plugins/Language/CPlusPlus/MsvcStl.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ SyntheticChildrenFrontEnd *
7171
MsvcStlOptionalSyntheticFrontEndCreator(CXXSyntheticChildren *,
7272
lldb::ValueObjectSP valobj_sp);
7373

74+
// MSVC STL std::variant<>
75+
bool IsMsvcStlVariant(ValueObject &valobj);
76+
bool MsvcStlVariantSummaryProvider(ValueObject &valobj, Stream &stream,
77+
const TypeSummaryOptions &options);
78+
SyntheticChildrenFrontEnd *
79+
MsvcStlVariantSyntheticFrontEndCreator(CXXSyntheticChildren *,
80+
lldb::ValueObjectSP valobj_sp);
81+
7482
} // namespace formatters
7583
} // namespace lldb_private
7684

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
//===-- MsvcStlVariant.cpp-------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "MsvcStl.h"
10+
#include "lldb/DataFormatters/FormattersHelpers.h"
11+
#include "lldb/Symbol/CompilerType.h"
12+
#include <optional>
13+
14+
using namespace lldb;
15+
using namespace lldb_private;
16+
17+
namespace {
18+
19+
// A variant when using DWARF looks as follows:
20+
// (lldb) fr v -R v1
21+
// (std::variant<int, double, char>) v1 = {
22+
// std::_SMF_control<std::_Variant_base<int, double, char>, int, double, char>
23+
// = {
24+
// std::_Variant_storage<int, double, char> = {
25+
// = {
26+
// _Head = 0
27+
// _Tail = {
28+
// = {
29+
// _Head = 2
30+
// _Tail = {
31+
// = {
32+
// _Head = '\0'
33+
// _Tail = {}
34+
// }
35+
// }
36+
// }
37+
// }
38+
// }
39+
// }
40+
// _Which = '\x01'
41+
// }
42+
// }
43+
//
44+
// ... when using PDB, it looks like this:
45+
// (lldb) fr v -R v1
46+
// (std::variant<int,double,char>) v1 = {
47+
// std::_Variant_base<int,double,char> = {
48+
// std::_Variant_storage_<1,int,double,char> = {
49+
// _Head = 0
50+
// _Tail = {
51+
// _Head = 2
52+
// _Tail = {
53+
// _Head = '\0'
54+
// _Tail = {}
55+
// }
56+
// }
57+
// }
58+
// _Which = '\x01'
59+
// }
60+
// }
61+
62+
ValueObjectSP GetStorageAtIndex(ValueObject &valobj, size_t index) {
63+
// PDB flattens the members on unions to the parent
64+
if (valobj.GetCompilerType().GetNumFields() == 2)
65+
return valobj.GetChildAtIndex(index);
66+
67+
// DWARF keeps the union
68+
ValueObjectSP union_sp = valobj.GetChildAtIndex(0);
69+
if (!union_sp)
70+
return nullptr;
71+
return union_sp->GetChildAtIndex(index);
72+
}
73+
74+
ValueObjectSP GetHead(ValueObject &valobj) {
75+
return GetStorageAtIndex(valobj, 0);
76+
}
77+
ValueObjectSP GetTail(ValueObject &valobj) {
78+
return GetStorageAtIndex(valobj, 1);
79+
}
80+
81+
std::optional<int64_t> GetIndexValue(ValueObject &valobj) {
82+
ValueObjectSP index_sp = valobj.GetChildMemberWithName("_Which");
83+
if (!index_sp)
84+
return std::nullopt;
85+
86+
return {index_sp->GetValueAsSigned(-1)};
87+
}
88+
89+
ValueObjectSP GetNthStorage(ValueObject &outer, int64_t index) {
90+
ValueObjectSP container_sp = outer.GetSP();
91+
92+
// When using DWARF, we need to find the std::_Variant_storage base class.
93+
// There, the top level type doesn't have any fields.
94+
if (container_sp->GetCompilerType().GetNumFields() == 0) {
95+
// -> std::_SMF_control (typedef to std::_Variant_base)
96+
container_sp = container_sp->GetChildAtIndex(0);
97+
if (!container_sp)
98+
return nullptr;
99+
// -> std::_Variant_storage
100+
container_sp = container_sp->GetChildAtIndex(0);
101+
if (!container_sp)
102+
return nullptr;
103+
}
104+
105+
for (int64_t i = 0; i < index; i++) {
106+
container_sp = GetTail(*container_sp);
107+
if (!container_sp)
108+
return nullptr;
109+
}
110+
return container_sp;
111+
}
112+
113+
} // namespace
114+
115+
bool formatters::IsMsvcStlVariant(ValueObject &valobj) {
116+
if (auto valobj_sp = valobj.GetNonSyntheticValue()) {
117+
return valobj_sp->GetChildMemberWithName("_Which") != nullptr;
118+
}
119+
return false;
120+
}
121+
122+
bool formatters::MsvcStlVariantSummaryProvider(
123+
ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
124+
ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue());
125+
if (!valobj_sp)
126+
return false;
127+
128+
auto index = GetIndexValue(*valobj_sp);
129+
if (!index)
130+
return false;
131+
132+
if (*index < 0) {
133+
stream.Printf(" No Value");
134+
return true;
135+
}
136+
137+
ValueObjectSP storage = GetNthStorage(*valobj_sp, *index);
138+
if (!storage)
139+
return false;
140+
CompilerType storage_type = storage->GetCompilerType();
141+
if (!storage_type)
142+
return false;
143+
// With DWARF, it's a typedef, with PDB, it's not
144+
if (storage_type.IsTypedefType())
145+
storage_type = storage_type.GetTypedefedType();
146+
147+
CompilerType active_type = storage_type.GetTypeTemplateArgument(1, true);
148+
if (!active_type) {
149+
// not enough debug info, try the type of _Head
150+
ValueObjectSP head_sp = GetHead(*storage);
151+
if (!head_sp)
152+
return false;
153+
active_type = head_sp->GetCompilerType();
154+
if (!active_type)
155+
return false;
156+
}
157+
158+
stream << " Active Type = " << active_type.GetDisplayTypeName() << " ";
159+
return true;
160+
}
161+
162+
namespace {
163+
class VariantFrontEnd : public SyntheticChildrenFrontEnd {
164+
public:
165+
VariantFrontEnd(ValueObject &valobj) : SyntheticChildrenFrontEnd(valobj) {
166+
Update();
167+
}
168+
169+
llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override {
170+
auto optional_idx = formatters::ExtractIndexFromString(name.GetCString());
171+
if (!optional_idx) {
172+
return llvm::createStringError("Type has no child named '%s'",
173+
name.AsCString());
174+
}
175+
return *optional_idx;
176+
}
177+
178+
lldb::ChildCacheState Update() override;
179+
llvm::Expected<uint32_t> CalculateNumChildren() override { return m_size; }
180+
ValueObjectSP GetChildAtIndex(uint32_t idx) override;
181+
182+
private:
183+
size_t m_size = 0;
184+
};
185+
} // namespace
186+
187+
lldb::ChildCacheState VariantFrontEnd::Update() {
188+
m_size = 0;
189+
190+
auto index = GetIndexValue(m_backend);
191+
if (index && *index >= 0)
192+
m_size = 1;
193+
194+
return lldb::ChildCacheState::eRefetch;
195+
}
196+
197+
ValueObjectSP VariantFrontEnd::GetChildAtIndex(uint32_t idx) {
198+
if (idx >= m_size)
199+
return nullptr;
200+
201+
auto index = GetIndexValue(m_backend);
202+
if (!index)
203+
return nullptr;
204+
205+
ValueObjectSP storage_sp = GetNthStorage(m_backend, *index);
206+
if (!storage_sp)
207+
return nullptr;
208+
209+
ValueObjectSP head_sp = GetHead(*storage_sp);
210+
if (!head_sp)
211+
return nullptr;
212+
213+
return head_sp->Clone(ConstString("Value"));
214+
}
215+
216+
SyntheticChildrenFrontEnd *formatters::MsvcStlVariantSyntheticFrontEndCreator(
217+
CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
218+
if (valobj_sp)
219+
return new VariantFrontEnd(*valobj_sp);
220+
return nullptr;
221+
}

lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/variant/TestDataFormatterStdVariant.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,9 @@ def test_libcxx(self):
8383
def test_libstdcxx(self):
8484
self.build(dictionary={"USE_LIBSTDCPP": 1})
8585
self.do_test()
86+
87+
@add_test_categories(["msvcstl"])
88+
def test_msvcstl(self):
89+
# No flags, because the "msvcstl" category checks that the MSVC STL is used by default.
90+
self.build()
91+
self.do_test()

0 commit comments

Comments
 (0)