Skip to content

Commit 1ec4330

Browse files
jeffreytan81jeffreytan81
andauthored
Implement data formatters for LibStdC++ std::variant (#68012)
This patch implements the data formatters for LibStdC++ `std::variant`. --------- Co-authored-by: jeffreytan81 <[email protected]>
1 parent 1493462 commit 1ec4330

File tree

5 files changed

+252
-7
lines changed

5 files changed

+252
-7
lines changed

lldb/examples/synthetic/gnu_libstdcpp.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -892,3 +892,87 @@ def update(self):
892892
except:
893893
pass
894894
return False
895+
896+
897+
def VariantSummaryProvider(valobj, dict):
898+
raw_obj = valobj.GetNonSyntheticValue()
899+
index_obj = raw_obj.GetChildMemberWithName("_M_index")
900+
data_obj = raw_obj.GetChildMemberWithName("_M_u")
901+
if not (index_obj and index_obj.IsValid() and data_obj and data_obj.IsValid()):
902+
return "<Can't find _M_index or _M_u>"
903+
904+
def get_variant_npos_value(index_byte_size):
905+
if index_byte_size == 1:
906+
return 0xFF
907+
elif index_byte_size == 2:
908+
return 0xFFFF
909+
else:
910+
return 0xFFFFFFFF
911+
912+
npos_value = get_variant_npos_value(index_obj.GetByteSize())
913+
index = index_obj.GetValueAsUnsigned(0)
914+
if index == npos_value:
915+
return " No Value"
916+
917+
active_type = data_obj.GetType().GetTemplateArgumentType(index)
918+
return f" Active Type = {active_type.GetDisplayTypeName()} "
919+
920+
921+
class VariantSynthProvider:
922+
def __init__(self, valobj, dict):
923+
self.raw_obj = valobj.GetNonSyntheticValue()
924+
self.is_valid = False
925+
self.index = None
926+
self.data_obj = None
927+
928+
def update(self):
929+
try:
930+
self.index = self.raw_obj.GetChildMemberWithName(
931+
"_M_index"
932+
).GetValueAsSigned(-1)
933+
self.is_valid = self.index != -1
934+
self.data_obj = self.raw_obj.GetChildMemberWithName("_M_u")
935+
except:
936+
self.is_valid = False
937+
return False
938+
939+
def has_children(self):
940+
return True
941+
942+
def num_children(self):
943+
return 1 if self.is_valid else 0
944+
945+
def get_child_index(self, name):
946+
return 0
947+
948+
def get_child_at_index(self, index):
949+
if not self.is_valid:
950+
return None
951+
cur = 0
952+
node = self.data_obj
953+
while cur < self.index:
954+
node = node.GetChildMemberWithName("_M_rest")
955+
cur += 1
956+
957+
# _M_storage's type depends on variant field's type "_Type".
958+
# 1. if '_Type' is literal type: _Type _M_storage.
959+
# 2. otherwise, __gnu_cxx::__aligned_membuf<_Type> _M_storage.
960+
#
961+
# For 2. we have to cast it to underlying template _Type.
962+
963+
value = node.GetChildMemberWithName("_M_first").GetChildMemberWithName(
964+
"_M_storage"
965+
)
966+
template_type = value.GetType().GetTemplateArgumentType(0)
967+
968+
# Literal type will return None for GetTemplateArgumentType(0)
969+
if (
970+
template_type
971+
and "__gnu_cxx::__aligned_membuf" in value.GetType().GetDisplayTypeName()
972+
and template_type.IsValid()
973+
):
974+
value = value.Cast(template_type)
975+
976+
if value.IsValid():
977+
return value.Clone("Value")
978+
return None

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

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -332,14 +332,12 @@ bool CPlusPlusLanguage::MethodName::ContainsPath(llvm::StringRef path) {
332332
// If we can't parse the incoming name, then just check that it contains path.
333333
if (m_parse_error)
334334
return m_full.GetStringRef().contains(path);
335-
335+
336336
llvm::StringRef identifier;
337337
llvm::StringRef context;
338338
std::string path_str = path.str();
339-
bool success
340-
= CPlusPlusLanguage::ExtractContextAndIdentifier(path_str.c_str(),
341-
context,
342-
identifier);
339+
bool success = CPlusPlusLanguage::ExtractContextAndIdentifier(
340+
path_str.c_str(), context, identifier);
343341
if (!success)
344342
return m_full.GetStringRef().contains(path);
345343

@@ -372,7 +370,7 @@ bool CPlusPlusLanguage::MethodName::ContainsPath(llvm::StringRef path) {
372370
return false;
373371
if (haystack.empty() || !isalnum(haystack.back()))
374372
return true;
375-
373+
376374
return false;
377375
}
378376

@@ -388,7 +386,7 @@ bool CPlusPlusLanguage::IsCPPMangledName(llvm::StringRef name) {
388386
return true;
389387
}
390388

391-
bool CPlusPlusLanguage::DemangledNameContainsPath(llvm::StringRef path,
389+
bool CPlusPlusLanguage::DemangledNameContainsPath(llvm::StringRef path,
392390
ConstString demangled) const {
393391
MethodName demangled_name(demangled);
394392
return demangled_name.ContainsPath(path);
@@ -1104,6 +1102,11 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
11041102
SyntheticChildrenSP(new ScriptedSyntheticChildren(
11051103
stl_synth_flags,
11061104
"lldb.formatters.cpp.gnu_libstdcpp.StdForwardListSynthProvider")));
1105+
cpp_category_sp->AddTypeSynthetic(
1106+
"^std::variant<.+>$", eFormatterMatchRegex,
1107+
SyntheticChildrenSP(new ScriptedSyntheticChildren(
1108+
stl_synth_flags,
1109+
"lldb.formatters.cpp.gnu_libstdcpp.VariantSynthProvider")));
11071110

11081111
stl_summary_flags.SetDontShowChildren(false);
11091112
stl_summary_flags.SetSkipPointers(false);
@@ -1148,6 +1151,11 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
11481151
TypeSummaryImplSP(new ScriptSummaryFormat(
11491152
stl_summary_flags,
11501153
"lldb.formatters.cpp.gnu_libstdcpp.ForwardListSummaryProvider")));
1154+
cpp_category_sp->AddTypeSummary(
1155+
"^std::variant<.+>$", eFormatterMatchRegex,
1156+
TypeSummaryImplSP(new ScriptSummaryFormat(
1157+
stl_summary_flags,
1158+
"lldb.formatters.cpp.gnu_libstdcpp.VariantSummaryProvider")));
11511159

11521160
AddCXXSynthetic(
11531161
cpp_category_sp,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
CXX_SOURCES := main.cpp
2+
3+
USE_LIBSTDCPP := 1
4+
CXXFLAGS_EXTRAS := -std=c++17
5+
include Makefile.rules
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
Test lldb data formatter for LibStdC++ std::variant.
3+
"""
4+
5+
6+
import lldb
7+
from lldbsuite.test.decorators import *
8+
from lldbsuite.test.lldbtest import *
9+
from lldbsuite.test import lldbutil
10+
11+
12+
class LibStdcxxVariantDataFormatterTestCase(TestBase):
13+
@add_test_categories(["libstdcxx"])
14+
def test_with_run_command(self):
15+
"""Test LibStdC++ std::variant data formatter works correctly."""
16+
self.build()
17+
18+
(self.target, self.process, _, bkpt) = lldbutil.run_to_source_breakpoint(
19+
self, "// break here", lldb.SBFileSpec("main.cpp", False)
20+
)
21+
22+
lldbutil.continue_to_breakpoint(self.process, bkpt)
23+
24+
self.expect(
25+
"frame variable v1",
26+
substrs=["v1 = Active Type = int {", "Value = 12", "}"],
27+
)
28+
29+
self.expect(
30+
"frame variable v1_ref",
31+
substrs=["v1_ref = Active Type = int : {", "Value = 12", "}"],
32+
)
33+
34+
self.expect(
35+
"frame variable v_v1",
36+
substrs=[
37+
"v_v1 = Active Type = std::variant<int, double, char> {",
38+
"Value = Active Type = int {",
39+
"Value = 12",
40+
"}",
41+
"}",
42+
],
43+
)
44+
45+
lldbutil.continue_to_breakpoint(self.process, bkpt)
46+
47+
self.expect(
48+
"frame variable v1",
49+
substrs=["v1 = Active Type = double {", "Value = 2", "}"],
50+
)
51+
52+
lldbutil.continue_to_breakpoint(self.process, bkpt)
53+
54+
self.expect(
55+
"frame variable v2",
56+
substrs=["v2 = Active Type = double {", "Value = 2", "}"],
57+
)
58+
59+
self.expect(
60+
"frame variable v3",
61+
substrs=["v3 = Active Type = char {", "Value = 'A'", "}"],
62+
)
63+
64+
self.expect("frame variable v_no_value", substrs=["v_no_value = No Value"])
65+
66+
self.expect(
67+
"frame variable v_many_types_no_value",
68+
substrs=["v_many_types_no_value = No Value"],
69+
)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#include <cstdio>
2+
#include <string>
3+
#include <variant>
4+
#include <vector>
5+
6+
struct S {
7+
operator int() { throw 42; }
8+
};
9+
10+
int main() {
11+
bool has_variant = true;
12+
13+
printf("%d\n", has_variant); // break here
14+
15+
std::variant<int, double, char> v1;
16+
std::variant<int, double, char> &v1_ref = v1;
17+
std::variant<int, double, char> v2;
18+
std::variant<int, double, char> v3;
19+
std::variant<std::variant<int, double, char>> v_v1;
20+
std::variant<int, double, char> v_no_value;
21+
// The next variant has many types, meaning the type index does not fit in
22+
// a byte and must be `unsigned short` instead of `unsigned char` when
23+
// using the unstable libc++ ABI. With stable libc++ ABI, the type index
24+
// is always just `unsigned int`.
25+
std::variant<
26+
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
27+
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
28+
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
29+
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
30+
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
31+
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
32+
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
33+
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
34+
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
35+
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
36+
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
37+
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
38+
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
39+
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
40+
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
41+
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
42+
int, int, int, int, int, int, int, int, int, int, int, int>
43+
v_many_types_no_value;
44+
45+
v1 = 12; // v contains int
46+
v_v1 = v1;
47+
int i = std::get<int>(v1);
48+
printf("%d\n", i); // break here
49+
50+
v2 = 2.0;
51+
double d = std::get<double>(v2);
52+
printf("%f\n", d);
53+
54+
v3 = 'A';
55+
char c = std::get<char>(v3);
56+
printf("%d\n", c);
57+
58+
// Checking v1 above and here to make sure we done maintain the incorrect
59+
// state when we change its value.
60+
v1 = 2.0;
61+
d = std::get<double>(v1);
62+
printf("%f\n", d); // break here
63+
64+
try {
65+
v_no_value.emplace<0>(S());
66+
} catch (...) {
67+
}
68+
69+
printf("%zu\n", v_no_value.index());
70+
71+
try {
72+
v_many_types_no_value.emplace<0>(S());
73+
} catch (...) {
74+
}
75+
76+
printf("%zu\n", v_many_types_no_value.index());
77+
78+
return 0; // break here
79+
}

0 commit comments

Comments
 (0)