[lldb/DataFormatters] Fix the $$deference$$ synthetic child

Summary:
The ValueObject code checks for a special `$$dereference$$` synthetic
child to allow formatter providers to implement a natural
dereferencing behavior in `frame variable` for objects like smart
pointers.

This support was broken when used directly throught the Python API and
not trhough `frame variable`. The reason is that
SBFrame.FindVariable() will return by default the synthetic variable
if it exists, while `frame variable` will not do this eagerly. The
code in `ValueObject::Dereference()` accounted for the latter but not
for the former. The fix is trivial. The test change includes
additional covergage for the already-working bahevior as it wasn't
covered by the testsuite before.

This commit also adds a short piece of documentatione explaining that
it is possible (even advisable) to provide this synthetic child
outstide of the range of the normal children.

Reviewers: jingham

Subscribers: lldb-commits

Tags: #lldb

Differential Revision: https://reviews.llvm.org/D73053
This commit is contained in:
Fred Riss
2020-01-20 08:39:25 -08:00
parent fd109308a7
commit 0478eadf73
5 changed files with 89 additions and 15 deletions

View File

@@ -846,7 +846,7 @@ adheres to a given interface (the word is italicized because Python has no
explicit notion of interface, by that word we mean a given set of methods must
be implemented by the Python class):
::
.. code-block:: python
class SyntheticChildrenProvider:
def __init__(self, valobj, internal_dict):
@@ -885,7 +885,28 @@ returning default no-children responses.
If a synthetic child provider supplies a special child named
``$$dereference$$`` then it will be used when evaluating ``operator *`` and
``operator ->`` in the frame variable command and related SB API functions.
``operator ->`` in the frame variable command and related SB API
functions. It is possible to declare this synthetic child without
including it in the range of children displayed by LLDB. For example,
this subset of a synthetic children provider class would allow the
synthetic value to be dereferenced without actually showing any
synthtic children in the UI:
.. code-block:: python
class SyntheticChildrenProvider:
[...]
def num_children(self):
return 0
def get_child_index(self, name):
if name == '$$dereference$$':
return 0
return -1
def get_child_at_index(self, index):
if index == 0:
return <valobj resulting from dereference>
return None
For examples of how synthetic children are created, you are encouraged to look
at examples/synthetic in the LLDB trunk. Please, be aware that the code in

View File

@@ -38,19 +38,9 @@ class PythonSynthDataFormatterTestCase(TestBase):
def data_formatter_commands(self):
"""Test using Python synthetic children provider."""
self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
lldbutil.run_break_set_by_file_and_line(
self, "main.cpp", self.line, num_expected_locations=1, loc_exact=True)
self.runCmd("run", RUN_SUCCEEDED)
process = self.dbg.GetSelectedTarget().GetProcess()
# The stop reason of the thread should be breakpoint.
self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
substrs=['stopped',
'stop reason = breakpoint'])
_, process, thread, _ = lldbutil.run_to_line_breakpoint(
self, lldb.SBFileSpec("main.cpp"), self.line)
# This is the function to remove the custom formats in order to have a
# clean slate for the next test case.
@@ -72,6 +62,7 @@ class PythonSynthDataFormatterTestCase(TestBase):
# now set up the synth
self.runCmd("script from fooSynthProvider import *")
self.runCmd("type synth add -l fooSynthProvider foo")
self.runCmd("type synth add -l wrapfooSynthProvider wrapfoo")
self.expect("type synthetic list foo", substrs=['fooSynthProvider'])
# note that the value of fake_a depends on target byte order
@@ -147,6 +138,10 @@ class PythonSynthDataFormatterTestCase(TestBase):
substrs=['r = 45',
'fake_a = %d' % fake_a_val,
'a = 12'])
self.expect("frame variable --ptr-depth 1 wrapper",
substrs=['r = 45',
'fake_a = %d' % fake_a_val,
'a = 12'])
# now add a filter.. it should fail
self.expect("type filter add foo --child b --child j", error=True,
@@ -160,9 +155,24 @@ class PythonSynthDataFormatterTestCase(TestBase):
substrs=['r = 45',
'fake_a = %d' % fake_a_val,
'a = 12'])
self.expect("frame variable --ptr-depth 1 wrapper",
substrs=['r = 45',
'fake_a = %d' % fake_a_val,
'a = 12'])
# Test that the custom dereference operator for `wrapfoo` works through
# the Python API. The synthetic children provider gets queried at
# slightly different times in this case.
wrapper_var = thread.GetSelectedFrame().FindVariable('wrapper')
foo_var = wrapper_var.Dereference()
self.assertEqual(foo_var.GetNumChildren(), 3)
self.assertEqual(foo_var.GetChildAtIndex(0).GetName(), 'a')
self.assertEqual(foo_var.GetChildAtIndex(1).GetName(), 'fake_a')
self.assertEqual(foo_var.GetChildAtIndex(2).GetName(), 'r')
# now delete the synth and add the filter
self.runCmd("type synth delete foo")
self.runCmd("type synth delete wrapfoo")
self.runCmd("type filter add foo --child b --child j")
self.expect('frame variable f00_1',
@@ -172,6 +182,10 @@ class PythonSynthDataFormatterTestCase(TestBase):
substrs=['r = 45',
'fake_a = %d' % fake_a_val,
'a = 12'])
self.expect("frame variable --ptr-depth 1 wrapper", matching=False,
substrs=['r = 45',
'fake_a = %d' % fake_a_val,
'a = 12'])
# now add the synth and it should fail
self.expect("type synth add -l fooSynthProvider foo", error=True,
@@ -197,6 +211,10 @@ class PythonSynthDataFormatterTestCase(TestBase):
substrs=['r = 45',
'fake_a = %d' % fake_a_val,
'a = 12'])
self.expect("frame variable --ptr-depth 1 wrapper",
substrs=['r = 45',
'fake_a = %d' % fake_a_val,
'a = 12'])
# check the listing
self.expect('type synth list',

View File

@@ -28,3 +28,29 @@ class fooSynthProvider:
def update(self):
return True
class wrapfooSynthProvider:
def __init__(self, valobj, dict):
self.valobj = valobj
def num_children(self):
return 1
def get_child_at_index(self, index):
if index == 0:
return self.valobj.GetChildMemberWithName('ptr')
if index == 1:
return self.valobj.GetChildMemberWithName('ptr').Dereference()
return None
def get_child_index(self, name):
if name == 'ptr':
return 0
if name == '$$dereference$$':
return 1
return -1
def update(self):
return True

View File

@@ -46,11 +46,17 @@ struct wrapint
wrapint(int X) : x(X) {}
};
struct wrapfoo
{
foo *ptr;
};
int main()
{
foo f00_1(1);
foo *f00_ptr = new foo(12);
wrapfoo wrapper{f00_ptr};
f00_1.a++; // Set break point at this line.
wrapint test_cast('A' +

View File

@@ -2859,6 +2859,9 @@ ValueObjectSP ValueObject::Dereference(Status &error) {
GetSyntheticValue()
->GetChildMemberWithName(ConstString("$$dereference$$"), true)
.get();
} else if (IsSynthetic()) {
m_deref_valobj =
GetChildMemberWithName(ConstString("$$dereference$$"), true).get();
}
if (m_deref_valobj) {