diff --git a/lldb/include/lldb/Core/ClangForward.h b/lldb/include/lldb/Core/ClangForward.h index a099cd21a883..bbb6adf1cb61 100644 --- a/lldb/include/lldb/Core/ClangForward.h +++ b/lldb/include/lldb/Core/ClangForward.h @@ -80,6 +80,7 @@ namespace clang class ObjCEncodeExpr; class ObjCImplicitSetterGetterRefExpr; class ObjCInterfaceDecl; + class ObjCIvarDecl; class ObjCIvarRefExpr; class ObjCMessageExpr; class ObjCMethodDecl; diff --git a/lldb/include/lldb/Symbol/ClangASTContext.h b/lldb/include/lldb/Symbol/ClangASTContext.h index 28468952d897..8db018f3d285 100644 --- a/lldb/include/lldb/Symbol/ClangASTContext.h +++ b/lldb/include/lldb/Symbol/ClangASTContext.h @@ -22,6 +22,7 @@ #include "llvm/ADT/SmallVector.h" #include "clang/AST/TemplateBase.h" + // Project includes #include "lldb/lldb-enumerations.h" #include "lldb/Core/ClangForward.h" @@ -247,7 +248,7 @@ public: int kind, lldb::LanguageType language); - static bool + static clang::FieldDecl * AddFieldToRecordType (clang::ASTContext *ast, lldb::clang_type_t record_qual_type, const char *name, @@ -255,7 +256,7 @@ public: lldb::AccessType access, uint32_t bitfield_bit_size); - bool + clang::FieldDecl * AddFieldToRecordType (lldb::clang_type_t record_qual_type, const char *name, lldb::clang_type_t field_type, @@ -383,7 +384,7 @@ public: bool isForwardDecl, bool isInternal); - static bool + static clang::FieldDecl * AddObjCClassIVar (clang::ASTContext *ast, lldb::clang_type_t class_opaque_type, const char *name, @@ -392,7 +393,7 @@ public: uint32_t bitfield_bit_size, bool isSynthesized); - bool + clang::FieldDecl * AddObjCClassIVar (lldb::clang_type_t class_opaque_type, const char *name, lldb::clang_type_t ivar_opaque_type, @@ -409,6 +410,41 @@ public: isSynthesized); } + static bool + AddObjCClassProperty + ( + clang::ASTContext *ast, + lldb::clang_type_t class_opaque_type, + const char *property_name, + lldb::clang_type_t property_opaque_type, // The property type is only required if you don't have an ivar decl + clang::ObjCIvarDecl *ivar_decl, + const char *property_setter_name, + const char *property_getter_name, + uint32_t property_attributes + ); + + bool + AddObjCClassProperty + ( + lldb::clang_type_t class_opaque_type, + const char *property_name, + lldb::clang_type_t property_opaque_type, + clang::ObjCIvarDecl *ivar_decl, + const char *property_setter_name, + const char *property_getter_name, + uint32_t property_attributes + ) + { + return ClangASTContext::AddObjCClassProperty (getASTContext(), + class_opaque_type, + property_name, + property_opaque_type, + ivar_decl, + property_setter_name, + property_getter_name, + property_attributes); + } + bool SetObjCSuperClass (lldb::clang_type_t class_clang_type, lldb::clang_type_t superclass_clang_type); diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp index 2881b0f739a9..f872152f7ce1 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp @@ -14,6 +14,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclGroup.h" +#include "clang/AST/DeclObjC.h" #include "clang/Basic/Builtins.h" #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/LangOptions.h" @@ -1324,6 +1325,12 @@ SymbolFileDWARF::ParseChildMembers Declaration decl; //DWARFExpression location; const char *name = NULL; + const char *prop_name = NULL; + const char *prop_getter_name = NULL; + const char *prop_setter_name = NULL; + uint32_t prop_attributes = 0; + + bool is_artificial = false; lldb::user_id_t encoding_uid = LLDB_INVALID_UID; AccessType accessibility = eAccessNone; @@ -1369,6 +1376,12 @@ SymbolFileDWARF::ParseChildMembers case DW_AT_description: case DW_AT_mutable: case DW_AT_visibility: + + case DW_AT_APPLE_property_name: prop_name = form_value.AsCString(&get_debug_str_data()); break; + case DW_AT_APPLE_property_getter: prop_getter_name = form_value.AsCString(&get_debug_str_data()); break; + case DW_AT_APPLE_property_setter: prop_setter_name = form_value.AsCString(&get_debug_str_data()); break; + case DW_AT_APPLE_property_attribute: prop_attributes = form_value.Unsigned(); break; + default: case DW_AT_sibling: break; @@ -1415,13 +1428,14 @@ SymbolFileDWARF::ParseChildMembers if (is_artificial == false) { Type *member_type = ResolveTypeUID(encoding_uid); + clang::FieldDecl *field_decl = NULL; if (member_type) { if (accessibility == eAccessNone) accessibility = default_accessibility; member_accessibilities.push_back(accessibility); - GetClangASTContext().AddFieldToRecordType (class_clang_type, + field_decl = GetClangASTContext().AddFieldToRecordType (class_clang_type, name, member_type->GetClangLayoutType(), accessibility, @@ -1439,6 +1453,22 @@ SymbolFileDWARF::ParseChildMembers MakeUserID(die->GetOffset()), encoding_uid); } + + if (prop_name != NULL) + { + + clang::ObjCIvarDecl *ivar_decl = clang::dyn_cast(field_decl); + assert (ivar_decl != NULL); + + + GetClangASTContext().AddObjCClassProperty (class_clang_type, + prop_name, + 0, + ivar_decl, + prop_setter_name, + prop_getter_name, + prop_attributes); + } } } ++member_idx; diff --git a/lldb/source/Symbol/ClangASTContext.cpp b/lldb/source/Symbol/ClangASTContext.cpp index ecaaebdedc1b..a476b33e2617 100644 --- a/lldb/source/Symbol/ClangASTContext.cpp +++ b/lldb/source/Symbol/ClangASTContext.cpp @@ -1782,7 +1782,7 @@ ClangASTContext::AddMethodToCXXRecordType return cxx_method_decl; } -bool +clang::FieldDecl * ClangASTContext::AddFieldToRecordType ( ASTContext *ast, @@ -1794,8 +1794,9 @@ ClangASTContext::AddFieldToRecordType ) { if (record_clang_type == NULL || field_type == NULL) - return false; + return NULL; + FieldDecl *field = NULL; IdentifierTable *identifier_table = &ast->Idents; assert (ast != NULL); @@ -1818,7 +1819,7 @@ ClangASTContext::AddFieldToRecordType APInt bitfield_bit_size_apint(ast->getTypeSize(ast->IntTy), bitfield_bit_size); bit_width = new (*ast)IntegerLiteral (*ast, bitfield_bit_size_apint, ast->IntTy, SourceLocation()); } - FieldDecl *field = FieldDecl::Create (*ast, + field = FieldDecl::Create (*ast, record_decl, SourceLocation(), SourceLocation(), @@ -1846,7 +1847,7 @@ ClangASTContext::AddFieldToRecordType if (objc_class_type) { bool is_synthesized = false; - ClangASTContext::AddObjCClassIVar (ast, + field = ClangASTContext::AddObjCClassIVar (ast, record_clang_type, name, field_type, @@ -1856,7 +1857,7 @@ ClangASTContext::AddFieldToRecordType } } } - return false; + return field; } bool @@ -2047,7 +2048,7 @@ ClangASTContext::SetObjCSuperClass (clang_type_t class_opaque_type, clang_type_t } -bool +FieldDecl * ClangASTContext::AddObjCClassIVar ( ASTContext *ast, @@ -2060,8 +2061,10 @@ ClangASTContext::AddObjCClassIVar ) { if (class_opaque_type == NULL || ivar_opaque_type == NULL) - return false; + return NULL; + ObjCIvarDecl *field = NULL; + IdentifierTable *identifier_table = &ast->Idents; assert (ast != NULL); @@ -2087,16 +2090,16 @@ ClangASTContext::AddObjCClassIVar bit_width = new (*ast)IntegerLiteral (*ast, bitfield_bit_size_apint, ast->IntTy, SourceLocation()); } - ObjCIvarDecl *field = ObjCIvarDecl::Create (*ast, - class_interface_decl, - SourceLocation(), - SourceLocation(), - &identifier_table->get(name), // Identifier - QualType::getFromOpaquePtr(ivar_opaque_type), // Field type - NULL, // TypeSourceInfo * - ConvertAccessTypeToObjCIvarAccessControl (access), - bit_width, - is_synthesized); + field = ObjCIvarDecl::Create (*ast, + class_interface_decl, + SourceLocation(), + SourceLocation(), + &identifier_table->get(name), // Identifier + QualType::getFromOpaquePtr(ivar_opaque_type), // Field type + NULL, // TypeSourceInfo * + ConvertAccessTypeToObjCIvarAccessControl (access), + bit_width, + is_synthesized); if (field) { @@ -2106,15 +2109,106 @@ ClangASTContext::AddObjCClassIVar VerifyDecl(field); #endif - return true; + return field; } } } } + return NULL; +} + +bool +ClangASTContext::AddObjCClassProperty +( + ASTContext *ast, + clang_type_t class_opaque_type, + const char *property_name, + clang_type_t property_opaque_type, + ObjCIvarDecl *ivar_decl, + const char *property_setter_name, + const char *property_getter_name, + uint32_t property_attributes +) +{ + if (class_opaque_type == NULL) + return false; + + IdentifierTable *identifier_table = &ast->Idents; + + assert (ast != NULL); + assert (identifier_table != NULL); + + QualType class_qual_type(QualType::getFromOpaquePtr(class_opaque_type)); + const clang::Type *class_type = class_qual_type.getTypePtr(); + if (class_type) + { + const ObjCObjectType *objc_class_type = dyn_cast(class_type); + + if (objc_class_type) + { + ObjCInterfaceDecl *class_interface_decl = objc_class_type->getInterface(); + + // FIXME: For now, we don't know how to add properties if we don't have their associated ivar. + if (class_interface_decl && ivar_decl) + { + clang::TypeSourceInfo *prop_type_source; + if (ivar_decl) + prop_type_source = ast->CreateTypeSourceInfo (ivar_decl->getType()); + else + prop_type_source = ast->CreateTypeSourceInfo (QualType::getFromOpaquePtr(property_opaque_type)); + + ObjCPropertyDecl *property_decl = ObjCPropertyDecl::Create(*ast, + class_interface_decl, + SourceLocation(), // Source Location + &identifier_table->get(property_name), + SourceLocation(), //Source Location for AT + prop_type_source + ); + if (property_decl) + { + class_interface_decl->addDecl (property_decl); + if (property_setter_name != NULL) + { + std::string property_setter_no_colon(property_setter_name, strlen(property_setter_name) - 1); + clang::IdentifierInfo *setter_ident = &identifier_table->get(property_setter_no_colon.c_str()); + Selector setter_sel = ast->Selectors.getSelector(1, &setter_ident); + property_decl->setSetterName(setter_sel); + property_decl->setPropertyAttributes (clang::ObjCPropertyDecl::OBJC_PR_setter); + } + + if (property_getter_name != NULL) + { + clang::IdentifierInfo *getter_ident = &identifier_table->get(property_getter_name); + Selector getter_sel = ast->Selectors.getSelector(0, &getter_ident); + property_decl->setGetterName(getter_sel); + property_decl->setPropertyAttributes (clang::ObjCPropertyDecl::OBJC_PR_getter); + + } + + if (ivar_decl) + property_decl->setPropertyIvarDecl (ivar_decl); + + if (property_attributes & DW_APPLE_PROPERTY_readonly) + property_decl->setPropertyAttributes (clang::ObjCPropertyDecl::OBJC_PR_readonly); + if (property_attributes & DW_APPLE_PROPERTY_readwrite) + property_decl->setPropertyAttributes (clang::ObjCPropertyDecl::OBJC_PR_readwrite); + if (property_attributes & DW_APPLE_PROPERTY_assign) + property_decl->setPropertyAttributes (clang::ObjCPropertyDecl::OBJC_PR_assign); + if (property_attributes & DW_APPLE_PROPERTY_retain) + property_decl->setPropertyAttributes (clang::ObjCPropertyDecl::OBJC_PR_retain); + if (property_attributes & DW_APPLE_PROPERTY_copy) + property_decl->setPropertyAttributes (clang::ObjCPropertyDecl::OBJC_PR_copy); + if (property_attributes & DW_APPLE_PROPERTY_nonatomic) + property_decl->setPropertyAttributes (clang::ObjCPropertyDecl::OBJC_PR_nonatomic); + + return true; + } + } + } + } return false; } - bool ClangASTContext::ObjCTypeHasIVars (clang_type_t class_opaque_type, bool check_superclass) { diff --git a/lldb/test/lang/objc/objc-property/Makefile b/lldb/test/lang/objc/objc-property/Makefile new file mode 100644 index 000000000000..a1608fe5a664 --- /dev/null +++ b/lldb/test/lang/objc/objc-property/Makefile @@ -0,0 +1,6 @@ +LEVEL = ../../../make + +OBJC_SOURCES := main.m +LDFLAGS = $(CFLAGS) -lobjc -framework Foundation + +include $(LEVEL)/Makefile.rules diff --git a/lldb/test/lang/objc/objc-property/TestObjCProperty.py b/lldb/test/lang/objc/objc-property/TestObjCProperty.py new file mode 100644 index 000000000000..fbcb79560d36 --- /dev/null +++ b/lldb/test/lang/objc/objc-property/TestObjCProperty.py @@ -0,0 +1,129 @@ +""" +Use lldb Python API to verify that expression evaluation for property references uses the correct getters and setters +""" + +import os, time +import re +import unittest2 +import lldb, lldbutil +from lldbtest import * + +class ObjCDynamicValueTestCase(TestBase): + + mydir = os.path.join("lang", "objc", "objc-property") + + @unittest2.skipUnless(sys.platform.startswith("darwin"), "requires Darwin") + @python_api_test + def test_get_dynamic_objc_vals_with_dsym(self): + """Test that expr uses the correct property getters and setters""" + self.buildDsym() + self.do_test_properties() + + @python_api_test + def test_get_objc_dynamic_vals_with_dwarf(self): + """Test that expr uses the correct property getters and setters""" + self.buildDwarf() + self.do_test_properties() + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + + # Find the line number to break for main.c. + + self.source_name = 'main.m' + + def run_to_main (self): + """Test that expr uses the correct property getters and setters""" + exe = os.path.join(os.getcwd(), "a.out") + + # Create a target from the debugger. + + target = self.dbg.CreateTarget (exe) + self.assertTrue(target, VALID_TARGET) + + # Set up our breakpoints: + + main_bkpt = target.BreakpointCreateBySourceRegex ("Set a breakpoint here.", lldb.SBFileSpec (self.source_name)) + self.assertTrue(main_bkpt and + main_bkpt.GetNumLocations() == 1, + VALID_BREAKPOINT) + + # Now launch the process, and do not stop at the entry point. + process = target.LaunchSimple (None, None, os.getcwd()) + + self.assertTrue(process.GetState() == lldb.eStateStopped, + PROCESS_STOPPED) + + threads = lldbutil.get_threads_stopped_at_breakpoint (process, main_bkpt) + self.assertTrue (len(threads) == 1) + thread = threads[0] + return thread + + def do_test_properties (self): + + thread = self.run_to_main() + + frame = thread.GetFrameAtIndex(0) + + mine = frame.FindVariable ("mine") + self.assertTrue (mine.IsValid()) + access_count = mine.GetChildMemberWithName ("_access_count") + self.assertTrue (access_count.IsValid()) + start_access_count = access_count.GetValueAsUnsigned (123456) + self.assertTrue (start_access_count != 123456) + + # + # The first set of tests test calling the getter & setter of + # a property that actually only has a getter & setter and no + # @property. + # + nonexistant_value = frame.EvaluateExpression("mine.nonexistantInt", False) + nonexistant_error = nonexistant_value.GetError() + self.assertTrue (nonexistant_error.Success()) + nonexistant_int = nonexistant_value.GetValueAsUnsigned (123456) + self.assertTrue (nonexistant_int == 6) + + # Calling the getter function would up the access count, so make sure that happened. + + new_access_count = access_count.GetValueAsUnsigned (123456) + self.assertTrue (new_access_count - start_access_count == 1) + start_access_count = new_access_count + + # + # Now call the setter, then make sure that + nonexistant_change = frame.EvaluateExpression("mine.nonexistantInt = 10", False) + nonexistant_error = nonexistant_change.GetError() + self.assertTrue (nonexistant_error.Success()) + + # Calling the setter function would up the access count, so make sure that happened. + + new_access_count = access_count.GetValueAsUnsigned (123456) + self.assertTrue (new_access_count - start_access_count == 1) + start_access_count = new_access_count + + # + # Now we call the getter of a property that is backed by an ivar, + # make sure it works and that we actually update the backing ivar. + # + + backed_value = frame.EvaluateExpression("mine.backedInt", False) + backed_error = backed_value.GetError() + self.assertTrue (backed_error.Success()) + backing_value = mine.GetChildMemberWithName ("_backedInt") + self.assertTrue (backing_value.IsValid()) + self.assertTrue (backed_value.GetValueAsUnsigned (12345) == backing_value.GetValueAsUnsigned(23456)) + + # + # This doesn't work correctly yet, because the clang Sema::HandleExprPropertyRefExpr + # doesn't complete the class before looking up the property. + # + #unbacked_value = frame.EvaluateExpression("mine.unbackedInt", False) + #unbacked_error = unbacked_value.GetError() + #self.assertTrue (unbacked_error.Success()) + +if __name__ == '__main__': + import atexit + lldb.SBDebugger.Initialize() + atexit.register(lambda: lldb.SBDebugger.Terminate()) + unittest2.main() diff --git a/lldb/test/lang/objc/objc-property/main.m b/lldb/test/lang/objc/objc-property/main.m new file mode 100644 index 000000000000..4ce78eccde5a --- /dev/null +++ b/lldb/test/lang/objc/objc-property/main.m @@ -0,0 +1,91 @@ +#import + +@interface BaseClass : NSObject +{ + int _backedInt; + int _access_count; +} + +- (int) nonexistantInt; +- (void) setNonexistantInt: (int) in_int; + +- (int) myGetUnbackedInt; +- (void) mySetUnbackedInt: (int) in_int; + +- (int) getAccessCount; + ++(BaseClass *) baseClassWithBackedInt: (int) inInt andUnbackedInt: (int) inOtherInt; + +@property(getter=myGetUnbackedInt,setter=mySetUnbackedInt:) int unbackedInt; +@property int backedInt; +@end + +@implementation BaseClass +@synthesize unbackedInt; +@synthesize backedInt = _backedInt; + ++ (BaseClass *) baseClassWithBackedInt: (int) inInt andUnbackedInt: (int) inOtherInt +{ + BaseClass *new = [[BaseClass alloc] init]; + + new->_backedInt = inInt; + new->unbackedInt = inOtherInt; + + return new; +} + +- (int) myGetUnbackedInt +{ + // NSLog (@"Getting BaseClass::unbackedInt - %d.\n", unbackedInt); + _access_count++; + return unbackedInt; +} + +- (void) mySetUnbackedInt: (int) in_int +{ + // NSLog (@"Setting BaseClass::unbackedInt from %d to %d.", unbackedInt, in_int); + _access_count++; + unbackedInt = in_int; +} + +- (int) nonexistantInt +{ + // NSLog (@"Getting BaseClass::nonexistantInt - %d.\n", 5); + _access_count++; + return 6; +} + +- (void) setNonexistantInt: (int) in_int +{ + // NSLog (@"Setting BaseClass::nonexistantInt from 7 to %d.", in_int); + _access_count++; +} + +- (int) getAccessCount +{ + return _access_count; +} +@end + +int +main () +{ + BaseClass *mine = [BaseClass baseClassWithBackedInt: 10 andUnbackedInt: 20]; + + // Set a breakpoint here. + int nonexistant = mine.nonexistantInt; + + int backedInt = mine.backedInt; + + int unbackedInt = mine.unbackedInt; + + NSLog (@"Results for %p: nonexistant: %d backed: %d unbacked: %d accessCount: %d.", + mine, + nonexistant, + backedInt, + unbackedInt, + [mine getAccessCount]); + return 0; + +} +