1"""
2Test lldb data formatter subsystem.
3"""
4
5from __future__ import print_function
6
7
8import lldb
9from lldbsuite.test.decorators import *
10from lldbsuite.test.lldbtest import *
11from lldbsuite.test import lldbutil
12
13
14class PythonSynthDataFormatterTestCase(TestBase):
15
16    mydir = TestBase.compute_mydir(__file__)
17
18    def test_with_run_command(self):
19        """Test data formatter commands."""
20        self.build()
21        self.data_formatter_commands()
22
23    def test_rdar10960550_with_run_command(self):
24        """Test data formatter commands."""
25        self.build()
26        self.rdar10960550_formatter_commands()
27
28    def setUp(self):
29        # Call super's setUp().
30        TestBase.setUp(self)
31        # Find the line number to break at.
32        self.line = line_number('main.cpp', '// Set break point at this line.')
33        self.line2 = line_number('main.cpp',
34                                 '// Set cast break point at this line.')
35        self.line3 = line_number(
36            'main.cpp', '// Set second cast break point at this line.')
37
38    def data_formatter_commands(self):
39        """Test using Python synthetic children provider."""
40
41        _, process, thread, _ = lldbutil.run_to_line_breakpoint(
42            self, lldb.SBFileSpec("main.cpp"), self.line)
43
44        # This is the function to remove the custom formats in order to have a
45        # clean slate for the next test case.
46        def cleanup():
47            self.runCmd('type format clear', check=False)
48            self.runCmd('type summary clear', check=False)
49            self.runCmd('type filter clear', check=False)
50            self.runCmd('type synth clear', check=False)
51
52        # Execute the cleanup function during test case tear down.
53        self.addTearDownHook(cleanup)
54
55        # print the f00_1 variable without a synth
56        self.expect("frame variable f00_1",
57                    substrs=['a = 1',
58                             'b = 2',
59                             'r = 34'])
60
61        # now set up the synth
62        self.runCmd("script from fooSynthProvider import *")
63        self.runCmd("type synth add -l fooSynthProvider foo")
64        self.runCmd("type synth add -l wrapfooSynthProvider wrapfoo")
65        self.expect("type synthetic list foo", substrs=['fooSynthProvider'])
66
67        # note that the value of fake_a depends on target byte order
68        if process.GetByteOrder() == lldb.eByteOrderLittle:
69            fake_a_val = 0x02000000
70        else:
71            fake_a_val = 0x00000100
72
73        # check that we get the two real vars and the fake_a variables
74        self.expect(
75            "frame variable f00_1",
76            substrs=[
77                'a = 1',
78                'fake_a = %d' % fake_a_val,
79                'r = 34',
80            ])
81
82        # check that we do not get the extra vars
83        self.expect("frame variable f00_1", matching=False,
84                    substrs=['b = 2'])
85
86        # check access to members by name
87        self.expect('frame variable f00_1.fake_a',
88                    substrs=['%d' % fake_a_val])
89
90        # check access to members by index
91        self.expect('frame variable f00_1[1]',
92                    substrs=['%d' % fake_a_val])
93
94        # put synthetic children in summary in several combinations
95        self.runCmd(
96            "type summary add --summary-string \"fake_a=${svar.fake_a}\" foo")
97        self.expect('frame variable f00_1',
98                    substrs=['fake_a=%d' % fake_a_val])
99        self.runCmd(
100            "type summary add --summary-string \"fake_a=${svar[1]}\" foo")
101        self.expect('frame variable f00_1',
102                    substrs=['fake_a=%d' % fake_a_val])
103
104        # clear the summary
105        self.runCmd("type summary delete foo")
106
107        # check that the caching does not span beyond the stopoint
108        self.runCmd("n")
109
110        if process.GetByteOrder() == lldb.eByteOrderLittle:
111            fake_a_val = 0x02000000
112        else:
113            fake_a_val = 0x00000200
114
115        self.expect(
116            "frame variable f00_1",
117            substrs=[
118                'a = 2',
119                'fake_a = %d' % fake_a_val,
120                'r = 34',
121            ])
122
123        # check that altering the object also alters fake_a
124        self.runCmd("expr f00_1.a = 280")
125
126        if process.GetByteOrder() == lldb.eByteOrderLittle:
127            fake_a_val = 0x02000001
128        else:
129            fake_a_val = 0x00011800
130
131        self.expect(
132            "frame variable f00_1",
133            substrs=[
134                'a = 280',
135                'fake_a = %d' % fake_a_val,
136                'r = 34',
137            ])
138
139        # check that expanding a pointer does the right thing
140        if process.GetByteOrder() == lldb.eByteOrderLittle:
141            fake_a_val = 0x0d000000
142        else:
143            fake_a_val = 0x00000c00
144
145        self.expect(
146            "frame variable --ptr-depth 1 f00_ptr",
147            substrs=[
148                'a = 12',
149                'fake_a = %d' % fake_a_val,
150                'r = 45',
151            ])
152        self.expect(
153            "frame variable --ptr-depth 1 wrapper",
154            substrs=[
155                'a = 12',
156                'fake_a = %d' % fake_a_val,
157                'r = 45',
158            ])
159
160        # now add a filter.. it should fail
161        self.expect("type filter add foo --child b --child j", error=True,
162                    substrs=['cannot add'])
163
164        # we get the synth again..
165        self.expect('frame variable f00_1', matching=False,
166                    substrs=['b = 1',
167                             'j = 17'])
168        self.expect(
169            "frame variable --ptr-depth 1 f00_ptr",
170            substrs=[
171                'a = 12',
172                'fake_a = %d' % fake_a_val,
173                'r = 45',
174            ])
175        self.expect(
176            "frame variable --ptr-depth 1 wrapper",
177            substrs=[
178                'a = 12',
179                'fake_a = %d' % fake_a_val,
180                'r = 45',
181            ])
182
183        # Test that the custom dereference operator for `wrapfoo` works through
184        # the Python API. The synthetic children provider gets queried at
185        # slightly different times in this case.
186        wrapper_var = thread.GetSelectedFrame().FindVariable('wrapper')
187        foo_var = wrapper_var.Dereference()
188        self.assertEqual(foo_var.GetNumChildren(), 3)
189        self.assertEqual(foo_var.GetChildAtIndex(0).GetName(), 'a')
190        self.assertEqual(foo_var.GetChildAtIndex(1).GetName(), 'fake_a')
191        self.assertEqual(foo_var.GetChildAtIndex(2).GetName(), 'r')
192
193        # now delete the synth and add the filter
194        self.runCmd("type synth delete foo")
195        self.runCmd("type synth delete wrapfoo")
196        self.runCmd("type filter add foo --child b --child j")
197
198        self.expect('frame variable f00_1',
199                    substrs=['b = 2',
200                             'j = 18'])
201        self.expect("frame variable --ptr-depth 1 f00_ptr", matching=False,
202                    substrs=['r = 45',
203                             'fake_a = %d' % fake_a_val,
204                             'a = 12'])
205        self.expect("frame variable --ptr-depth 1 wrapper", matching=False,
206                    substrs=['r = 45',
207                             'fake_a = %d' % fake_a_val,
208                             'a = 12'])
209
210        # now add the synth and it should fail
211        self.expect("type synth add -l fooSynthProvider foo", error=True,
212                    substrs=['cannot add'])
213
214        # check the listing
215        self.expect('type synth list', matching=False,
216                    substrs=['foo',
217                             'Python class fooSynthProvider'])
218        self.expect('type filter list',
219                    substrs=['foo',
220                             '.b',
221                             '.j'])
222
223        # delete the filter, add the synth
224        self.runCmd("type filter delete foo")
225        self.runCmd("type synth add -l fooSynthProvider foo")
226
227        self.expect('frame variable f00_1', matching=False,
228                    substrs=['b = 2',
229                             'j = 18'])
230        self.expect(
231            "frame variable --ptr-depth 1 f00_ptr",
232            substrs=[
233                'a = 12',
234                'fake_a = %d' % fake_a_val,
235                'r = 45',
236            ])
237        self.expect(
238            "frame variable --ptr-depth 1 wrapper",
239            substrs=[
240                'a = 12',
241                'fake_a = %d' % fake_a_val,
242                'r = 45',
243            ])
244
245        # check the listing
246        self.expect('type synth list',
247                    substrs=['foo',
248                             'Python class fooSynthProvider'])
249        self.expect('type filter list', matching=False,
250                    substrs=['foo',
251                             '.b',
252                             '.j'])
253
254        # delete the synth and check that we get good output
255        self.runCmd("type synth delete foo")
256
257        self.expect("frame variable f00_1",
258                    substrs=['a = 280',
259                             'b = 2',
260                             'j = 18'])
261
262        self.expect("frame variable f00_1", matching=False,
263                    substrs=['fake_a = '])
264
265    def rdar10960550_formatter_commands(self):
266        """Test that synthetic children persist stoppoints."""
267        self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET)
268
269        # The second breakpoint is on a multi-line expression, so the comment
270        # can't be on the right line...
271        lldbutil.run_break_set_by_file_and_line(
272            self, "main.cpp", self.line2, num_expected_locations=1, loc_exact=False)
273        lldbutil.run_break_set_by_file_and_line(
274            self, "main.cpp", self.line3, num_expected_locations=1, loc_exact=True)
275
276        self.runCmd("run", RUN_SUCCEEDED)
277
278        # The stop reason of the thread should be breakpoint.
279        self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
280                    substrs=['stopped',
281                             'stop reason = breakpoint'])
282
283        # This is the function to remove the custom formats in order to have a
284        # clean slate for the next test case.
285        def cleanup():
286            self.runCmd('type format clear', check=False)
287            self.runCmd('type summary clear', check=False)
288            self.runCmd('type filter clear', check=False)
289            self.runCmd('type synth clear', check=False)
290
291        # Execute the cleanup function during test case tear down.
292        self.addTearDownHook(cleanup)
293
294        self.runCmd("command script import ./ftsp.py --allow-reload")
295        self.runCmd("type synth add -l ftsp.ftsp wrapint")
296
297        # we need to check that the VO is properly updated so that the same synthetic children are reused
298        # but their values change correctly across stop-points - in order to do this, self.runCmd("next")
299        # does not work because it forces a wipe of the stack frame - this is why we are using this more contrived
300        # mechanism to achieve our goal of preserving test_cast as a VO
301        test_cast = self.dbg.GetSelectedTarget().GetProcess(
302        ).GetSelectedThread().GetSelectedFrame().FindVariable('test_cast')
303
304        str_cast = str(test_cast)
305
306        if self.TraceOn():
307            print(str_cast)
308
309        self.assertTrue(str_cast.find('A') != -1, 'could not find A in output')
310        self.assertTrue(str_cast.find('B') != -1, 'could not find B in output')
311        self.assertTrue(str_cast.find('C') != -1, 'could not find C in output')
312        self.assertTrue(str_cast.find('D') != -1, 'could not find D in output')
313        self.assertTrue(
314            str_cast.find("4 = '\\0'") != -1,
315            'could not find item 4 == 0')
316
317        self.dbg.GetSelectedTarget().GetProcess().GetSelectedThread().StepOver()
318
319        str_cast = str(test_cast)
320
321        if self.TraceOn():
322            print(str_cast)
323
324        # we detect that all the values of the child objects have changed - but the counter-generated item
325        # is still fixed at 0 because it is cached - this would fail if update(self): in ftsp returned False
326        # or if synthetic children were not being preserved
327        self.assertTrue(str_cast.find('Q') != -1, 'could not find Q in output')
328        self.assertTrue(str_cast.find('X') != -1, 'could not find X in output')
329        self.assertTrue(str_cast.find('T') != -1, 'could not find T in output')
330        self.assertTrue(str_cast.find('F') != -1, 'could not find F in output')
331        self.assertTrue(
332            str_cast.find("4 = '\\0'") != -1,
333            'could not find item 4 == 0')
334