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