Files
clang-p2996/lldb/test/API/lang/objc/exceptions/TestObjCExceptions.py
Alex Langford 28fb39f16a [lldb] Adjust for changes in objc runtime
The Objective-C runtime and the shared cache has changed slightly.
Given a class_ro_t, the baseMethods ivar is now a pointer union and may
either be a method_list_t pointer or a pointer to a relative list of
lists. The entries of this relative list of lists are indexes that refer
to a specific image in the shared cache in addition to a pointer offset
to find the accompanying method_list_t. We have to go over each of these
entries, parse it, and then if the relevant image is loaded in the
process, we add those methods to the relevant clang Decl.

In order to determine if an image is loaded, the Objective-C runtime
exposes a symbol that lets us determine if a particular image is loaded.
We maintain a data structure SharedCacheImageHeaders to keep track of
that information.

There is a known issue where if an image is loaded after we create a
Decl for a class, the Decl will not have the relevant methods from that
image (i.e. for Categories).

rdar://107957209

Differential Revision: https://reviews.llvm.org/D153597
2023-06-22 16:42:22 -07:00

259 lines
8.9 KiB
Python

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