1# encoding: utf-8
2"""
3Test lldb Obj-C exception support.
4"""
5
6
7
8import lldb
9from lldbsuite.test.decorators import *
10from lldbsuite.test.lldbtest import *
11from lldbsuite.test import lldbutil
12
13
14class ObjCExceptionsTestCase(TestBase):
15
16    mydir = TestBase.compute_mydir(__file__)
17
18    def test_objc_exceptions_at_throw(self):
19        self.build()
20
21        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
22        self.assertTrue(target, VALID_TARGET)
23
24        launch_info = lldb.SBLaunchInfo(["a.out", "0"])
25        launch_info.SetLaunchFlags(lldb.eLaunchFlagInheritTCCFromParent)
26        lldbutil.run_to_name_breakpoint(self, "objc_exception_throw", launch_info=launch_info)
27
28        self.expect("thread list",
29            substrs=['stopped', 'stop reason = hit Objective-C exception'])
30
31        self.expect('thread exception', substrs=[
32                '(NSException *) exception = ',
33                '"SomeReason"',
34            ])
35
36        target = self.dbg.GetSelectedTarget()
37        thread = target.GetProcess().GetSelectedThread()
38        frame = thread.GetSelectedFrame()
39
40        opts = lldb.SBVariablesOptions()
41        opts.SetIncludeRecognizedArguments(True)
42        variables = frame.GetVariables(opts)
43
44        self.assertEqual(variables.GetSize(), 1)
45        self.assertEqual(variables.GetValueAtIndex(0).name, "exception")
46        self.assertEqual(variables.GetValueAtIndex(0).GetValueType(), lldb.eValueTypeVariableArgument)
47
48        lldbutil.run_to_source_breakpoint(self, "// Set break point at this line.", lldb.SBFileSpec("main.mm"), launch_info=launch_info)
49
50        self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
51                    substrs=['stopped', 'stop reason = breakpoint'])
52
53        target = self.dbg.GetSelectedTarget()
54        thread = target.GetProcess().GetSelectedThread()
55        frame = thread.GetSelectedFrame()
56
57        # No exception being currently thrown/caught at this point
58        self.assertFalse(thread.GetCurrentException().IsValid())
59        self.assertFalse(thread.GetCurrentExceptionBacktrace().IsValid())
60
61        self.expect(
62            'frame variable e1',
63            substrs=[
64                '(NSException *) e1 = ',
65                '"SomeReason"'
66            ])
67
68        self.expect(
69            'frame variable --dynamic-type no-run-target *e1',
70            substrs=[
71                '(NSException) *e1 = ',
72                'name = ', '"ExceptionName"',
73                'reason = ', '"SomeReason"',
74                'userInfo = ', '1 key/value pair',
75                'reserved = ', 'nil',
76            ])
77
78        e1 = frame.FindVariable("e1")
79        self.assertTrue(e1)
80        self.assertEqual(e1.type.name, "NSException *")
81        self.assertEqual(e1.GetSummary(), '"SomeReason"')
82        self.assertEqual(e1.GetChildMemberWithName("name").description, "ExceptionName")
83        self.assertEqual(e1.GetChildMemberWithName("reason").description, "SomeReason")
84        userInfo = e1.GetChildMemberWithName("userInfo").dynamic
85        self.assertEqual(userInfo.summary, "1 key/value pair")
86        self.assertEqual(userInfo.GetChildAtIndex(0).GetChildAtIndex(0).description, "some_key")
87        self.assertEqual(userInfo.GetChildAtIndex(0).GetChildAtIndex(1).description, "some_value")
88        self.assertEqual(e1.GetChildMemberWithName("reserved").description, "<nil>")
89
90        self.expect(
91            'frame variable e2',
92            substrs=[
93                '(NSException *) e2 = ',
94                '"SomeReason"'
95            ])
96
97        self.expect(
98            'frame variable --dynamic-type no-run-target *e2',
99            substrs=[
100                '(NSException) *e2 = ',
101                'name = ', '"ThrownException"',
102                'reason = ', '"SomeReason"',
103                'userInfo = ', '1 key/value pair',
104                'reserved = ',
105            ])
106
107        e2 = frame.FindVariable("e2")
108        self.assertTrue(e2)
109        self.assertEqual(e2.type.name, "NSException *")
110        self.assertEqual(e2.GetSummary(), '"SomeReason"')
111        self.assertEqual(e2.GetChildMemberWithName("name").description, "ThrownException")
112        self.assertEqual(e2.GetChildMemberWithName("reason").description, "SomeReason")
113        userInfo = e2.GetChildMemberWithName("userInfo").dynamic
114        self.assertEqual(userInfo.summary, "1 key/value pair")
115        self.assertEqual(userInfo.GetChildAtIndex(0).GetChildAtIndex(0).description, "some_key")
116        self.assertEqual(userInfo.GetChildAtIndex(0).GetChildAtIndex(1).description, "some_value")
117        reserved = e2.GetChildMemberWithName("reserved").dynamic
118        self.assertGreater(reserved.num_children, 0)
119        callStackReturnAddresses = [reserved.GetChildAtIndex(i).GetChildAtIndex(1) for i in range(0, reserved.GetNumChildren())
120                if reserved.GetChildAtIndex(i).GetChildAtIndex(0).description == "callStackReturnAddresses"][0].dynamic
121        children = [callStackReturnAddresses.GetChildAtIndex(i) for i in range(0, callStackReturnAddresses.num_children)]
122
123        pcs = [i.unsigned for i in children]
124        names = [target.ResolveSymbolContextForAddress(lldb.SBAddress(pc, target), lldb.eSymbolContextSymbol).GetSymbol().name for pc in pcs]
125        for n in ["objc_exception_throw", "foo(int)", "main"]:
126            self.assertIn(n, names, "%s is in the exception backtrace (%s)" % (n, names))
127
128    def test_objc_exceptions_at_abort(self):
129        self.build()
130
131        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
132        self.assertTrue(target, VALID_TARGET)
133
134        self.runCmd("run 0")
135
136        # We should be stopped at pthread_kill because of an unhandled exception
137        self.expect("thread list",
138            substrs=['stopped', 'stop reason = signal SIGABRT'])
139
140        self.expect('thread exception', substrs=[
141                '(NSException *) exception = ',
142                '"SomeReason"',
143                'libobjc.A.dylib`objc_exception_throw',
144                'a.out`foo', 'at main.mm:16',
145                'a.out`rethrow', 'at main.mm:27',
146                'a.out`main',
147            ])
148
149        process = self.dbg.GetSelectedTarget().process
150        thread = process.GetSelectedThread()
151
152        # There is an exception being currently processed at this point
153        self.assertTrue(thread.GetCurrentException().IsValid())
154        self.assertTrue(thread.GetCurrentExceptionBacktrace().IsValid())
155
156        history_thread = thread.GetCurrentExceptionBacktrace()
157        self.assertGreaterEqual(history_thread.num_frames, 4)
158        for n in ["objc_exception_throw", "foo(int)", "rethrow(int)", "main"]:
159            self.assertEqual(len([f for f in history_thread.frames if f.GetFunctionName() == n]), 1)
160
161        self.runCmd("kill")
162
163        self.runCmd("run 1")
164        # We should be stopped at pthread_kill because of an unhandled exception
165        self.expect("thread list",
166            substrs=['stopped', 'stop reason = signal SIGABRT'])
167
168        self.expect('thread exception', substrs=[
169                '(MyCustomException *) exception = ',
170                'libobjc.A.dylib`objc_exception_throw',
171                'a.out`foo', 'at main.mm:18',
172                'a.out`rethrow', 'at main.mm:27',
173                'a.out`main',
174            ])
175
176        process = self.dbg.GetSelectedTarget().process
177        thread = process.GetSelectedThread()
178
179        history_thread = thread.GetCurrentExceptionBacktrace()
180        self.assertGreaterEqual(history_thread.num_frames, 4)
181        for n in ["objc_exception_throw", "foo(int)", "rethrow(int)", "main"]:
182            self.assertEqual(len([f for f in history_thread.frames if f.GetFunctionName() == n]), 1)
183
184    def test_cxx_exceptions_at_abort(self):
185        self.build()
186
187        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
188        self.assertTrue(target, VALID_TARGET)
189
190        self.runCmd("run 2")
191
192        # We should be stopped at pthread_kill because of an unhandled exception
193        self.expect("thread list",
194            substrs=['stopped', 'stop reason = signal SIGABRT'])
195
196        self.expect('thread exception', substrs=['exception ='])
197
198        process = self.dbg.GetSelectedTarget().process
199        thread = process.GetSelectedThread()
200
201        self.assertTrue(thread.GetCurrentException().IsValid())
202
203        # C++ exception backtraces are not exposed in the API (yet).
204        self.assertFalse(thread.GetCurrentExceptionBacktrace().IsValid())
205