| # encoding: utf-8 |
| """ |
| Test lldb Obj-C exception support. |
| """ |
| |
| |
| import lldb |
| from lldbsuite.test.decorators import * |
| from lldbsuite.test.lldbtest import * |
| from lldbsuite.test import lldbutil |
| |
| |
| class ObjCExceptionsTestCase(TestBase): |
| @skipIf(compiler="clang", compiler_version=["<", "13.0"]) |
| def test_objc_exceptions_at_throw(self): |
| self.build() |
| |
| target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) |
| self.assertTrue(target, VALID_TARGET) |
| |
| launch_info = lldb.SBLaunchInfo(["a.out", "0"]) |
| launch_info.SetLaunchFlags(lldb.eLaunchFlagInheritTCCFromParent) |
| lldbutil.run_to_name_breakpoint( |
| self, "objc_exception_throw", launch_info=launch_info |
| ) |
| |
| self.expect( |
| "thread list", |
| substrs=["stopped", "stop reason = hit Objective-C exception"], |
| ) |
| |
| self.expect( |
| "thread exception", |
| substrs=[ |
| "(NSException *) exception = ", |
| '"SomeReason"', |
| ], |
| ) |
| |
| target = self.dbg.GetSelectedTarget() |
| thread = target.GetProcess().GetSelectedThread() |
| frame = thread.GetSelectedFrame() |
| |
| opts = lldb.SBVariablesOptions() |
| opts.SetIncludeRecognizedArguments(True) |
| variables = frame.GetVariables(opts) |
| |
| self.assertEqual(variables.GetSize(), 1) |
| self.assertEqual(variables.GetValueAtIndex(0).name, "exception") |
| self.assertEqual( |
| variables.GetValueAtIndex(0).GetValueType(), lldb.eValueTypeVariableArgument |
| ) |
| |
| lldbutil.run_to_source_breakpoint( |
| self, |
| "// Set break point at this line.", |
| lldb.SBFileSpec("main.mm"), |
| launch_info=launch_info, |
| ) |
| |
| self.expect( |
| "thread list", |
| STOPPED_DUE_TO_BREAKPOINT, |
| substrs=["stopped", "stop reason = breakpoint"], |
| ) |
| |
| target = self.dbg.GetSelectedTarget() |
| thread = target.GetProcess().GetSelectedThread() |
| frame = thread.GetSelectedFrame() |
| |
| # No exception being currently thrown/caught at this point |
| self.assertFalse(thread.GetCurrentException().IsValid()) |
| self.assertFalse(thread.GetCurrentExceptionBacktrace().IsValid()) |
| |
| self.expect( |
| "frame variable e1", substrs=["(NSException *) e1 = ", '"SomeReason"'] |
| ) |
| |
| self.expect( |
| "frame variable *e1", |
| substrs=[ |
| "(NSException) *e1 = ", |
| "name = ", |
| '"ExceptionName"', |
| "reason = ", |
| '"SomeReason"', |
| "userInfo = ", |
| "1 key/value pair", |
| "reserved = ", |
| ], |
| ) |
| |
| e1 = frame.FindVariable("e1") |
| self.assertTrue(e1) |
| self.assertEqual(e1.type.name, "NSException *") |
| self.assertEqual(e1.GetSummary(), '"SomeReason"') |
| self.assertEqual(e1.GetChildMemberWithName("name").description, "ExceptionName") |
| self.assertEqual(e1.GetChildMemberWithName("reason").description, "SomeReason") |
| userInfo = e1.GetChildMemberWithName("userInfo").dynamic |
| self.assertEqual(userInfo.summary, "1 key/value pair") |
| self.assertEqual( |
| userInfo.GetChildAtIndex(0).GetChildAtIndex(0).description, "some_key" |
| ) |
| self.assertEqual( |
| userInfo.GetChildAtIndex(0).GetChildAtIndex(1).description, "some_value" |
| ) |
| |
| self.expect( |
| "frame variable e2", substrs=["(NSException *) e2 = ", '"SomeReason"'] |
| ) |
| |
| self.expect( |
| "frame variable *e2", |
| substrs=[ |
| "(NSException) *e2 = ", |
| "name = ", |
| '"ThrownException"', |
| "reason = ", |
| '"SomeReason"', |
| "userInfo = ", |
| "1 key/value pair", |
| "reserved = ", |
| ], |
| ) |
| |
| e2 = frame.FindVariable("e2") |
| self.assertTrue(e2) |
| self.assertEqual(e2.type.name, "NSException *") |
| self.assertEqual(e2.GetSummary(), '"SomeReason"') |
| self.assertEqual( |
| e2.GetChildMemberWithName("name").description, "ThrownException" |
| ) |
| self.assertEqual(e2.GetChildMemberWithName("reason").description, "SomeReason") |
| userInfo = e2.GetChildMemberWithName("userInfo").dynamic |
| self.assertEqual(userInfo.summary, "1 key/value pair") |
| self.assertEqual( |
| userInfo.GetChildAtIndex(0).GetChildAtIndex(0).description, "some_key" |
| ) |
| self.assertEqual( |
| userInfo.GetChildAtIndex(0).GetChildAtIndex(1).description, "some_value" |
| ) |
| reserved = e2.GetChildMemberWithName("reserved").dynamic |
| self.assertGreater(reserved.num_children, 0) |
| callStackReturnAddresses = [ |
| reserved.GetChildAtIndex(i).GetChildAtIndex(1) |
| for i in range(0, reserved.GetNumChildren()) |
| if reserved.GetChildAtIndex(i).GetChildAtIndex(0).description |
| == "callStackReturnAddresses" |
| ][0].dynamic |
| children = [ |
| callStackReturnAddresses.GetChildAtIndex(i) |
| for i in range(0, callStackReturnAddresses.num_children) |
| ] |
| |
| pcs = [i.unsigned for i in children] |
| names = [ |
| target.ResolveSymbolContextForAddress( |
| lldb.SBAddress(pc, target), lldb.eSymbolContextSymbol |
| ) |
| .GetSymbol() |
| .name |
| for pc in pcs |
| ] |
| for n in ["objc_exception_throw", "foo(int)", "main"]: |
| self.assertIn( |
| n, names, "%s is in the exception backtrace (%s)" % (n, names) |
| ) |
| |
| @skipIf(compiler="clang", compiler_version=["<", "13.0"]) |
| def test_objc_exceptions_at_abort(self): |
| self.build() |
| |
| target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) |
| self.assertTrue(target, VALID_TARGET) |
| |
| self.runCmd("run 0") |
| |
| # We should be stopped at pthread_kill because of an unhandled exception |
| self.expect("thread list", substrs=["stopped", "stop reason = signal SIGABRT"]) |
| |
| self.expect( |
| "thread exception", |
| substrs=[ |
| "(NSException *) exception = ", |
| '"SomeReason"', |
| "libobjc.A.dylib`objc_exception_throw", |
| "a.out`foo", |
| "at main.mm:16", |
| "a.out`rethrow", |
| "at main.mm:27", |
| "a.out`main", |
| ], |
| ) |
| |
| process = self.dbg.GetSelectedTarget().process |
| thread = process.GetSelectedThread() |
| |
| # There is an exception being currently processed at this point |
| self.assertTrue(thread.GetCurrentException().IsValid()) |
| self.assertTrue(thread.GetCurrentExceptionBacktrace().IsValid()) |
| |
| history_thread = thread.GetCurrentExceptionBacktrace() |
| self.assertGreaterEqual(history_thread.num_frames, 4) |
| for n in ["objc_exception_throw", "foo(int)", "rethrow(int)", "main"]: |
| self.assertEqual( |
| len([f for f in history_thread.frames if f.GetFunctionName() == n]), 1 |
| ) |
| |
| self.runCmd("kill") |
| |
| self.runCmd("run 1") |
| # We should be stopped at pthread_kill because of an unhandled exception |
| self.expect("thread list", substrs=["stopped", "stop reason = signal SIGABRT"]) |
| |
| self.expect( |
| "thread exception", |
| substrs=[ |
| "(MyCustomException *) exception = ", |
| "libobjc.A.dylib`objc_exception_throw", |
| "a.out`foo", |
| "at main.mm:18", |
| "a.out`rethrow", |
| "at main.mm:27", |
| "a.out`main", |
| ], |
| ) |
| |
| process = self.dbg.GetSelectedTarget().process |
| thread = process.GetSelectedThread() |
| |
| history_thread = thread.GetCurrentExceptionBacktrace() |
| self.assertGreaterEqual(history_thread.num_frames, 4) |
| for n in ["objc_exception_throw", "foo(int)", "rethrow(int)", "main"]: |
| self.assertEqual( |
| len([f for f in history_thread.frames if f.GetFunctionName() == n]), 1 |
| ) |
| |
| @skipIf(compiler="clang", compiler_version=["<", "13.0"]) |
| def test_cxx_exceptions_at_abort(self): |
| self.build() |
| |
| target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) |
| self.assertTrue(target, VALID_TARGET) |
| |
| self.runCmd("run 2") |
| |
| # We should be stopped at pthread_kill because of an unhandled exception |
| self.expect("thread list", substrs=["stopped", "stop reason = signal SIGABRT"]) |
| |
| self.expect("thread exception", substrs=["exception ="]) |
| |
| process = self.dbg.GetSelectedTarget().process |
| thread = process.GetSelectedThread() |
| |
| self.assertTrue(thread.GetCurrentException().IsValid()) |
| |
| # C++ exception backtraces are not exposed in the API (yet). |
| self.assertFalse(thread.GetCurrentExceptionBacktrace().IsValid()) |