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