[formatters] Add a formatter for libstdc++ optional

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
This commit is contained in:
Walter Erquinigo 2021-11-22 13:46:49 -08:00
parent 9cd7c534e2
commit e3dea5cf0e
9 changed files with 138 additions and 73 deletions

View file

@ -410,6 +410,9 @@ public:
bool
SetData (lldb::SBData &data, lldb::SBError& error);
lldb::SBValue
Clone(const char *new_name);
lldb::addr_t
GetLoadAddress();

View file

@ -8,6 +8,34 @@ import lldb.formatters.Logger
# 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
@ -26,15 +54,15 @@ class StdUnorderedMapSynthProvider:
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
@ -64,12 +92,12 @@ class StdUnorderedMapSynthProvider:
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
@ -115,7 +143,7 @@ class AbstractListSynthProvider:
else:
logger >> "synthetic value is not valid"
return valid
def value(self, node):
logger = lldb.formatters.Logger.Logger()
value = node.GetValueAsUnsigned()
@ -161,7 +189,7 @@ class AbstractListSynthProvider:
# 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
@ -172,14 +200,14 @@ class AbstractListSynthProvider:
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
@ -190,7 +218,7 @@ class AbstractListSynthProvider:
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)
@ -247,7 +275,7 @@ class AbstractListSynthProvider:
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
@ -368,7 +396,7 @@ class StdVectorSynthProvider:
self.count = 0
except:
pass
return False
return False
class StdVBoolImplementation(object):

View file

@ -246,6 +246,12 @@ public:
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.

View file

@ -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);
@ -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 &));

View file

@ -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(
@ -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(

View file

@ -1,6 +1,4 @@
CXX_SOURCES := main.cpp
USE_LIBCPP := 1
CXXFLAGS_EXTRAS := -std=c++17 -fno-exceptions
include Makefile.rules

View file

@ -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(
@ -45,7 +34,7 @@ class LibcxxOptionalDataFormatterTestCase(TestBase):
## 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)
@ -71,3 +60,21 @@ class LibcxxOptionalDataFormatterTestCase(TestBase):
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)

View file

@ -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
}

View file

@ -1,42 +0,0 @@
#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 _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
}