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