Skip to content

Commit

Permalink
[formatters] Add a formatter for libstdc++ optional
Browse files Browse the repository at this point in the history
Besides adding the formatter and the summary, this makes the libcxx
tests also work for this case.

This is the polished version of https://reviews.llvm.org/D114266,
authored by Danil Stefaniuc.

Differential Revision: https://reviews.llvm.org/D114403
  • Loading branch information
walter-erquinigo committed Nov 22, 2021
1 parent 9cd7c53 commit e3dea5c
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 73 deletions.
3 changes: 3 additions & 0 deletions lldb/bindings/interface/SBValue.i
Expand Up @@ -410,6 +410,9 @@ public:
bool
SetData (lldb::SBData &data, lldb::SBError& error);

lldb::SBValue
Clone(const char *new_name);

lldb::addr_t
GetLoadAddress();

Expand Down
52 changes: 40 additions & 12 deletions lldb/examples/synthetic/gnu_libstdcpp.py
Expand Up @@ -8,6 +8,34 @@
# You are encouraged to look at the STL implementation for your platform
# before relying on these formatters to do the right thing for your setup

def StdOptionalSummaryProvider(valobj, dict):
has_value = valobj.GetNumChildren() > 0
# We add wrapping spaces for consistency with the libcxx formatter
return " Has Value=" + ("true" if has_value else "false") + " "


class StdOptionalSynthProvider:
def __init__(self, valobj, dict):
self.valobj = valobj

def update(self):
try:
self.payload = self.valobj.GetChildMemberWithName('_M_payload')
self.value = self.payload.GetChildMemberWithName('_M_payload')
self.count = self.payload.GetChildMemberWithName('_M_engaged').GetValueAsUnsigned(0)
except:
self.count = 0
return False


def num_children(self):
return self.count

def get_child_index(self, name):
return 0

def get_child_at_index(self, index):
return self.value.Clone('Value')

