X-Git-Url: https://git.saurik.com/apple/ld64.git/blobdiff_plain/b2fa67a80bc53211e4d1ea81f23e9f953ee1dd6c..7f09b9353af9897bf18933788d6a59c152c29edd:/src/ld/passes/objc.cpp diff --git a/src/ld/passes/objc.cpp b/src/ld/passes/objc.cpp index b209b1d..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_IS_REPLACEMENT (1<<0) -#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_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,11 +66,9 @@ template class ObjCImageInfoAtom : public ld::Atom { public: ObjCImageInfoAtom(ld::File::ObjcConstraint objcConstraint, - bool compaction, bool objcReplacementClasses, bool abi2); + bool compaction, bool abi2, bool hasCategoryClassProperties, uint8_t swiftVersion); virtual const ld::File* file() const { return NULL; } - virtual bool translationUnitSource(const char** dir, const char**) const - { return false; } virtual const char* name() const { return "objc image info"; } virtual uint64_t size() const { return sizeof(objc_image_info); } virtual uint64_t objectAddress() const { return 0; } @@ -91,15 +90,13 @@ template ld::Section ObjCImageInfoAtom::_s_sectionABI2("__DATA", template ObjCImageInfoAtom::ObjCImageInfoAtom(ld::File::ObjcConstraint objcConstraint, bool compaction, - bool objcReplacementClasses, 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)) { uint32_t value = 0; - if ( objcReplacementClasses ) - value = OBJC_IMAGE_IS_REPLACEMENT; switch ( objcConstraint ) { case ld::File::objcConstraintNone: case ld::File::objcConstraintRetainRelease: @@ -116,8 +113,18 @@ ObjCImageInfoAtom::ObjCImageInfoAtom(ld::File::ObjcConstraint objcConstraint, if ( compaction ) value |= OBJC_IMAGE_SUPPORTS_COMPACTION; break; + case ld::File::objcConstraintRetainReleaseForSimulator: + value |= OBJC_IMAGE_IS_SIMULATED; + 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); } @@ -135,15 +142,13 @@ public: std::set& deadAtoms); virtual const ld::File* file() const { return _file; } - virtual bool translationUnitSource(const char** dir, const char**) const - { return false; } virtual const char* name() const { return "objc merged method list"; } virtual uint64_t size() const { return _methodCount*3*sizeof(pint_t) + 8; } virtual uint64_t objectAddress() const { return 0; } virtual void setScope(Scope) { } virtual void copyRawContent(uint8_t buffer[]) const { bzero(buffer, size()); - A::P::E::set32(*((uint32_t*)(&buffer[0])), 24); + A::P::E::set32(*((uint32_t*)(&buffer[0])), 3*sizeof(pint_t)); // entry size A::P::E::set32(*((uint32_t*)(&buffer[4])), _methodCount); } virtual ld::Fixup::iterator fixupsBegin() const { return (ld::Fixup*)&_fixups[0]; } @@ -174,8 +179,6 @@ public: std::set& deadAtoms); virtual const ld::File* file() const { return _file; } - virtual bool translationUnitSource(const char** dir, const char**) const - { return false; } virtual const char* name() const { return "objc merged protocol list"; } virtual uint64_t size() const { return (_protocolCount+1)*sizeof(pint_t); } virtual uint64_t objectAddress() const { return 0; } @@ -208,13 +211,14 @@ 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 bool translationUnitSource(const char** dir, const char**) const - { return false; } virtual const char* name() const { return "objc merged property list"; } virtual uint64_t size() const { return _propertyCount*2*sizeof(pint_t) + 8; } virtual uint64_t objectAddress() const { return 0; } @@ -255,8 +259,6 @@ public: // overrides of ld::Atom virtual const ld::File* file() const { return _atom->file(); } - virtual bool translationUnitSource(const char** dir, const char** nm) const - { return _atom->translationUnitSource(dir, nm); } virtual const char* name() const { return _atom->name(); } virtual uint64_t size() const { return _atom->size(); } virtual uint64_t objectAddress() const { return _atom->objectAddress(); } @@ -279,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; }; @@ -320,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]; @@ -371,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; @@ -383,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 @@ -407,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 { @@ -452,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, @@ -466,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 } @@ -541,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 } @@ -563,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); } @@ -575,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 } @@ -596,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)); } @@ -691,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: @@ -747,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; @@ -761,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 @@ -775,6 +842,36 @@ private: const std::set& _dead; }; + struct AtomSorter + { + bool operator()(const Atom* left, const Atom* right) + { + // sort by file ordinal, then object address, then zero size, then symbol name + // only file based atoms are supported (file() != NULL) + if (left==right) return false; + const File *leftf = left->file(); + const File *rightf = right->file(); + + if (leftf == rightf) { + if (left->objectAddress() != right->objectAddress()) { + return left->objectAddress() < right->objectAddress(); + } else { + // for atoms in the same file with the same address, zero sized + // atoms must sort before nonzero sized atoms + if ((left->size() == 0 && right->size() > 0) || (left->size() > 0 && right->size() == 0)) + return left->size() < right->size(); + return strcmp(left->name(), right->name()); + } + } + return (leftf->ordinal() < rightf->ordinal()); + } + }; + + static void sortAtomVector(std::vector &atoms) { + std::sort(atoms.begin(), atoms.end(), AtomSorter()); + } + + template void OptimizeCategories::doit(const Options& opts, ld::Internal& state) { @@ -782,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)) { @@ -798,6 +895,7 @@ void OptimizeCategories::doit(const Options& opts, ld::Internal& state) // build map of all classes in this image that have categories on them typedef std::map*> CatMap; CatMap classToCategories; + std::vector classOrder; std::set deadAtoms; ld::Internal::FinalSection* methodListSection = NULL; for (std::vector::iterator sit=state.sections.begin(); sit != state.sections.end(); ++sit) { @@ -813,17 +911,25 @@ void OptimizeCategories::doit(const Options& opts, ld::Internal& state) continue; } assert(categoryAtom != NULL); - assert(categoryAtom->size() == Category::size()); + assert(categoryAtom->size() >= Category::size()); // 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(); + classOrder.push_back(categoryOnClassAtom); } classToCategories[categoryOnClassAtom]->push_back(categoryAtom); // mark category atom and catlist atom as dead @@ -833,17 +939,18 @@ 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; } // if found some categories if ( classToCategories.size() != 0 ) { assert(methodListSection != NULL); + sortAtomVector(classOrder); // alter each class definition to have new method list which includes all category methods - for (CatMap::iterator it=classToCategories.begin(); it != classToCategories.end(); ++it) { - const ld::Atom* classAtom = it->first; - const std::vector* categories = it->second; + for (std::vector::iterator it = classOrder.begin(); it != classOrder.end(); it++) { + const ld::Atom* classAtom = *it; + const std::vector* categories = classToCategories[classAtom]; assert(categories->size() != 0); // if any category adds instance methods, generate new merged method list, and replace if ( OptimizeCategories::hasInstanceMethods(state, categories) ) { @@ -852,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 @@ -864,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 @@ -877,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 @@ -910,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) @@ -1063,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) @@ -1078,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()); @@ -1095,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; @@ -1122,64 +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() ) { - case CPU_TYPE_X86_64: - state.addAtom(*new ObjCImageInfoAtom(state.objcObjectConstraint, compaction, - state.hasObjcReplacementClasses, true)); - break; - case CPU_TYPE_I386: - state.addAtom(*new ObjCImageInfoAtom(state.objcObjectConstraint, compaction, - state.hasObjcReplacementClasses, opts.objCABIVersion2POverride() ? true : false)); - break; - case CPU_TYPE_ARM: - state.addAtom(*new ObjCImageInfoAtom(state.objcObjectConstraint, compaction, - state.hasObjcReplacementClasses, true)); - break; - 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() ) { - case CPU_TYPE_X86_64: - OptimizeCategories::doit(opts, state); - break; - case CPU_TYPE_I386: - // disable optimization until fully tested - //if ( opts.objCABIVersion2POverride() ) - // OptimizeCategories::doit(opts, state); - break; - case CPU_TYPE_ARM: - // disable optimization until fully tested - //OptimizeCategories::doit(opts, state); - break; - default: - assert(0 && "unknown objc arch"); - } + 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: + doPass(opts, state); + break; +#endif +#if SUPPORT_ARCH_i386 + 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: + doPass(opts, state); + break; +#endif +#if SUPPORT_ARCH_arm64 + case CPU_TYPE_ARM64: + doPass(opts, state); + break; +#endif + default: + assert(0 && "unknown objc arch"); } }