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