# 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())