From 0478eadf73c191199cba12c85785cfafb8bfa174 Mon Sep 17 00:00:00 2001 From: Fred Riss Date: Mon, 20 Jan 2020 08:39:25 -0800 Subject: [PATCH] [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 --- lldb/docs/use/variable.rst | 25 +++++++++++-- .../TestDataFormatterPythonSynth.py | 42 +++++++++++++++------- .../fooSynthProvider.py | 26 ++++++++++++++ .../data-formatter-python-synth/main.cpp | 8 ++++- lldb/source/Core/ValueObject.cpp | 3 ++ 5 files changed, 89 insertions(+), 15 deletions(-) diff --git a/lldb/docs/use/variable.rst b/lldb/docs/use/variable.rst index 13a56637ece..4e3f25eb6a4 100644 --- a/lldb/docs/use/variable.rst +++ b/lldb/docs/use/variable.rst @@ -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 + 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 diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-python-synth/TestDataFormatterPythonSynth.py b/lldb/packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-python-synth/TestDataFormatterPythonSynth.py index 5f908f76b0a..9d4759100ce 100644 --- a/lldb/packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-python-synth/TestDataFormatterPythonSynth.py +++ b/lldb/packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-python-synth/TestDataFormatterPythonSynth.py @@ -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', diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-python-synth/fooSynthProvider.py b/lldb/packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-python-synth/fooSynthProvider.py index 45fb00468e0..6ee749b720b 100644 --- a/lldb/packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-python-synth/fooSynthProvider.py +++ b/lldb/packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-python-synth/fooSynthProvider.py @@ -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 diff --git a/lldb/packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-python-synth/main.cpp b/lldb/packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-python-synth/main.cpp index f45a2abfb9f..5cf4b634592 100644 --- a/lldb/packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-python-synth/main.cpp +++ b/lldb/packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-python-synth/main.cpp @@ -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' + diff --git a/lldb/source/Core/ValueObject.cpp b/lldb/source/Core/ValueObject.cpp index 1dd9a6cf62c..1e553596fcf 100644 --- a/lldb/source/Core/ValueObject.cpp +++ b/lldb/source/Core/ValueObject.cpp @@ -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) { -- 2.11.0