X-Git-Url: https://git.saurik.com/apple/ld64.git/blobdiff_plain/f80fe69f3f29962e8aa43a99f8ed9201548f3d78..7f09b9353af9897bf18933788d6a59c152c29edd:/src/ld/passes/objc.cpp?ds=sidebyside diff --git a/src/ld/passes/objc.cpp b/src/ld/passes/objc.cpp index d921a64..e2afca1 100644 --- a/src/ld/passes/objc.cpp +++ b/src/ld/passes/objc.cpp @@ -50,11 +50,12 @@ struct objc_image_info { uint32_t flags; }; -#define OBJC_IMAGE_SUPPORTS_GC (1<<1) -#define OBJC_IMAGE_REQUIRES_GC (1<<2) -#define OBJC_IMAGE_OPTIMIZED_BY_DYLD (1<<3) -#define OBJC_IMAGE_SUPPORTS_COMPACTION (1<<4) -#define OBJC_IMAGE_IS_SIMULATED (1<<5) +#define OBJC_IMAGE_SUPPORTS_GC (1<<1) +#define OBJC_IMAGE_REQUIRES_GC (1<<2) +#define OBJC_IMAGE_OPTIMIZED_BY_DYLD (1<<3) +#define OBJC_IMAGE_SUPPORTS_COMPACTION (1<<4) +#define OBJC_IMAGE_IS_SIMULATED (1<<5) +#define OBJC_IMAGE_HAS_CATEGORY_CLASS_PROPERTIES (1<<6) @@ -65,7 +66,7 @@ template class ObjCImageInfoAtom : public ld::Atom { public: ObjCImageInfoAtom(ld::File::ObjcConstraint objcConstraint, - bool compaction, bool abi2); + bool compaction, bool abi2, bool hasCategoryClassProperties, uint8_t swiftVersion); virtual const ld::File* file() const { return NULL; } virtual const char* name() const { return "objc image info"; } @@ -89,7 +90,7 @@ template ld::Section ObjCImageInfoAtom::_s_sectionABI2("__DATA", template ObjCImageInfoAtom::ObjCImageInfoAtom(ld::File::ObjcConstraint objcConstraint, bool compaction, - bool abi2) + bool abi2, bool hasCategoryClassProperties, uint8_t swiftVersion) : ld::Atom(abi2 ? _s_sectionABI2 : _s_sectionABI1, ld::Atom::definitionRegular, ld::Atom::combineNever, ld::Atom::scopeLinkageUnit, ld::Atom::typeUnclassified, symbolTableNotIn, false, false, false, ld::Atom::Alignment(2)) @@ -117,6 +118,13 @@ ObjCImageInfoAtom::ObjCImageInfoAtom(ld::File::ObjcConstraint objcConstraint, break; } + if ( hasCategoryClassProperties ) { + value |= OBJC_IMAGE_HAS_CATEGORY_CLASS_PROPERTIES; + } + + // provide swift language version in final binary for runtime to inspect + value |= (swiftVersion << 8); + _content.version = 0; A::P::E::set32(_content.flags, value); } @@ -203,9 +211,12 @@ ld::Section ProtocolListAtom::_s_section("__DATA", "__objc_const", ld::Sectio template class PropertyListAtom : public ld::Atom { public: + enum class PropertyKind { ClassProperties, InstanceProperties }; + PropertyListAtom(ld::Internal& state, const ld::Atom* baseProtocolList, - const std::vector* categories, - std::set& deadAtoms); + const std::vector* categories, + std::set& deadAtoms, + PropertyKind kind); virtual const ld::File* file() const { return _file; } virtual const char* name() const { return "objc merged property list"; } @@ -270,6 +281,8 @@ public: private: typedef typename A::P::uint_t pint_t; + void addFixupAtOffset(uint32_t offset); + const ld::Atom* _atom; std::vector _fixups; }; @@ -311,7 +324,7 @@ const ld::Atom* ObjCData::getPointerInContent(ld::Internal& state, const ld:: if ( hasAddend != NULL ) *hasAddend = false; for (ld::Fixup::iterator fit=contentAtom->fixupsBegin(); fit != contentAtom->fixupsEnd(); ++fit) { - if ( fit->offsetInAtom == offset ) { + if ( (fit->offsetInAtom == offset) && (fit->kind != ld::Fixup::kindNoneFollowOn) ) { switch ( fit->binding ) { case ld::Fixup::bindingsIndirectlyBound: target = state.indirectBindingTable[fit->u.bindingIndex]; @@ -362,11 +375,12 @@ void ObjCData::setPointerInContent(ld::Internal& state, const ld::Atom* conte template class Category : public ObjCData { public: - static const ld::Atom* getClass(ld::Internal& state, const ld::Atom* contentAtom); + static const ld::Atom* getClass(ld::Internal& state, const ld::Atom* contentAtom, bool& hasAddend); static const ld::Atom* getInstanceMethods(ld::Internal& state, const ld::Atom* contentAtom); static const ld::Atom* getClassMethods(ld::Internal& state, const ld::Atom* contentAtom); static const ld::Atom* getProtocols(ld::Internal& state, const ld::Atom* contentAtom); - static const ld::Atom* getProperties(ld::Internal& state, const ld::Atom* contentAtom); + static const ld::Atom* getInstanceProperties(ld::Internal& state, const ld::Atom* contentAtom); + static const ld::Atom* getClassProperties(ld::Internal& state, const ld::Atom* contentAtom); static uint32_t size() { return 6*sizeof(pint_t); } private: typedef typename A::P::uint_t pint_t; @@ -374,9 +388,9 @@ private: template -const ld::Atom* Category::getClass(ld::Internal& state, const ld::Atom* contentAtom) +const ld::Atom* Category::getClass(ld::Internal& state, const ld::Atom* contentAtom, bool& hasAddend) { - return ObjCData::getPointerInContent(state, contentAtom, sizeof(pint_t)); // category_t.cls + return ObjCData::getPointerInContent(state, contentAtom, sizeof(pint_t), &hasAddend); // category_t.cls } template @@ -398,11 +412,20 @@ const ld::Atom* Category::getProtocols(ld::Internal& state, const ld::Atom* c } template -const ld::Atom* Category::getProperties(ld::Internal& state, const ld::Atom* contentAtom) +const ld::Atom* Category::getInstanceProperties(ld::Internal& state, const ld::Atom* contentAtom) { return ObjCData::getPointerInContent(state, contentAtom, 5*sizeof(pint_t)); // category_t.instanceProperties } +template +const ld::Atom* Category::getClassProperties(ld::Internal& state, const ld::Atom* contentAtom) +{ + // Only specially-marked files have this field. + if (!contentAtom->file()->objcHasCategoryClassPropertiesField()) return NULL; + + return ObjCData::getPointerInContent(state, contentAtom, 6*sizeof(pint_t)); // category_t.classProperties +} + template class MethodList : public ObjCData { @@ -443,10 +466,12 @@ private: template class Class : public ObjCData { public: + static const ld::Atom* getMetaClass(ld::Internal& state, const ld::Atom* classAtom); static const ld::Atom* getInstanceMethodList(ld::Internal& state, const ld::Atom* classAtom); static const ld::Atom* getInstanceProtocolList(ld::Internal& state, const ld::Atom* classAtom); static const ld::Atom* getInstancePropertyList(ld::Internal& state, const ld::Atom* classAtom); static const ld::Atom* getClassMethodList(ld::Internal& state, const ld::Atom* classAtom); + static const ld::Atom* getClassPropertyList(ld::Internal& state, const ld::Atom* classAtom); static const ld::Atom* setInstanceMethodList(ld::Internal& state, const ld::Atom* classAtom, const ld::Atom* methodListAtom, std::set& deadAtoms); static const ld::Atom* setInstanceProtocolList(ld::Internal& state, const ld::Atom* classAtom, @@ -457,74 +482,117 @@ public: const ld::Atom* methodListAtom, std::set& deadAtoms); static const ld::Atom* setClassProtocolList(ld::Internal& state, const ld::Atom* classAtom, const ld::Atom* protocolListAtom, std::set& deadAtoms); - static uint32_t size() { return 5*sizeof(pint_t); } - static unsigned int class_ro_header_size(); + static const ld::Atom* setClassPropertyList(ld::Internal& state, const ld::Atom* classAtom, + const ld::Atom* propertyListAtom, std::set& deadAtoms); + static uint32_t size() { return sizeof(Content); } + private: + friend class ClassROOverlayAtom; + typedef typename A::P::uint_t pint_t; + static const ld::Atom* getROData(ld::Internal& state, const ld::Atom* classAtom); + + struct Content { + pint_t isa; + pint_t superclass; + pint_t method_cache; + pint_t vtable; + pint_t data; + }; + + struct ROContent { + uint32_t flags; + uint32_t instanceStart; + // Note there is 4-bytes of alignment padding between instanceSize + // and ivarLayout on 64-bit archs, but no padding on 32-bit archs. + // This union is a way to model that. + union { + uint32_t instanceSize; + pint_t pad; + } instanceSize; + pint_t ivarLayout; + pint_t name; + pint_t baseMethods; + pint_t baseProtocols; + pint_t ivars; + pint_t weakIvarLayout; + pint_t baseProperties; + }; }; -template <> unsigned int Class::class_ro_header_size() { return 16; } -template <> unsigned int Class::class_ro_header_size() { return 12;} -template <> unsigned int Class::class_ro_header_size() { return 12; } +#define GET_FIELD(state, classAtom, field) \ + ObjCData::getPointerInContent(state, classAtom, offsetof(Content, field)) +#define SET_FIELD(state, classAtom, field, valueAtom) \ + ObjCData::setPointerInContent(state, classAtom, offsetof(Content, field), valueAtom) +#define GET_RO_FIELD(state, classAtom, field) \ + ObjCData::getPointerInContent(state, getROData(state, classAtom), offsetof(ROContent, field)) +#define SET_RO_FIELD(state, classROAtom, field, valueAtom) \ + ObjCData::setPointerInContent(state, getROData(state, classAtom), offsetof(ROContent, field), valueAtom) template -const ld::Atom* Class::getROData(ld::Internal& state, const ld::Atom* classAtom) +const ld::Atom* Class::getMetaClass(ld::Internal& state, const ld::Atom* classAtom) { - return ObjCData::getPointerInContent(state, classAtom, 4*sizeof(pint_t)); // class_t.data + const ld::Atom* metaClassAtom = GET_FIELD(state, classAtom, isa); + assert(metaClassAtom != NULL); + return metaClassAtom; +} +template +const ld::Atom* Class::getROData(ld::Internal& state, const ld::Atom* classAtom) +{ + const ld::Atom* classROAtom = GET_FIELD(state, classAtom, data); + assert(classROAtom != NULL); + return classROAtom; } template const ld::Atom* Class::getInstanceMethodList(ld::Internal& state, const ld::Atom* classAtom) { - const ld::Atom* classROAtom = getROData(state, classAtom); // class_t.data - assert(classROAtom != NULL); - return ObjCData::getPointerInContent(state, classROAtom, class_ro_header_size() + 2*sizeof(pint_t)); // class_ro_t.baseMethods + return GET_RO_FIELD(state, classAtom, baseMethods); } template const ld::Atom* Class::getInstanceProtocolList(ld::Internal& state, const ld::Atom* classAtom) { - const ld::Atom* classROAtom = getROData(state, classAtom); // class_t.data - assert(classROAtom != NULL); - return ObjCData::getPointerInContent(state, classROAtom, class_ro_header_size() + 3*sizeof(pint_t)); // class_ro_t.baseProtocols + return GET_RO_FIELD(state, classAtom, baseProtocols); } template const ld::Atom* Class::getInstancePropertyList(ld::Internal& state, const ld::Atom* classAtom) { - const ld::Atom* classROAtom = getROData(state, classAtom); // class_t.data - assert(classROAtom != NULL); - return ObjCData::getPointerInContent(state, classROAtom, class_ro_header_size() + 6*sizeof(pint_t)); // class_ro_t.baseProperties + return GET_RO_FIELD(state, classAtom, baseProperties); } template const ld::Atom* Class::getClassMethodList(ld::Internal& state, const ld::Atom* classAtom) { - const ld::Atom* metaClassAtom = ObjCData::getPointerInContent(state, classAtom, 0); // class_t.isa - assert(metaClassAtom != NULL); - return Class::getInstanceMethodList(state, metaClassAtom); + return Class::getInstanceMethodList(state, getMetaClass(state, classAtom)); +} + +template +const ld::Atom* Class::getClassPropertyList(ld::Internal& state, const ld::Atom* classAtom) +{ + return Class::getInstancePropertyList(state, getMetaClass(state, classAtom)); } template const ld::Atom* Class::setInstanceMethodList(ld::Internal& state, const ld::Atom* classAtom, const ld::Atom* methodListAtom, std::set& deadAtoms) { - const ld::Atom* classROAtom = getROData(state, classAtom); // class_t.data - assert(classROAtom != NULL); // if the base class does not already have a method list, we need to create an overlay if ( getInstanceMethodList(state, classAtom) == NULL ) { - ClassROOverlayAtom* overlay = new ClassROOverlayAtom(classROAtom); + const ld::Atom* oldROAtom = getROData(state, classAtom); + deadAtoms.insert(oldROAtom); + ClassROOverlayAtom* overlay = new ClassROOverlayAtom(oldROAtom); //fprintf(stderr, "replace class RO atom %p with %p for method list in class atom %s\n", classROAtom, overlay, classAtom->name()); overlay->addMethodListFixup(); - ObjCData::setPointerInContent(state, classAtom, 4*sizeof(pint_t), overlay); // class_t.data - deadAtoms.insert(classROAtom); - ObjCData::setPointerInContent(state, overlay, class_ro_header_size() + 2*sizeof(pint_t), methodListAtom); // class_ro_t.baseMethods + SET_FIELD(state, classAtom, data, overlay); + SET_RO_FIELD(state, classAtom, baseMethods, methodListAtom); return overlay; } - ObjCData::setPointerInContent(state, classROAtom, class_ro_header_size() + 2*sizeof(pint_t), methodListAtom); // class_ro_t.baseMethods + SET_RO_FIELD(state, classAtom, baseMethods, methodListAtom); return NULL; // means classRO atom was not replaced } @@ -532,20 +600,19 @@ template const ld::Atom* Class::setInstanceProtocolList(ld::Internal& state, const ld::Atom* classAtom, const ld::Atom* protocolListAtom, std::set& deadAtoms) { - const ld::Atom* classROAtom = getROData(state, classAtom); // class_t.data - assert(classROAtom != NULL); // if the base class does not already have a protocol list, we need to create an overlay if ( getInstanceProtocolList(state, classAtom) == NULL ) { - ClassROOverlayAtom* overlay = new ClassROOverlayAtom(classROAtom); + const ld::Atom* oldROAtom = getROData(state, classAtom); + deadAtoms.insert(oldROAtom); + ClassROOverlayAtom* overlay = new ClassROOverlayAtom(oldROAtom); //fprintf(stderr, "replace class RO atom %p with %p for protocol list in class atom %s\n", classROAtom, overlay, classAtom->name()); overlay->addProtocolListFixup(); - ObjCData::setPointerInContent(state, classAtom, 4*sizeof(pint_t), overlay); // class_t.data - deadAtoms.insert(classROAtom); - ObjCData::setPointerInContent(state, overlay, class_ro_header_size() + 3*sizeof(pint_t), protocolListAtom); // class_ro_t.baseProtocols + SET_FIELD(state, classAtom, data, overlay); + SET_RO_FIELD(state, classAtom, baseProtocols, protocolListAtom); return overlay; } //fprintf(stderr, "set class RO atom %p protocol list in class atom %s\n", classROAtom, classAtom->name()); - ObjCData::setPointerInContent(state, classROAtom, class_ro_header_size() + 3*sizeof(pint_t), protocolListAtom); // class_ro_t.baseProtocols + SET_RO_FIELD(state, classAtom, baseProtocols, protocolListAtom); return NULL; // means classRO atom was not replaced } @@ -554,9 +621,8 @@ const ld::Atom* Class::setClassProtocolList(ld::Internal& state, const ld::At const ld::Atom* protocolListAtom, std::set& deadAtoms) { // meta class also points to same protocol list as class - const ld::Atom* metaClassAtom = ObjCData::getPointerInContent(state, classAtom, 0); // class_t.isa + const ld::Atom* metaClassAtom = getMetaClass(state, classAtom); //fprintf(stderr, "setClassProtocolList(), classAtom=%p %s, metaClass=%p %s\n", classAtom, classAtom->name(), metaClassAtom, metaClassAtom->name()); - assert(metaClassAtom != NULL); return setInstanceProtocolList(state, metaClassAtom, protocolListAtom, deadAtoms); } @@ -566,19 +632,18 @@ template const ld::Atom* Class::setInstancePropertyList(ld::Internal& state, const ld::Atom* classAtom, const ld::Atom* propertyListAtom, std::set& deadAtoms) { - const ld::Atom* classROAtom = getROData(state, classAtom); // class_t.data - assert(classROAtom != NULL); // if the base class does not already have a property list, we need to create an overlay if ( getInstancePropertyList(state, classAtom) == NULL ) { - ClassROOverlayAtom* overlay = new ClassROOverlayAtom(classROAtom); + const ld::Atom* oldROAtom = getROData(state, classAtom); + deadAtoms.insert(oldROAtom); + ClassROOverlayAtom* overlay = new ClassROOverlayAtom(oldROAtom); //fprintf(stderr, "replace class RO atom %p with %p for property list in class atom %s\n", classROAtom, overlay, classAtom->name()); overlay->addPropertyListFixup(); - ObjCData::setPointerInContent(state, classAtom, 4*sizeof(pint_t), overlay); // class_t.data - deadAtoms.insert(classROAtom); - ObjCData::setPointerInContent(state, overlay, class_ro_header_size() + 6*sizeof(pint_t), propertyListAtom); // class_ro_t.baseProperties + SET_FIELD(state, classAtom, data, overlay); + SET_RO_FIELD(state, classAtom, baseProperties, propertyListAtom); return overlay; } - ObjCData::setPointerInContent(state, classROAtom, class_ro_header_size() + 6*sizeof(pint_t), propertyListAtom); // class_ro_t.baseProperties + SET_RO_FIELD(state, classAtom, baseProperties, propertyListAtom); return NULL; // means classRO atom was not replaced } @@ -587,86 +652,67 @@ const ld::Atom* Class::setClassMethodList(ld::Internal& state, const ld::Atom const ld::Atom* methodListAtom, std::set& deadAtoms) { // class methods is just instance methods of metaClass - const ld::Atom* metaClassAtom = ObjCData::getPointerInContent(state, classAtom, 0); // class_t.isa - assert(metaClassAtom != NULL); - return setInstanceMethodList(state, metaClassAtom, methodListAtom, deadAtoms); + return setInstanceMethodList(state, getMetaClass(state, classAtom), methodListAtom, deadAtoms); } - - -template <> -void ClassROOverlayAtom::addMethodListFixup() -{ - const ld::Atom* targetAtom = this; // temporary - uint32_t offset = Class::class_ro_header_size() + 2*8; // class_ro_t.baseMethods - _fixups.push_back(ld::Fixup(offset, ld::Fixup::k1of1, ld::Fixup::kindStoreTargetAddressLittleEndian64, targetAtom)); -} - -template <> -void ClassROOverlayAtom::addMethodListFixup() +template +const ld::Atom* Class::setClassPropertyList(ld::Internal& state, const ld::Atom* classAtom, + const ld::Atom* propertyListAtom, std::set& deadAtoms) { - const ld::Atom* targetAtom = this; // temporary - uint32_t offset = Class::class_ro_header_size() + 2*4; // class_ro_t.baseMethods - _fixups.push_back(ld::Fixup(offset, ld::Fixup::k1of1, ld::Fixup::kindStoreTargetAddressLittleEndian32, targetAtom)); + // class properties is just instance properties of metaClass + return setInstancePropertyList(state, getMetaClass(state, classAtom), propertyListAtom, deadAtoms); } -template <> -void ClassROOverlayAtom::addMethodListFixup() -{ - const ld::Atom* targetAtom = this; // temporary - uint32_t offset = Class::class_ro_header_size() + 2*4; // class_ro_t.baseMethods - _fixups.push_back(ld::Fixup(offset, ld::Fixup::k1of1, ld::Fixup::kindStoreTargetAddressLittleEndian32, targetAtom)); -} +#undef GET_FIELD +#undef SET_FIELD +#undef GET_RO_FIELD +#undef SET_RO_FIELD +template +ld::Fixup::Kind pointerFixupKind(); -template <> -void ClassROOverlayAtom::addProtocolListFixup() -{ - const ld::Atom* targetAtom = this; // temporary - uint32_t offset = Class::class_ro_header_size() + 3*8; // class_ro_t.baseProtocols - _fixups.push_back(ld::Fixup(offset, ld::Fixup::k1of1, ld::Fixup::kindStoreTargetAddressLittleEndian64, targetAtom)); +template <> +ld::Fixup::Kind pointerFixupKind>() { + return ld::Fixup::kindStoreTargetAddressBigEndian32; } - -template <> -void ClassROOverlayAtom::addProtocolListFixup() -{ - const ld::Atom* targetAtom = this; // temporary - uint32_t offset = Class::class_ro_header_size() + 3*4; // class_ro_t.baseProtocols - _fixups.push_back(ld::Fixup(offset, ld::Fixup::k1of1, ld::Fixup::kindStoreTargetAddressLittleEndian32, targetAtom)); +template <> +ld::Fixup::Kind pointerFixupKind>() { + return ld::Fixup::kindStoreTargetAddressBigEndian64; +} +template <> +ld::Fixup::Kind pointerFixupKind>() { + return ld::Fixup::kindStoreTargetAddressLittleEndian32; +} +template <> +ld::Fixup::Kind pointerFixupKind>() { + return ld::Fixup::kindStoreTargetAddressLittleEndian64; } -template <> -void ClassROOverlayAtom::addProtocolListFixup() +template +void ClassROOverlayAtom::addFixupAtOffset(uint32_t offset) { const ld::Atom* targetAtom = this; // temporary - uint32_t offset = Class::class_ro_header_size() + 3*4; // class_ro_t.baseProtocols - _fixups.push_back(ld::Fixup(offset, ld::Fixup::k1of1, ld::Fixup::kindStoreTargetAddressLittleEndian32, targetAtom)); + _fixups.push_back(ld::Fixup(offset, ld::Fixup::k1of1, pointerFixupKind(), targetAtom)); } -template <> -void ClassROOverlayAtom::addPropertyListFixup() +template +void ClassROOverlayAtom::addMethodListFixup() { - const ld::Atom* targetAtom = this; // temporary - uint32_t offset = Class::class_ro_header_size() + 6*8; // class_ro_t.baseProperties - _fixups.push_back(ld::Fixup(offset, ld::Fixup::k1of1, ld::Fixup::kindStoreTargetAddressLittleEndian64, targetAtom)); + addFixupAtOffset(offsetof(typename Class::ROContent, baseMethods)); } -template <> -void ClassROOverlayAtom::addPropertyListFixup() +template +void ClassROOverlayAtom::addProtocolListFixup() { - const ld::Atom* targetAtom = this; // temporary - uint32_t offset = Class::class_ro_header_size() + 6*4; // class_ro_t.baseProperties - _fixups.push_back(ld::Fixup(offset, ld::Fixup::k1of1, ld::Fixup::kindStoreTargetAddressLittleEndian32, targetAtom)); + addFixupAtOffset(offsetof(typename Class::ROContent, baseProtocols)); } -template <> -void ClassROOverlayAtom::addPropertyListFixup() +template +void ClassROOverlayAtom::addPropertyListFixup() { - const ld::Atom* targetAtom = this; // temporary - uint32_t offset = Class::class_ro_header_size() + 6*4; // class_ro_t.baseProperties - _fixups.push_back(ld::Fixup(offset, ld::Fixup::k1of1, ld::Fixup::kindStoreTargetAddressLittleEndian32, targetAtom)); + addFixupAtOffset(offsetof(typename Class::ROContent, baseProperties)); } @@ -682,8 +728,8 @@ public: static bool hasInstanceMethods(ld::Internal& state, const std::vector* categories); static bool hasClassMethods(ld::Internal& state, const std::vector* categories); static bool hasProtocols(ld::Internal& state, const std::vector* categories); - static bool hasProperties(ld::Internal& state, const std::vector* categories); - + static bool hasInstanceProperties(ld::Internal& state, const std::vector* categories); + static bool hasClassProperties(ld::Internal& state, const std::vector* categories); static unsigned int class_ro_baseMethods_offset(); private: @@ -738,11 +784,26 @@ bool OptimizeCategories::hasProtocols(ld::Internal& state, const std::vector< template -bool OptimizeCategories::hasProperties(ld::Internal& state, const std::vector* categories) +bool OptimizeCategories::hasInstanceProperties(ld::Internal& state, const std::vector* categories) +{ + for (std::vector::const_iterator it=categories->begin(); it != categories->end(); ++it) { + const ld::Atom* categoryAtom = *it; + const ld::Atom* propertyListAtom = Category::getInstanceProperties(state, categoryAtom); + if ( propertyListAtom != NULL ) { + if ( PropertyList::count(state, propertyListAtom) > 0 ) + return true; + } + } + return false; +} + + +template +bool OptimizeCategories::hasClassProperties(ld::Internal& state, const std::vector* categories) { for (std::vector::const_iterator it=categories->begin(); it != categories->end(); ++it) { const ld::Atom* categoryAtom = *it; - const ld::Atom* propertyListAtom = Category::getProperties(state, categoryAtom); + const ld::Atom* propertyListAtom = Category::getClassProperties(state, categoryAtom); if ( propertyListAtom != NULL ) { if ( PropertyList::count(state, propertyListAtom) > 0 ) return true; @@ -752,6 +813,21 @@ bool OptimizeCategories::hasProperties(ld::Internal& state, const std::vector } +static const ld::Atom* fixClassAliases(const ld::Atom* classAtom) +{ + if ( (classAtom->size() != 0) || (classAtom->definition() == ld::Atom::definitionProxy) ) + return classAtom; + + for (ld::Fixup::iterator fit=classAtom->fixupsBegin(); fit != classAtom->fixupsEnd(); ++fit) { + if ( fit->kind == ld::Fixup::kindNoneFollowOn ) { + assert(fit->offsetInAtom == 0); + assert(fit->binding == ld::Fixup::bindingDirectlyBound); + return fit->u.target; + } + } + + return classAtom; +} // // Helper for std::remove_if @@ -795,6 +871,7 @@ private: std::sort(atoms.begin(), atoms.end(), AtomSorter()); } + template void OptimizeCategories::doit(const Options& opts, ld::Internal& state) { @@ -802,7 +879,7 @@ void OptimizeCategories::doit(const Options& opts, ld::Internal& state) std::set nlcatListAtoms; for (std::vector::iterator sit=state.sections.begin(); sit != state.sections.end(); ++sit) { ld::Internal::FinalSection* sect = *sit; - if ( (strcmp(sect->sectionName(), "__objc_nlcatlist") == 0) && (strcmp(sect->segmentName(), "__DATA") == 0) ) { + if ( (strcmp(sect->sectionName(), "__objc_nlcatlist") == 0) && (strncmp(sect->segmentName(), "__DATA", 6) == 0) ) { for (std::vector::iterator ait=sect->atoms.begin(); ait != sect->atoms.end(); ++ait) { const ld::Atom* categoryListElementAtom = *ait; for (unsigned int offset=0; offset < categoryListElementAtom->size(); offset += sizeof(pint_t)) { @@ -838,10 +915,17 @@ void OptimizeCategories::doit(const Options& opts, ld::Internal& state) // ignore categories also in __objc_nlcatlist if ( nlcatListAtoms.count(categoryAtom) != 0 ) continue; - const ld::Atom* categoryOnClassAtom = Category::getClass(state, categoryAtom); + const ld::Atom* categoryOnClassAtom = fixClassAliases(Category::getClass(state, categoryAtom, hasAddend)); assert(categoryOnClassAtom != NULL); + // only look at classes defined in this image if ( categoryOnClassAtom->definition() != ld::Atom::definitionProxy ) { - // only look at classes defined in this image + // for now, back off optimization on new style classes + if ( hasAddend != 0 ) + continue; + // don't apply categories to swift classes + if ( categoryOnClassAtom->hasFixupsOfKind(ld::Fixup::kindNoneGroupSubordinate) ) + continue; + CatMap::iterator pos = classToCategories.find(categoryOnClassAtom); if ( pos == classToCategories.end() ) { classToCategories[categoryOnClassAtom] = new std::vector(); @@ -855,7 +939,7 @@ void OptimizeCategories::doit(const Options& opts, ld::Internal& state) } } // record method list section - if ( (strcmp(sect->sectionName(), "__objc_const") == 0) && (strcmp(sect->segmentName(), "__DATA") == 0) ) + if ( (strcmp(sect->sectionName(), "__objc_const") == 0) && (strncmp(sect->segmentName(), "__DATA", 6) == 0) ) methodListSection = sect; } @@ -875,9 +959,11 @@ void OptimizeCategories::doit(const Options& opts, ld::Internal& state) const ld::Atom* newClassRO = Class::setInstanceMethodList(state, classAtom, newInstanceMethodListAtom, deadAtoms); // add new method list to final sections methodListSection->atoms.push_back(newInstanceMethodListAtom); + state.atomToSection[newInstanceMethodListAtom] = methodListSection; if ( newClassRO != NULL ) { assert(strcmp(newClassRO->section().sectionName(), "__objc_const") == 0); methodListSection->atoms.push_back(newClassRO); + state.atomToSection[newClassRO] = methodListSection; } } // if any category adds class methods, generate new merged method list, and replace @@ -887,9 +973,11 @@ void OptimizeCategories::doit(const Options& opts, ld::Internal& state) const ld::Atom* newClassRO = Class::setClassMethodList(state, classAtom, newClassMethodListAtom, deadAtoms); // add new method list to final sections methodListSection->atoms.push_back(newClassMethodListAtom); + state.atomToSection[newClassMethodListAtom] = methodListSection; if ( newClassRO != NULL ) { assert(strcmp(newClassRO->section().sectionName(), "__objc_const") == 0); methodListSection->atoms.push_back(newClassRO); + state.atomToSection[newClassRO] = methodListSection; } } // if any category adds protocols, generate new merged protocol list, and replace @@ -900,28 +988,46 @@ void OptimizeCategories::doit(const Options& opts, ld::Internal& state) const ld::Atom* newMetaClassRO = Class::setClassProtocolList(state, classAtom, newProtocolListAtom, deadAtoms); // add new protocol list to final sections methodListSection->atoms.push_back(newProtocolListAtom); + state.atomToSection[newProtocolListAtom] = methodListSection; if ( newClassRO != NULL ) { assert(strcmp(newClassRO->section().sectionName(), "__objc_const") == 0); methodListSection->atoms.push_back(newClassRO); + state.atomToSection[newClassRO] = methodListSection; } if ( newMetaClassRO != NULL ) { assert(strcmp(newMetaClassRO->section().sectionName(), "__objc_const") == 0); methodListSection->atoms.push_back(newMetaClassRO); + state.atomToSection[newMetaClassRO] = methodListSection; } } - // if any category adds properties, generate new merged property list, and replace - if ( OptimizeCategories::hasProperties(state, categories) ) { + // if any category adds instance properties, generate new merged property list, and replace + if ( OptimizeCategories::hasInstanceProperties(state, categories) ) { const ld::Atom* basePropertyListAtom = Class::getInstancePropertyList(state, classAtom); - const ld::Atom* newPropertyListAtom = new PropertyListAtom(state, basePropertyListAtom, categories, deadAtoms); + const ld::Atom* newPropertyListAtom = new PropertyListAtom(state, basePropertyListAtom, categories, deadAtoms, PropertyListAtom::PropertyKind::InstanceProperties); const ld::Atom* newClassRO = Class::setInstancePropertyList(state, classAtom, newPropertyListAtom, deadAtoms); // add new property list to final sections methodListSection->atoms.push_back(newPropertyListAtom); + state.atomToSection[newPropertyListAtom] = methodListSection; if ( newClassRO != NULL ) { assert(strcmp(newClassRO->section().sectionName(), "__objc_const") == 0); methodListSection->atoms.push_back(newClassRO); + state.atomToSection[newClassRO] = methodListSection; } } - + // if any category adds class properties, generate new merged property list, and replace + if ( OptimizeCategories::hasClassProperties(state, categories) ) { + const ld::Atom* basePropertyListAtom = Class::getClassPropertyList(state, classAtom); + const ld::Atom* newPropertyListAtom = new PropertyListAtom(state, basePropertyListAtom, categories, deadAtoms, PropertyListAtom::PropertyKind::ClassProperties); + const ld::Atom* newClassRO = Class::setClassPropertyList(state, classAtom, newPropertyListAtom, deadAtoms); + // add new property list to final sections + methodListSection->atoms.push_back(newPropertyListAtom); + state.atomToSection[newPropertyListAtom] = methodListSection; + if ( newClassRO != NULL ) { + assert(strcmp(newClassRO->section().sectionName(), "__objc_const") == 0); + methodListSection->atoms.push_back(newClassRO); + state.atomToSection[newClassRO] = methodListSection; + } + } } // remove dead atoms @@ -933,6 +1039,7 @@ void OptimizeCategories::doit(const Options& opts, ld::Internal& state) } + template MethodListAtom::MethodListAtom(ld::Internal& state, const ld::Atom* baseMethodList, bool meta, const std::vector* categories, std::set& deadAtoms) @@ -1086,7 +1193,7 @@ ProtocolListAtom::ProtocolListAtom(ld::Internal& state, const ld::Atom* baseP template PropertyListAtom::PropertyListAtom(ld::Internal& state, const ld::Atom* basePropertyList, - const std::vector* categories, std::set& deadAtoms) + const std::vector* categories, std::set& deadAtoms, PropertyKind kind) : ld::Atom(_s_section, ld::Atom::definitionRegular, ld::Atom::combineNever, ld::Atom::scopeLinkageUnit, ld::Atom::typeUnclassified, symbolTableNotIn, false, false, false, ld::Atom::Alignment(3)), _file(NULL), _propertyCount(0) @@ -1101,7 +1208,7 @@ PropertyListAtom::PropertyListAtom(ld::Internal& state, const ld::Atom* baseP fixupCount = basePropertyList->fixupsEnd() - basePropertyList->fixupsBegin(); } for (std::vector::const_iterator ait=categories->begin(); ait != categories->end(); ++ait) { - const ld::Atom* categoryPropertyListAtom = Category::getProperties(state, *ait); + const ld::Atom* categoryPropertyListAtom = kind == PropertyKind::ClassProperties ? Category::getClassProperties(state, *ait) : Category::getInstanceProperties(state, *ait); if ( categoryPropertyListAtom != NULL ) { _propertyCount += PropertyList::count(state, categoryPropertyListAtom); fixupCount += (categoryPropertyListAtom->fixupsEnd() - categoryPropertyListAtom->fixupsBegin()); @@ -1118,7 +1225,7 @@ PropertyListAtom::PropertyListAtom(ld::Internal& state, const ld::Atom* baseP _fixups.reserve(fixupCount); uint32_t slide = 0; for (std::vector::const_iterator it=categories->begin(); it != categories->end(); ++it) { - const ld::Atom* categoryPropertyListAtom = Category::getProperties(state, *it); + const ld::Atom* categoryPropertyListAtom = kind == PropertyKind::ClassProperties ? Category::getClassProperties(state, *it) : Category::getInstanceProperties(state, *it); if ( categoryPropertyListAtom != NULL ) { for (ld::Fixup::iterator fit=categoryPropertyListAtom->fixupsBegin(); fit != categoryPropertyListAtom->fixupsEnd(); ++fit) { ld::Fixup fixup = *fit; @@ -1145,85 +1252,112 @@ PropertyListAtom::PropertyListAtom(ld::Internal& state, const ld::Atom* baseP } +template +void scanCategories(ld::Internal& state, + bool& haveCategoriesWithNonNullClassProperties, + bool& haveCategoriesWithoutClassPropertyStorage) +{ + haveCategoriesWithNonNullClassProperties = false; + haveCategoriesWithoutClassPropertyStorage = false; + + for (std::vector::iterator sit=state.sections.begin(); sit != state.sections.end(); ++sit) { + ld::Internal::FinalSection* sect = *sit; + if ( sect->type() == ld::Section::typeObjC2CategoryList ) { + for (std::vector::iterator ait=sect->atoms.begin(); ait != sect->atoms.end(); ++ait) { + const ld::Atom* categoryListElementAtom = *ait; + bool hasAddend; + const ld::Atom* categoryAtom = ObjCData::getPointerInContent(state, categoryListElementAtom, 0, &hasAddend); + + if (Category::getClassProperties(state, categoryAtom)) { + haveCategoriesWithNonNullClassProperties = true; + // fprintf(stderr, "category in file %s has non-null class properties\n", categoryAtom->file()->path()); + } + + if (!categoryAtom->file()->objcHasCategoryClassPropertiesField()) { + haveCategoriesWithoutClassPropertyStorage = true; + // fprintf(stderr, "category in file %s has no storage for class properties\n", categoryAtom->file()->path()); + } + } + } + } +} +template void doPass(const Options& opts, ld::Internal& state) -{ - // only make image info section if objc was used - if ( state.objcObjectConstraint != ld::File::objcConstraintNone ) { - - // verify dylibs are GC compatible with object files - if ( state.objcObjectConstraint != state.objcDylibConstraint ) { - if ( (state.objcDylibConstraint == ld::File::objcConstraintRetainRelease) - && (state.objcObjectConstraint == ld::File::objcConstraintGC) ) { - throw "Linked dylibs built for retain/release but object files built for GC-only"; - } - else if ( (state.objcDylibConstraint == ld::File::objcConstraintGC) - && (state.objcObjectConstraint == ld::File::objcConstraintRetainRelease) ) { - throw "Linked dylibs built for GC-only but object files built for retain/release"; - } +{ + // Do nothing if the output has no ObjC content. + if ( state.objcObjectConstraint == ld::File::objcConstraintNone ) { + return; + } + + // verify dylibs are GC compatible with object files + if ( state.objcObjectConstraint != state.objcDylibConstraint ) { + if ( (state.objcDylibConstraint == ld::File::objcConstraintRetainRelease) + && (state.objcObjectConstraint == ld::File::objcConstraintGC) ) { + throw "Linked dylibs built for retain/release but object files built for GC-only"; + } + else if ( (state.objcDylibConstraint == ld::File::objcConstraintGC) + && (state.objcObjectConstraint == ld::File::objcConstraintRetainRelease) ) { + throw "Linked dylibs built for GC-only but object files built for retain/release"; } - - const bool compaction = opts.objcGcCompaction(); - - // add image info atom - switch ( opts.architecture() ) { -#if SUPPORT_ARCH_x86_64 - case CPU_TYPE_X86_64: - state.addAtom(*new ObjCImageInfoAtom(state.objcObjectConstraint, compaction, - true)); - break; -#endif -#if SUPPORT_ARCH_i386 - case CPU_TYPE_I386: - state.addAtom(*new ObjCImageInfoAtom(state.objcObjectConstraint, compaction, - opts.objCABIVersion2POverride() ? true : false)); - break; -#endif -#if SUPPORT_ARCH_arm_any - case CPU_TYPE_ARM: - state.addAtom(*new ObjCImageInfoAtom(state.objcObjectConstraint, compaction, - true)); - break; -#endif -#if SUPPORT_ARCH_arm64 - case CPU_TYPE_ARM64: - state.addAtom(*new ObjCImageInfoAtom(state.objcObjectConstraint, compaction, - true)); - break; -#endif - default: - assert(0 && "unknown objc arch"); - } } - + if ( opts.objcCategoryMerging() ) { // optimize classes defined in this linkage unit by merging in categories also in this linkage unit - switch ( opts.architecture() ) { + OptimizeCategories::doit(opts, state); + } + + // Search for surviving categories that have a non-null class properties field. + // Search for surviving categories that do not have storage for the class properties field. + bool haveCategoriesWithNonNullClassProperties; + bool haveCategoriesWithoutClassPropertyStorage; + scanCategories(state, haveCategoriesWithNonNullClassProperties, haveCategoriesWithoutClassPropertyStorage); + + // Complain about mismatched category ABI. + // These can't be combined into a single linkage unit because there is only one size indicator for all categories in the file. + // If there is a mismatch then we don't set the HasCategoryClassProperties bit in the output file, + // which has at runtime causes any class property metadata that was present to be ignored. + if (haveCategoriesWithNonNullClassProperties && haveCategoriesWithoutClassPropertyStorage) { + warning("Some object files have incompatible Objective-C category definitions. Some category metadata may be lost. All files containing Objective-C categories should be built using the same compiler."); + } + + // add image info atom + // The HasCategoryClassProperties bit is set as often as possible. + state.addAtom(*new ObjCImageInfoAtom(state.objcObjectConstraint, opts.objcGcCompaction(), isObjC2, + !haveCategoriesWithoutClassPropertyStorage, state.swiftVersion)); +} + + +void doPass(const Options& opts, ld::Internal& state) +{ + switch ( opts.architecture() ) { #if SUPPORT_ARCH_x86_64 - case CPU_TYPE_X86_64: - OptimizeCategories::doit(opts, state); - break; + case CPU_TYPE_X86_64: + doPass(opts, state); + break; #endif #if SUPPORT_ARCH_i386 - case CPU_TYPE_I386: - if ( opts.objCABIVersion2POverride() ) - OptimizeCategories::doit(opts, state); - break; + case CPU_TYPE_I386: + if (opts.objCABIVersion2POverride()) { + doPass(opts, state); + } else { + doPass(opts, state); + } + break; #endif #if SUPPORT_ARCH_arm_any - case CPU_TYPE_ARM: - OptimizeCategories::doit(opts, state); - break; + case CPU_TYPE_ARM: + doPass(opts, state); + break; #endif #if SUPPORT_ARCH_arm64 - case CPU_TYPE_ARM64: - // disabled until tested - break; + case CPU_TYPE_ARM64: + doPass(opts, state); + break; #endif - default: - assert(0 && "unknown objc arch"); - } + default: + assert(0 && "unknown objc arch"); } }