1"""Test stepping through ObjC method dispatch in various forms."""
2
3from __future__ import print_function
4
5
6import lldb
7from lldbsuite.test.decorators import *
8from lldbsuite.test.lldbtest import *
9from lldbsuite.test import lldbutil
10
11
12class TestObjCStepping(TestBase):
13
14    mydir = TestBase.compute_mydir(__file__)
15
16    def setUp(self):
17        # Call super's setUp().
18        TestBase.setUp(self)
19        # Find the line numbers that we will step to in main:
20        self.main_source = "stepping-tests.m"
21        self.source_randomMethod_line = line_number(
22            self.main_source, '// Source randomMethod start line.')
23        self.sourceBase_randomMethod_line = line_number(
24            self.main_source, '// SourceBase randomMethod start line.')
25        self.source_returnsStruct_start_line = line_number(
26            self.main_source, '// Source returnsStruct start line.')
27        self.sourceBase_returnsStruct_start_line = line_number(
28            self.main_source, '// SourceBase returnsStruct start line.')
29        self.stepped_past_nil_line = line_number(
30            self.main_source, '// Step over nil should stop here.')
31
32    @add_test_categories(['pyapi', 'basic_process'])
33    def test_with_python_api(self):
34        """Test stepping through ObjC method dispatch in various forms."""
35        self.build()
36        exe = self.getBuildArtifact("a.out")
37
38        target = self.dbg.CreateTarget(exe)
39        self.assertTrue(target, VALID_TARGET)
40
41        self.main_source_spec = lldb.SBFileSpec(self.main_source)
42
43        breakpoints_to_disable = []
44
45        break1 = target.BreakpointCreateBySourceRegex(
46            "// Set first breakpoint here.", self.main_source_spec)
47        self.assertTrue(break1, VALID_BREAKPOINT)
48        breakpoints_to_disable.append(break1)
49
50        break2 = target.BreakpointCreateBySourceRegex(
51            "// Set second breakpoint here.", self.main_source_spec)
52        self.assertTrue(break2, VALID_BREAKPOINT)
53        breakpoints_to_disable.append(break2)
54
55        break3 = target.BreakpointCreateBySourceRegex(
56            '// Set third breakpoint here.', self.main_source_spec)
57        self.assertTrue(break3, VALID_BREAKPOINT)
58        breakpoints_to_disable.append(break3)
59
60        break4 = target.BreakpointCreateBySourceRegex(
61            '// Set fourth breakpoint here.', self.main_source_spec)
62        self.assertTrue(break4, VALID_BREAKPOINT)
63        breakpoints_to_disable.append(break4)
64
65        break5 = target.BreakpointCreateBySourceRegex(
66            '// Set fifth breakpoint here.', self.main_source_spec)
67        self.assertTrue(break5, VALID_BREAKPOINT)
68        breakpoints_to_disable.append(break5)
69
70        break_returnStruct_call_super = target.BreakpointCreateBySourceRegex(
71            '// Source returnsStruct call line.', self.main_source_spec)
72        self.assertTrue(break_returnStruct_call_super, VALID_BREAKPOINT)
73        breakpoints_to_disable.append(break_returnStruct_call_super)
74
75        break_step_nil = target.BreakpointCreateBySourceRegex(
76            '// Set nil step breakpoint here.', self.main_source_spec)
77        self.assertTrue(break_step_nil, VALID_BREAKPOINT)
78
79        # Now launch the process, and do not stop at entry point.
80        process = target.LaunchSimple(
81            None, None, self.get_process_working_directory())
82
83        self.assertTrue(process, PROCESS_IS_VALID)
84
85        # The stop reason of the thread should be breakpoint.
86        threads = lldbutil.get_threads_stopped_at_breakpoint(process, break1)
87        if len(threads) != 1:
88            self.fail("Failed to stop at breakpoint 1.")
89
90        thread = threads[0]
91
92        mySource = thread.GetFrameAtIndex(0).FindVariable("mySource")
93        self.assertTrue(mySource, "Found mySource local variable.")
94        mySource_isa = mySource.GetChildMemberWithName("isa")
95        self.assertTrue(mySource_isa, "Found mySource->isa local variable.")
96        className = mySource_isa.GetSummary()
97
98        if self.TraceOn():
99            print(mySource_isa)
100
101        # Lets delete mySource so we can check that after stepping a child variable
102        # with no parent persists and is useful.
103        del (mySource)
104
105        # Now step in, that should leave us in the Source randomMethod:
106        thread.StepInto()
107        line_number = thread.GetFrameAtIndex(0).GetLineEntry().GetLine()
108        self.assertEqual(
109            line_number, self.source_randomMethod_line,
110            "Stepped into Source randomMethod.")
111
112        # Now step in again, through the super call, and that should leave us
113        # in the SourceBase randomMethod:
114        thread.StepInto()
115        line_number = thread.GetFrameAtIndex(0).GetLineEntry().GetLine()
116        self.assertEqual(
117            line_number, self.sourceBase_randomMethod_line,
118            "Stepped through super into SourceBase randomMethod.")
119
120        threads = lldbutil.continue_to_breakpoint(process, break2)
121        self.assertEqual(
122            len(threads), 1,
123            "Continued to second breakpoint in main.")
124
125        # Again, step in twice gets us to a stret method and a stret super
126        # call:
127        thread = threads[0]
128        thread.StepInto()
129        line_number = thread.GetFrameAtIndex(0).GetLineEntry().GetLine()
130        self.assertEqual(
131            line_number, self.source_returnsStruct_start_line,
132            "Stepped into Source returnsStruct.")
133
134        threads = lldbutil.continue_to_breakpoint(
135            process, break_returnStruct_call_super)
136        self.assertEqual(
137            len(threads), 1,
138            "Stepped to the call super line in Source returnsStruct.")
139        thread = threads[0]
140
141        thread.StepInto()
142        line_number = thread.GetFrameAtIndex(0).GetLineEntry().GetLine()
143        self.assertEqual(
144            line_number, self.sourceBase_returnsStruct_start_line,
145            "Stepped through super into SourceBase returnsStruct.")
146
147        # Cool now continue to get past the call that initializes the Observer, and then do our steps in again to see that
148        # we can find our way when we're stepping through a KVO swizzled
149        # object.
150
151        threads = lldbutil.continue_to_breakpoint(process, break3)
152        self.assertEqual(
153            len(threads), 1,
154            "Continued to third breakpoint in main, our object should now be swizzled.")
155
156        newClassName = mySource_isa.GetSummary()
157
158        if self.TraceOn():
159            print("className is %s, newClassName is %s" % (className, newClassName))
160            print(mySource_isa)
161
162        self.assertTrue(
163            newClassName != className,
164            "The isa did indeed change, swizzled!")
165
166        # Now step in, that should leave us in the Source randomMethod:
167        thread = threads[0]
168        thread.StepInto()
169        line_number = thread.GetFrameAtIndex(0).GetLineEntry().GetLine()
170        self.assertEqual(
171            line_number, self.source_randomMethod_line,
172            "Stepped into Source randomMethod in swizzled object.")
173
174        # Now step in again, through the super call, and that should leave us
175        # in the SourceBase randomMethod:
176        thread.StepInto()
177        line_number = thread.GetFrameAtIndex(0).GetLineEntry().GetLine()
178        self.assertEqual(
179            line_number, self.sourceBase_randomMethod_line,
180            "Stepped through super into SourceBase randomMethod in swizzled object.")
181
182        threads = lldbutil.continue_to_breakpoint(process, break4)
183        self.assertEqual(
184            len(threads), 1,
185            "Continued to fourth breakpoint in main.")
186        thread = threads[0]
187
188        # Again, step in twice gets us to a stret method and a stret super
189        # call:
190        thread.StepInto()
191        line_number = thread.GetFrameAtIndex(0).GetLineEntry().GetLine()
192        self.assertEqual(
193            line_number, self.source_returnsStruct_start_line,
194            "Stepped into Source returnsStruct in swizzled object.")
195
196        threads = lldbutil.continue_to_breakpoint(
197            process, break_returnStruct_call_super)
198        self.assertEqual(
199            len(threads), 1,
200            "Stepped to the call super line in Source returnsStruct - second time.")
201        thread = threads[0]
202
203        thread.StepInto()
204        line_number = thread.GetFrameAtIndex(0).GetLineEntry().GetLine()
205        self.assertEqual(
206            line_number, self.sourceBase_returnsStruct_start_line,
207            "Stepped through super into SourceBase returnsStruct in swizzled object.")
208
209        for bkpt in breakpoints_to_disable:
210            bkpt.SetEnabled(False)
211
212        threads = lldbutil.continue_to_breakpoint(process, break_step_nil)
213        self.assertEqual(len(threads), 1, "Continued to step nil breakpoint.")
214
215        thread.StepInto()
216        line_number = thread.GetFrameAtIndex(0).GetLineEntry().GetLine()
217        self.assertEqual(
218            line_number, self.stepped_past_nil_line,
219            "Step in over dispatch to nil stepped over.")
220