Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement data formatters for LibStdC++ std::variant #68012

Merged
merged 4 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions lldb/examples/synthetic/gnu_libstdcpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -892,3 +892,87 @@ def update(self):
except:
pass
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"

active_type = data_obj.GetType().GetTemplateArgumentType(index)
return f" Active Type = {active_type.GetDisplayTypeName()} "


class VariantSynthProvider:
def __init__(self, valobj, dict):
self.raw_obj = valobj.GetNonSyntheticValue()
self.is_valid = False
self.index = None
self.data_obj = None

def update(self):
try:
self.index = self.raw_obj.GetChildMemberWithName(
"_M_index"
).GetValueAsSigned(-1)
self.is_valid = self.index != -1
self.data_obj = self.raw_obj.GetChildMemberWithName("_M_u")
except:
self.is_valid = False
return False

def has_children(self):
return True

def num_children(self):
return 1 if self.is_valid else 0

def get_child_index(self, name):
return 0

def get_child_at_index(self, index):
if not self.is_valid:
return None
cur = 0
node = self.data_obj
while cur < self.index:
node = node.GetChildMemberWithName("_M_rest")
cur += 1

# _M_storage's type depends on variant field's type "_Type".
# 1. if '_Type' is literal type: _Type _M_storage.
# 2. otherwise, __gnu_cxx::__aligned_membuf<_Type> _M_storage.
#
# For 2. we have to cast it to underlying template _Type.

value = node.GetChildMemberWithName("_M_first").GetChildMemberWithName(
"_M_storage"
)
template_type = value.GetType().GetTemplateArgumentType(0)

# Literal type will return None for GetTemplateArgumentType(0)
if (
template_type
and "__gnu_cxx::__aligned_membuf" in value.GetType().GetDisplayTypeName()
and template_type.IsValid()
):
value = value.Cast(template_type)

if value.IsValid():
return value.Clone("Value")
return None
22 changes: 15 additions & 7 deletions lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -332,14 +332,12 @@ bool CPlusPlusLanguage::MethodName::ContainsPath(llvm::StringRef path) {
// If we can't parse the incoming name, then just check that it contains path.
if (m_parse_error)
return m_full.GetStringRef().contains(path);

llvm::StringRef identifier;
llvm::StringRef context;
std::string path_str = path.str();
bool success
= CPlusPlusLanguage::ExtractContextAndIdentifier(path_str.c_str(),
context,
identifier);
bool success = CPlusPlusLanguage::ExtractContextAndIdentifier(
path_str.c_str(), context, identifier);
if (!success)
return m_full.GetStringRef().contains(path);

Expand Down Expand Up @@ -372,7 +370,7 @@ bool CPlusPlusLanguage::MethodName::ContainsPath(llvm::StringRef path) {
return false;
if (haystack.empty() || !isalnum(haystack.back()))
return true;

return false;
}

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

bool CPlusPlusLanguage::DemangledNameContainsPath(llvm::StringRef path,
bool CPlusPlusLanguage::DemangledNameContainsPath(llvm::StringRef path,
ConstString demangled) const {
MethodName demangled_name(demangled);
return demangled_name.ContainsPath(path);
Expand Down Expand Up @@ -1104,6 +1102,11 @@ 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);
Expand Down Expand Up @@ -1148,6 +1151,11 @@ 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,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CXX_SOURCES := main.cpp

USE_LIBSTDCPP := 1
CXXFLAGS_EXTRAS := -std=c++17
include Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""
Test lldb data formatter for LibStdC++ std::variant.
"""


import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil


class LibStdcxxVariantDataFormatterTestCase(TestBase):
@add_test_categories(["libstdcxx"])
def test_with_run_command(self):
"""Test LibStdC++ std::variant data formatter works correctly."""
self.build()

(self.target, self.process, _, bkpt) = lldbutil.run_to_source_breakpoint(
self, "// break here", lldb.SBFileSpec("main.cpp", False)
)

lldbutil.continue_to_breakpoint(self.process, bkpt)

self.expect(
"frame variable v1",
substrs=["v1 = Active Type = int {", "Value = 12", "}"],
)

self.expect(
"frame variable v1_ref",
substrs=["v1_ref = Active Type = int : {", "Value = 12", "}"],
)

self.expect(
"frame variable v_v1",
substrs=[
"v_v1 = Active Type = std::variant<int, double, char> {",
"Value = Active Type = int {",
"Value = 12",
"}",
"}",
],
)

lldbutil.continue_to_breakpoint(self.process, bkpt)

self.expect(
"frame variable v1",
substrs=["v1 = Active Type = double {", "Value = 2", "}"],
)

lldbutil.continue_to_breakpoint(self.process, bkpt)

self.expect(
"frame variable v2",
substrs=["v2 = Active Type = double {", "Value = 2", "}"],
)

self.expect(
"frame variable v3",
substrs=["v3 = Active Type = char {", "Value = 'A'", "}"],
)

self.expect("frame variable v_no_value", substrs=["v_no_value = No Value"])

self.expect(
"frame variable v_many_types_no_value",
substrs=["v_many_types_no_value = No Value"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include <cstdio>
#include <string>
#include <variant>
#include <vector>

struct S {
operator int() { throw 42; }
};

int main() {
bool has_variant = true;

printf("%d\n", has_variant); // break here

std::variant<int, double, char> v1;
std::variant<int, double, char> &v1_ref = v1;
std::variant<int, double, char> v2;
std::variant<int, double, char> v3;
std::variant<std::variant<int, double, char>> v_v1;
std::variant<int, double, char> v_no_value;
// The next variant has many types, meaning the type index does not fit in
// a byte and must be `unsigned short` instead of `unsigned char` when
// using the unstable libc++ ABI. With stable libc++ ABI, the type index
// is always just `unsigned int`.
std::variant<
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int, int, int, int, int, int, int, int, int>
v_many_types_no_value;

v1 = 12; // v contains int
v_v1 = v1;
int i = std::get<int>(v1);
printf("%d\n", i); // break here

v2 = 2.0;
double d = std::get<double>(v2);
printf("%f\n", d);

v3 = 'A';
char c = std::get<char>(v3);
printf("%d\n", c);

// Checking v1 above and here to make sure we done maintain the incorrect
// state when we change its value.
v1 = 2.0;
d = std::get<double>(v1);
printf("%f\n", d); // break here

try {
v_no_value.emplace<0>(S());
} catch (...) {
}

printf("%zu\n", v_no_value.index());

try {
v_many_types_no_value.emplace<0>(S());
} catch (...) {
}

printf("%zu\n", v_many_types_no_value.index());

return 0; // break here
}