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