Index: packages/Python/lldbsuite/test/lang/objc/exceptions/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/lang/objc/exceptions/Makefile @@ -0,0 +1,9 @@ +LEVEL = ../../../make + +OBJC_SOURCES := main.m + +CFLAGS_EXTRAS += -w + +include $(LEVEL)/Makefile.rules + +LDFLAGS += -framework Foundation Index: packages/Python/lldbsuite/test/lang/objc/exceptions/TestObjCExceptions.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/lang/objc/exceptions/TestObjCExceptions.py @@ -0,0 +1,71 @@ +# encoding: utf-8 +""" +Test lldb Obj-C exception support. +""" + +from __future__ import print_function + + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class ObjCExceptionsTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + # Find the line number to break at. + self.line = line_number('main.m', '// Set break point at this line.') + + @skipUnlessDarwin + def test_objc_exceptions_1(self): + self.build() + + self.runCmd("file a.out", CURRENT_EXECUTABLE_SET) + + lldbutil.run_break_set_by_file_and_line( + self, "main.m", self.line, num_expected_locations=1, loc_exact=True) + + self.runCmd("run", RUN_SUCCEEDED) + + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs=['stopped', 'stop reason = breakpoint']) + + self.expect( + 'frame variable e1', + substrs=[ + '(NSException *) e1 = ', + 'name: @"ExceptionName" - reason: @"SomeReason"' + ]) + + self.expect( + 'frame variable --dynamic-type no-run-target *e1', + substrs=[ + '(NSException) *e1 = ', + 'name = ', '@"ExceptionName"', + 'reason = ', '@"SomeReason"', + 'userInfo = ', '1 key/value pair', + 'reserved = ', 'nil', + ]) + + self.expect( + 'frame variable e2', + substrs=[ + '(NSException *) e2 = ', + 'name: @"ThrownException" - reason: @"SomeReason"' + ]) + + self.expect( + 'frame variable --dynamic-type no-run-target *e2', + substrs=[ + '(NSException) *e2 = ', + 'name = ', '@"ThrownException"', + 'reason = ', '@"SomeReason"', + 'userInfo = ', '1 key/value pair', + 'reserved = ', + ]) Index: packages/Python/lldbsuite/test/lang/objc/exceptions/main.m =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/lang/objc/exceptions/main.m @@ -0,0 +1,36 @@ +//===-- main.m ------------------------------------------------*- ObjC -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#import + +void foo() +{ + NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:@"some_key", @"some_value", nil]; + @throw [[NSException alloc] initWithName:@"ThrownException" reason:@"SomeReason" userInfo:info]; +} + +int main (int argc, const char * argv[]) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:@"some_key", @"some_value", nil]; + NSException *e1 = [[NSException alloc] initWithName:@"ExceptionName" reason:@"SomeReason" userInfo:info]; + NSException *e2; + + @try { + foo(); + } @catch(NSException *e) { + e2 = e; + } + + NSLog(@"1"); // Set break point at this line. + [pool drain]; + return 0; +} + Index: source/Plugins/Language/ObjC/NSArray.cpp =================================================================== --- source/Plugins/Language/ObjC/NSArray.cpp +++ source/Plugins/Language/ObjC/NSArray.cpp @@ -218,6 +218,25 @@ } +namespace CallStackArray { + struct DataDescriptor_32 { + uint32_t _data; + uint32_t _used; + uint32_t _offset; + const uint32_t _size = 0; + }; + + struct DataDescriptor_64 { + uint64_t _data; + uint64_t _used; + uint64_t _offset; + const uint64_t _size = 0; + }; + + using NSCallStackArraySyntheticFrontEnd = + GenericNSArrayMSyntheticFrontEnd; +} + template class GenericNSArrayISyntheticFrontEnd : public SyntheticChildrenFrontEnd { public: @@ -368,6 +387,7 @@ static const ConstString g_NSArrayCF("__NSCFArray"); static const ConstString g_NSArrayMLegacy("__NSArrayM_Legacy"); static const ConstString g_NSArrayMImmutable("__NSArrayM_Immutable"); + static const ConstString g_NSCallStackArray("_NSCallStackArray"); if (class_name.IsEmpty()) return false; @@ -423,6 +443,8 @@ valobj_addr + 2 * ptr_size, ptr_size, 0, error); if (error.Fail()) return false; + } else if (class_name == g_NSCallStackArray) { + return false; } else { auto &map(NSArray_Additionals::GetAdditionalSummaries()); auto iter = map.find(class_name), end = map.end(); @@ -817,6 +839,7 @@ static const ConstString g_NSArray1("__NSSingleObjectArrayI"); static const ConstString g_NSArrayMLegacy("__NSArrayM_Legacy"); static const ConstString g_NSArrayMImmutable("__NSArrayM_Immutable"); + static const ConstString g_NSCallStackArray("_NSCallStackArray"); if (class_name.IsEmpty()) return nullptr; @@ -846,6 +869,8 @@ return (new Foundation1010::NSArrayMSyntheticFrontEnd(valobj_sp)); else return (new Foundation109::NSArrayMSyntheticFrontEnd(valobj_sp)); + } else if (class_name == g_NSCallStackArray) { + return (new CallStackArray::NSCallStackArraySyntheticFrontEnd(valobj_sp)); } else { auto &map(NSArray_Additionals::GetAdditionalSynthetics()); auto iter = map.find(class_name), end = map.end(); Index: source/Plugins/Language/ObjC/NSException.cpp =================================================================== --- source/Plugins/Language/ObjC/NSException.cpp +++ source/Plugins/Language/ObjC/NSException.cpp @@ -33,56 +33,75 @@ using namespace lldb_private; using namespace lldb_private::formatters; -bool lldb_private::formatters::NSException_SummaryProvider( - ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { +static bool ExtractFields(ValueObject &valobj, ValueObjectSP *name_sp, + ValueObjectSP *reason_sp, ValueObjectSP *userinfo_sp, + ValueObjectSP *reserved_sp) { ProcessSP process_sp(valobj.GetProcessSP()); - if (!process_sp) - return false; + if (!process_sp) return false; - lldb::addr_t ptr_value = LLDB_INVALID_ADDRESS; + lldb::addr_t ptr = LLDB_INVALID_ADDRESS; CompilerType valobj_type(valobj.GetCompilerType()); Flags type_flags(valobj_type.GetTypeInfo()); if (type_flags.AllClear(eTypeHasValue)) { if (valobj.IsBaseClass() && valobj.GetParent()) - ptr_value = valobj.GetParent()->GetValueAsUnsigned(LLDB_INVALID_ADDRESS); + ptr = valobj.GetParent()->GetValueAsUnsigned(LLDB_INVALID_ADDRESS); } else - ptr_value = valobj.GetValueAsUnsigned(LLDB_INVALID_ADDRESS); + ptr = valobj.GetValueAsUnsigned(LLDB_INVALID_ADDRESS); - if (ptr_value == LLDB_INVALID_ADDRESS) + if (ptr == LLDB_INVALID_ADDRESS) return false; size_t ptr_size = process_sp->GetAddressByteSize(); - lldb::addr_t name_location = ptr_value + 1 * ptr_size; - lldb::addr_t reason_location = ptr_value + 2 * ptr_size; Status error; - lldb::addr_t name = process_sp->ReadPointerFromMemory(name_location, error); - if (error.Fail() || name == LLDB_INVALID_ADDRESS) - return false; - - lldb::addr_t reason = - process_sp->ReadPointerFromMemory(reason_location, error); - if (error.Fail() || reason == LLDB_INVALID_ADDRESS) - return false; + auto name = process_sp->ReadPointerFromMemory(ptr + 1 * ptr_size, error); + if (error.Fail() || name == LLDB_INVALID_ADDRESS) return false; + auto reason = process_sp->ReadPointerFromMemory(ptr + 2 * ptr_size, error); + if (error.Fail() || reason == LLDB_INVALID_ADDRESS) return false; + auto userinfo = process_sp->ReadPointerFromMemory(ptr + 3 * ptr_size, error); + if (error.Fail() || reason == LLDB_INVALID_ADDRESS) return false; + auto reserved = process_sp->ReadPointerFromMemory(ptr + 4 * ptr_size, error); + if (error.Fail() || reason == LLDB_INVALID_ADDRESS) return false; InferiorSizedWord name_isw(name, *process_sp); InferiorSizedWord reason_isw(reason, *process_sp); + InferiorSizedWord userinfo_isw(userinfo, *process_sp); + InferiorSizedWord reserved_isw(reserved, *process_sp); CompilerType voidstar = process_sp->GetTarget() - .GetScratchClangASTContext() - ->GetBasicType(lldb::eBasicTypeVoid) - .GetPointerType(); - - ValueObjectSP name_sp = ValueObject::CreateValueObjectFromData( - "name_str", name_isw.GetAsData(process_sp->GetByteOrder()), - valobj.GetExecutionContextRef(), voidstar); - ValueObjectSP reason_sp = ValueObject::CreateValueObjectFromData( - "reason_str", reason_isw.GetAsData(process_sp->GetByteOrder()), - valobj.GetExecutionContextRef(), voidstar); - - if (!name_sp || !reason_sp) + .GetScratchClangASTContext() + ->GetBasicType(lldb::eBasicTypeVoid) + .GetPointerType(); + + if (name_sp) + *name_sp = ValueObject::CreateValueObjectFromData( + "name", name_isw.GetAsData(process_sp->GetByteOrder()), + valobj.GetExecutionContextRef(), voidstar); + if (reason_sp) + *reason_sp = ValueObject::CreateValueObjectFromData( + "reason", reason_isw.GetAsData(process_sp->GetByteOrder()), + valobj.GetExecutionContextRef(), voidstar); + if (userinfo_sp) + *userinfo_sp = ValueObject::CreateValueObjectFromData( + "userInfo", userinfo_isw.GetAsData(process_sp->GetByteOrder()), + valobj.GetExecutionContextRef(), voidstar); + if (reserved_sp) + *reserved_sp = ValueObject::CreateValueObjectFromData( + "reserved", reserved_isw.GetAsData(process_sp->GetByteOrder()), + valobj.GetExecutionContextRef(), voidstar); + + return true; +} + +bool lldb_private::formatters::NSException_SummaryProvider( + ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { + lldb::ValueObjectSP name_sp; + lldb::ValueObjectSP reason_sp; + if (!ExtractFields(valobj, &name_sp, &reason_sp, nullptr, nullptr)) return false; + if (!name_sp || !reason_sp) return false; + StreamString name_str_summary; StreamString reason_str_summary; if (NSStringSummaryProvider(*name_sp, name_str_summary, options) && @@ -101,84 +120,53 @@ : SyntheticChildrenFrontEnd(*valobj_sp) {} ~NSExceptionSyntheticFrontEnd() override = default; - // no need to delete m_child_ptr - it's kept alive by the cluster manager on - // our behalf size_t CalculateNumChildren() override { - if (m_child_ptr) - return 1; - if (m_child_sp) - return 1; - return 0; + return 4; } lldb::ValueObjectSP GetChildAtIndex(size_t idx) override { - if (idx != 0) - return lldb::ValueObjectSP(); - - if (m_child_ptr) - return m_child_ptr->GetSP(); - return m_child_sp; + switch (idx) { + case 0: return m_name_sp; + case 1: return m_reason_sp; + case 2: return m_userinfo_sp; + case 3: return m_reserved_sp; + } + return lldb::ValueObjectSP(); } bool Update() override { - m_child_ptr = nullptr; - m_child_sp.reset(); - - ProcessSP process_sp(m_backend.GetProcessSP()); - if (!process_sp) - return false; - - lldb::addr_t userinfo_location = LLDB_INVALID_ADDRESS; + m_name_sp.reset(); + m_reason_sp.reset(); + m_userinfo_sp.reset(); + m_reserved_sp.reset(); - CompilerType valobj_type(m_backend.GetCompilerType()); - Flags type_flags(valobj_type.GetTypeInfo()); - if (type_flags.AllClear(eTypeHasValue)) { - if (m_backend.IsBaseClass() && m_backend.GetParent()) - userinfo_location = - m_backend.GetParent()->GetValueAsUnsigned(LLDB_INVALID_ADDRESS); - } else - userinfo_location = m_backend.GetValueAsUnsigned(LLDB_INVALID_ADDRESS); - - if (userinfo_location == LLDB_INVALID_ADDRESS) + if (!ExtractFields(m_backend, &m_name_sp, &m_reason_sp, &m_userinfo_sp, + &m_reserved_sp)) return false; - size_t ptr_size = process_sp->GetAddressByteSize(); - - userinfo_location += 3 * ptr_size; - Status error; - lldb::addr_t userinfo = - process_sp->ReadPointerFromMemory(userinfo_location, error); - if (userinfo == LLDB_INVALID_ADDRESS || error.Fail()) - return false; - InferiorSizedWord isw(userinfo, *process_sp); - m_child_sp = CreateValueObjectFromData( - "userInfo", isw.GetAsData(process_sp->GetByteOrder()), - m_backend.GetExecutionContextRef(), - process_sp->GetTarget().GetScratchClangASTContext()->GetBasicType( - lldb::eBasicTypeObjCID)); - return false; + return true; } bool MightHaveChildren() override { return true; } size_t GetIndexOfChildWithName(const ConstString &name) override { + static ConstString g___name("name"); + static ConstString g___reason("reason"); static ConstString g___userInfo("userInfo"); - if (name == g___userInfo) - return 0; + static ConstString g___reserved("reserved"); + if (name == g___name) return 0; + if (name == g___reason) return 1; + if (name == g___userInfo) return 2; + if (name == g___reserved) return 3; return UINT32_MAX; } private: - // the child here can be "real" (i.e. an actual child of the root) or - // synthetized from raw memory - // if the former, I need to store a plain pointer to it - or else a loop of - // references will cause this entire hierarchy of values to leak - // if the latter, then I need to store a SharedPointer to it - so that it only - // goes away when everyone else in the cluster goes away - // oh joy! - ValueObject *m_child_ptr; - ValueObjectSP m_child_sp; + ValueObjectSP m_name_sp; + ValueObjectSP m_reason_sp; + ValueObjectSP m_userinfo_sp; + ValueObjectSP m_reserved_sp; }; SyntheticChildrenFrontEnd *