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        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.assertTrue(n in names, "%s is in the exception backtrace (%s)" % (n, names))
127
128    @skipUnlessDarwin
129    def test_objc_exceptions_at_abort(self):
130        self.build()
131
132        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
133        self.assertTrue(target, VALID_TARGET)
134
135        self.runCmd("run 0")
136
137        # We should be stopped at pthread_kill because of an unhandled exception
138        self.expect("thread list",
139            substrs=['stopped', 'stop reason = signal SIGABRT'])
140
141        self.expect('thread exception', substrs=[
142                '(NSException *) exception = ',
143                '"SomeReason"',
144                'libobjc.A.dylib`objc_exception_throw',
145                'a.out`foo', 'at main.mm:16',
146                'a.out`rethrow', 'at main.mm:27',
147                'a.out`main',
148            ])
149
150        process = self.dbg.GetSelectedTarget().process
151        thread = process.GetSelectedThread()
152
153        # There is an exception being currently processed at this point
154        self.assertTrue(thread.GetCurrentException().IsValid())
155        self.assertTrue(thread.GetCurrentExceptionBacktrace().IsValid())
156
157        history_thread = thread.GetCurrentExceptionBacktrace()
158        self.assertGreaterEqual(history_thread.num_frames, 4)
159        for n in ["objc_exception_throw", "foo(int)", "rethrow(int)", "main"]:
160            self.assertEqual(len([f for f in history_thread.frames if f.GetFunctionName() == n]), 1)
161
162        self.runCmd("kill")
163
164        self.runCmd("run 1")
165        # We should be stopped at pthread_kill because of an unhandled exception
166        self.expect("thread list",
167            substrs=['stopped', 'stop reason = signal SIGABRT'])
168
169        self.expect('thread exception', substrs=[
170                '(MyCustomException *) exception = ',
171                'libobjc.A.dylib`objc_exception_throw',
172                'a.out`foo', 'at main.mm:18',
173                'a.out`rethrow', 'at main.mm:27',
174                'a.out`main',
175            ])
176
177        process = self.dbg.GetSelectedTarget().process
178        thread = process.GetSelectedThread()
179
180        history_thread = thread.GetCurrentExceptionBacktrace()
181        self.assertGreaterEqual(history_thread.num_frames, 4)
182        for n in ["objc_exception_throw", "foo(int)", "rethrow(int)", "main"]:
183            self.assertEqual(len([f for f in history_thread.frames if f.GetFunctionName() == n]), 1)
184
185    @skipUnlessDarwin
186    def test_cxx_exceptions_at_abort(self):
187        self.build()
188
189        target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
190        self.assertTrue(target, VALID_TARGET)
191
192        self.runCmd("run 2")
193
194        # We should be stopped at pthread_kill because of an unhandled exception
195        self.expect("thread list",
196            substrs=['stopped', 'stop reason = signal SIGABRT'])
197
198        self.expect('thread exception', substrs=['exception ='])
199
200        process = self.dbg.GetSelectedTarget().process
201        thread = process.GetSelectedThread()
202
203        self.assertTrue(thread.GetCurrentException().IsValid())
204
205        # C++ exception backtraces are not exposed in the API (yet).
206        self.assertFalse(thread.GetCurrentExceptionBacktrace().IsValid())
207