[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:
parent
9cd7c534e2
commit
e3dea5cf0e
|
@ -410,6 +410,9 @@ public:
|
|||
bool
|
||||
SetData (lldb::SBData &data, lldb::SBError& error);
|
||||
|
||||
lldb::SBValue
|
||||
Clone(const char *new_name);
|
||||
|
||||
lldb::addr_t
|
||||
GetLoadAddress();
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 &));
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
@ -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)
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in a new issue