"""
This formatter can be applied to all
Expand All @@ -26,15 +54,15 @@ def get_object_kind(self, valobj):
def extract_type(self):
type = self.valobj.GetType()
# type of std::pair<key, value> is the first template
# argument type of the 4th template argument to std::map and
# 3rd template argument for std::set. That's why
# argument type of the 4th template argument to std::map and
# 3rd template argument for std::set. That's why
# we need to know kind of the object
template_arg_num = 4 if self.kind == "map" else 3
allocator_type = type.GetTemplateArgumentType(template_arg_num)
data_type = allocator_type.GetTemplateArgumentType(0)
return data_type

def update(self):
def update(self):
# preemptively setting this to None - we might end up changing our mind
# later
self.count = None
Expand Down Expand Up @@ -64,12 +92,12 @@ def get_child_at_index(self, index):
return None
try:
offset = index
current = self.next
current = self.next
while offset > 0:
current = current.GetChildMemberWithName('_M_nxt')
offset = offset - 1
return current.CreateChildAtOffset( '[' + str(index) + ']', self.skip_size, self.data_type)

except:
logger >> "Cannot get child"
return None
Expand Down Expand Up @@ -115,7 +143,7 @@ def is_valid(self, node):
else:
logger >> "synthetic value is not valid"
return valid

def value(self, node):
logger = lldb.formatters.Logger.Logger()
value = node.GetValueAsUnsigned()
Expand Down Expand Up @@ -161,7 +189,7 @@ def num_children_impl(self):
# After a std::list has been initialized, both next and prev will
# be non-NULL
next_val = self.next.GetValueAsUnsigned(0)
if next_val == 0:
if next_val == 0:
return 0
if self.has_loop():
return 0
Expand All @@ -172,14 +200,14 @@ def num_children_impl(self):
if next_val == self.node_address:
return 0
if next_val == prev_val:
return 1
return 1
size = 1
current = self.next
while current.GetChildMemberWithName(
'_M_next').GetValueAsUnsigned(0) != self.get_end_of_list_address():
size = size + 1
current = current.GetChildMemberWithName('_M_next')
return size
return size
except:
logger >> "Error determining the size"
return 0
Expand All @@ -190,7 +218,7 @@ def get_child_index(self, name):
return int(name.lstrip('[').rstrip(']'))
except:
return -1

def get_child_at_index(self, index):
logger = lldb.formatters.Logger.Logger()
logger >> "Fetching child " + str(index)
Expand Down Expand Up @@ -247,7 +275,7 @@ def updateNodes(self):

def has_children(self):
return True

'''
Method is used to identify if a node traversal has reached its end
and is mandatory to be overriden in each AbstractListSynthProvider subclass
Expand Down Expand Up @@ -368,7 +396,7 @@ def update(self):
self.count = 0
except:
pass
return False
return False

class StdVBoolImplementation(object):

Expand Down
6 changes: 6 additions & 0 deletions lldb/include/lldb/API/SBValue.h
Expand Up @@ -246,6 +246,12 @@ class LLDB_API SBValue {

bool SetData(lldb::SBData &data, lldb::SBError &error);

/// Creates a copy of the SBValue with a new name and setting the current
/// SBValue as its parent. It should be used when we want to change the
/// name of a SBValue without modifying the actual SBValue itself
/// (e.g. sythetic child provider).
lldb::SBValue Clone(const char *new_name);

lldb::SBDeclaration GetDeclaration();

/// Find out if a SBValue might have children.
Expand Down
13 changes: 13 additions & 0 deletions lldb/source/API/SBValue.cpp
Expand Up @@ -1431,6 +1431,18 @@ bool SBValue::SetData(lldb::SBData &data, SBError &error) {
return ret;
}

lldb::SBValue SBValue::Clone(const char *new_name) {
LLDB_RECORD_METHOD(lldb::SBValue, SBValue, Clone, (const char *), new_name);

ValueLocker locker;
lldb::ValueObjectSP value_sp(GetSP(locker));

if (value_sp)
return lldb::SBValue(value_sp->Clone(ConstString(new_name)));
else
return lldb::SBValue();
}

lldb::SBDeclaration SBValue::GetDeclaration() {
LLDB_RECORD_METHOD_NO_ARGS(lldb::SBDeclaration, SBValue, GetDeclaration);

Expand Down Expand Up @@ -1656,6 +1668,7 @@ void RegisterMethods<SBValue>(Registry &R) {
LLDB_REGISTER_METHOD(lldb::SBData, SBValue, GetData, ());
LLDB_REGISTER_METHOD(bool, SBValue, SetData,
(lldb::SBData &, lldb::SBError &));
LLDB_REGISTER_METHOD(lldb::SBValue, SBValue, Clone, (const char *));
LLDB_REGISTER_METHOD(lldb::SBDeclaration, SBValue, GetDeclaration, ());
LLDB_REGISTER_METHOD(lldb::SBWatchpoint, SBValue, Watch,
(bool, bool, bool, lldb::SBError &));
Expand Down
11 changes: 11 additions & 0 deletions lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
Expand Up @@ -913,6 +913,11 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
SyntheticChildrenSP(new ScriptedSyntheticChildren(
stl_deref_flags,
"lldb.formatters.cpp.gnu_libstdcpp.StdMapLikeSynthProvider")));
cpp_category_sp->GetRegexTypeSyntheticsContainer()->Add(
RegularExpression("^std::optional<.+>(( )?&)?$"),
SyntheticChildrenSP(new ScriptedSyntheticChildren(
stl_synth_flags,
"lldb.formatters.cpp.gnu_libstdcpp.StdOptionalSynthProvider")));
cpp_category_sp->GetRegexTypeSyntheticsContainer()->Add(
RegularExpression("^std::multiset<.+> >(( )?&)?$"),
SyntheticChildrenSP(new ScriptedSyntheticChildren(
Expand All @@ -933,8 +938,14 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
SyntheticChildrenSP(new ScriptedSyntheticChildren(
stl_synth_flags,
"lldb.formatters.cpp.gnu_libstdcpp.StdForwardListSynthProvider")));

stl_summary_flags.SetDontShowChildren(false);
stl_summary_flags.SetSkipPointers(false);
cpp_category_sp->GetRegexTypeSummariesContainer()->Add(
RegularExpression("^std::optional<.+>(( )?&)?$"),
TypeSummaryImplSP(new ScriptSummaryFormat(
stl_summary_flags,
"lldb.formatters.cpp.gnu_libstdcpp.StdOptionalSummaryProvider")));
cpp_category_sp->GetRegexTypeSummariesContainer()->Add(
RegularExpression("^std::bitset<.+>(( )?&)?$"),
TypeSummaryImplSP(
Expand Down
@@ -1,6 +1,4 @@
CXX_SOURCES := main.cpp

USE_LIBCPP := 1

CXXFLAGS_EXTRAS := -std=c++17 -fno-exceptions
include Makefile.rules
@@ -1,29 +1,18 @@
"""
Test lldb data formatter subsystem.
"""



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

USE_LIBSTDCPP = "USE_LIBSTDCPP"
USE_LIBCPP = "USE_LIBCPP"

class LibcxxOptionalDataFormatterTestCase(TestBase):
class GenericOptionalDataFormatterTestCase(TestBase):

mydir = TestBase.compute_mydir(__file__)

@add_test_categories(["libc++"])
## Clang 7.0 is the oldest Clang that can reliably parse newer libc++ versions
## with -std=c++17.
@skipIf(oslist=no_match(["macosx"]), compiler="clang", compiler_version=['<', '7.0'])
## We are skipping gcc version less that 5.1 since this test requires -std=c++17
@skipIf(compiler="gcc", compiler_version=['<', '5.1'])

def test_with_run_command(self):
def do_test_with_run_command(self, stdlib_type):
"""Test that that file and class static variables display correctly."""
self.build()
self.build(dictionary={stdlib_type: "1"})
self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)

bkpt = self.target().FindBreakpointByID(
Expand All @@ -45,7 +34,7 @@ def test_with_run_command(self):
## detected we have a sufficient libc++ version to support optional
## false means we do not and therefore should skip the test
if output.find("(bool) has_optional = false") != -1 :
self.skipTest( "Optional not supported" )
self.skipTest( "Optional not supported" )

lldbutil.continue_to_breakpoint(self.process(), bkpt)

Expand All @@ -71,3 +60,21 @@ def test_with_run_command(self):
substrs=['(optional_string) ostring = Has Value=true {',
'Value = "hello"',
'}'])

@add_test_categories(["libc++"])
## Clang 7.0 is the oldest Clang that can reliably parse newer libc++ versions
## with -std=c++17.
@skipIf(oslist=no_match(["macosx"]), compiler="clang", compiler_version=['<', '7.0'])
## We are skipping gcc version less that 5.1 since this test requires -std=c++17
@skipIf(compiler="gcc", compiler_version=['<', '5.1'])
def test_with_run_command_libcpp(self):
self.do_test_with_run_command(USE_LIBCPP)

@add_test_categories(["libstdcxx"])
## Clang 7.0 is the oldest Clang that can reliably parse newer libc++ versions
## with -std=c++17.
@skipIf(compiler="clang", compiler_version=['<', '7.0'])
## We are skipping gcc version less that 5.1 since this test requires -std=c++17
@skipIf(compiler="gcc", compiler_version=['<', '5.1'])
def test_with_run_command_libstdcpp(self):
self.do_test_with_run_command(USE_LIBSTDCPP)
@@ -0,0 +1,41 @@
#include <cstdio>
#include <string>
#include <vector>

// If we have libc++ 4.0 or greater we should have <optional>
// According to libc++ C++1z status page
// https://libcxx.llvm.org/cxx1z_status.html
#if !defined(_LIBCPP_VERSION) || _LIBCPP_VERSION >= 4000
#include <optional>
#define HAVE_OPTIONAL 1
#else
#define HAVE_OPTIONAL 0
#endif

int main() {
bool has_optional = HAVE_OPTIONAL;

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

#if HAVE_OPTIONAL == 1
using int_vect = std::vector<int>;
using optional_int = std::optional<int>;
using optional_int_vect = std::optional<int_vect>;
using optional_string = std::optional<std::string>;

optional_int number_not_engaged;
optional_int number_engaged = 42;

printf("%d\n", *number_engaged);

optional_int_vect numbers{{1, 2, 3, 4}};

printf("%d %d\n", numbers.value()[0], numbers.value()[1]);

optional_string ostring = "hello";

printf("%s\n", ostring->c_str());
#endif

return 0; // break here
}

This file was deleted.

0 comments on commit e3dea5c

Please sign in to comment.