//===- ObjC.cpp -----------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "ObjC.h" #include "InputFiles.h" #include "InputSection.h" #include "Layout.h" #include "OutputSegment.h" #include "Target.h" #include "lld/Common/ErrorHandler.h" #include "llvm/ADT/DenseMap.h" #include "llvm/BinaryFormat/MachO.h" #include "llvm/Bitcode/BitcodeReader.h" using namespace llvm; using namespace llvm::MachO; using namespace lld; using namespace lld::macho; template static bool objectHasObjCSection(MemoryBufferRef mb) { using SectionHeader = typename LP::section; auto *hdr = reinterpret_cast(mb.getBufferStart()); if (hdr->magic != LP::magic) return false; if (const auto *c = findCommand(hdr, LP::segmentLCType)) { auto sectionHeaders = ArrayRef{ reinterpret_cast(c + 1), c->nsects}; for (const SectionHeader &secHead : sectionHeaders) { StringRef sectname(secHead.sectname, strnlen(secHead.sectname, sizeof(secHead.sectname))); StringRef segname(secHead.segname, strnlen(secHead.segname, sizeof(secHead.segname))); if ((segname == segment_names::data && sectname == section_names::objcCatList) || (segname == segment_names::text && sectname.starts_with(section_names::swift))) { return true; } } } return false; } static bool objectHasObjCSection(MemoryBufferRef mb) { if (target->wordSize == 8) return ::objectHasObjCSection(mb); else return ::objectHasObjCSection(mb); } bool macho::hasObjCSection(MemoryBufferRef mb) { switch (identify_magic(mb.getBuffer())) { case file_magic::macho_object: return objectHasObjCSection(mb); case file_magic::bitcode: return check(isBitcodeContainingObjCCategory(mb)); default: return false; } } namespace { #define FOR_EACH_CATEGORY_FIELD(DO) \ DO(Ptr, name) \ DO(Ptr, klass) \ DO(Ptr, instanceMethods) \ DO(Ptr, classMethods) \ DO(Ptr, protocols) \ DO(Ptr, instanceProps) \ DO(Ptr, classProps) CREATE_LAYOUT_CLASS(Category, FOR_EACH_CATEGORY_FIELD); #undef FOR_EACH_CATEGORY_FIELD #define FOR_EACH_CLASS_FIELD(DO) \ DO(Ptr, metaClass) \ DO(Ptr, superClass) \ DO(Ptr, methodCache) \ DO(Ptr, vtable) \ DO(Ptr, roData) CREATE_LAYOUT_CLASS(Class, FOR_EACH_CLASS_FIELD); #undef FOR_EACH_CLASS_FIELD #define FOR_EACH_RO_CLASS_FIELD(DO) \ DO(uint32_t, flags) \ DO(uint32_t, instanceStart) \ DO(Ptr, instanceSize) \ DO(Ptr, ivarLayout) \ DO(Ptr, name) \ DO(Ptr, baseMethods) \ DO(Ptr, baseProtocols) \ DO(Ptr, ivars) \ DO(Ptr, weakIvarLayout) \ DO(Ptr, baseProperties) CREATE_LAYOUT_CLASS(ROClass, FOR_EACH_RO_CLASS_FIELD); #undef FOR_EACH_RO_CLASS_FIELD #define FOR_EACH_LIST_HEADER(DO) \ DO(uint32_t, size) \ DO(uint32_t, count) CREATE_LAYOUT_CLASS(ListHeader, FOR_EACH_LIST_HEADER); #undef FOR_EACH_LIST_HEADER #define FOR_EACH_METHOD(DO) \ DO(Ptr, name) \ DO(Ptr, type) \ DO(Ptr, impl) CREATE_LAYOUT_CLASS(Method, FOR_EACH_METHOD); #undef FOR_EACH_METHOD enum MethodContainerKind { MCK_Class, MCK_Category, }; struct MethodContainer { MethodContainerKind kind; const ConcatInputSection *isec; }; enum MethodKind { MK_Instance, MK_Static, }; struct ObjcClass { DenseMap instanceMethods; DenseMap classMethods; }; } // namespace class ObjcCategoryChecker { public: ObjcCategoryChecker(); void parseCategory(const ConcatInputSection *catListIsec); private: void parseClass(const Defined *classSym); void parseMethods(const ConcatInputSection *methodsIsec, const Symbol *methodContainer, const ConcatInputSection *containerIsec, MethodContainerKind, MethodKind); CategoryLayout catLayout; ClassLayout classLayout; ROClassLayout roClassLayout; ListHeaderLayout listHeaderLayout; MethodLayout methodLayout; DenseMap classMap; }; ObjcCategoryChecker::ObjcCategoryChecker() : catLayout(target->wordSize), classLayout(target->wordSize), roClassLayout(target->wordSize), listHeaderLayout(target->wordSize), methodLayout(target->wordSize) {} // \p r must point to an offset within a cstring section. static StringRef getReferentString(const Reloc &r) { if (auto *isec = r.referent.dyn_cast()) return cast(isec)->getStringRefAtOffset(r.addend); auto *sym = cast(r.referent.get()); return cast(sym->isec)->getStringRefAtOffset(sym->value + r.addend); } void ObjcCategoryChecker::parseMethods(const ConcatInputSection *methodsIsec, const Symbol *methodContainerSym, const ConcatInputSection *containerIsec, MethodContainerKind mcKind, MethodKind mKind) { ObjcClass &klass = classMap[methodContainerSym]; for (const Reloc &r : methodsIsec->relocs) { if ((r.offset - listHeaderLayout.totalSize) % methodLayout.totalSize != methodLayout.nameOffset) continue; CachedHashStringRef methodName(getReferentString(r)); // +load methods are special: all implementations are called by the runtime // even if they are part of the same class. Thus there is no need to check // for duplicates. // NOTE: Instead of specifically checking for this method name, ld64 simply // checks whether a class / category is present in __objc_nlclslist / // __objc_nlcatlist respectively. This will be the case if the class / // category has a +load method. It skips optimizing the categories if there // are multiple +load methods. Since it does dupe checking as part of the // optimization process, this avoids spurious dupe messages around +load, // but it also means that legit dupe issues for other methods are ignored. if (mKind == MK_Static && methodName.val() == "load") continue; auto &methodMap = mKind == MK_Instance ? klass.instanceMethods : klass.classMethods; if (methodMap .try_emplace(methodName, MethodContainer{mcKind, containerIsec}) .second) continue; // We have a duplicate; generate a warning message. const auto &mc = methodMap.lookup(methodName); const Reloc *nameReloc = nullptr; if (mc.kind == MCK_Category) { nameReloc = mc.isec->getRelocAt(catLayout.nameOffset); } else { assert(mc.kind == MCK_Class); const auto *roIsec = mc.isec->getRelocAt(classLayout.roDataOffset) ->getReferentInputSection(); nameReloc = roIsec->getRelocAt(roClassLayout.nameOffset); } StringRef containerName = getReferentString(*nameReloc); StringRef methPrefix = mKind == MK_Instance ? "-" : "+"; // We should only ever encounter collisions when parsing category methods // (since the Class struct is parsed before any of its categories). assert(mcKind == MCK_Category); StringRef newCatName = getReferentString(*containerIsec->getRelocAt(catLayout.nameOffset)); StringRef containerType = mc.kind == MCK_Category ? "category" : "class"; warn("method '" + methPrefix + methodName.val() + "' has conflicting definitions:\n>>> defined in category " + newCatName + " from " + toString(containerIsec->getFile()) + "\n>>> defined in " + containerType + " " + containerName + " from " + toString(mc.isec->getFile())); } } void ObjcCategoryChecker::parseCategory(const ConcatInputSection *catIsec) { auto *classReloc = catIsec->getRelocAt(catLayout.klassOffset); if (!classReloc) return; auto *classSym = classReloc->referent.get(); if (auto *d = dyn_cast(classSym)) if (!classMap.count(d)) parseClass(d); if (const auto *r = catIsec->getRelocAt(catLayout.classMethodsOffset)) { parseMethods(cast(r->getReferentInputSection()), classSym, catIsec, MCK_Category, MK_Static); } if (const auto *r = catIsec->getRelocAt(catLayout.instanceMethodsOffset)) { parseMethods(cast(r->getReferentInputSection()), classSym, catIsec, MCK_Category, MK_Instance); } } void ObjcCategoryChecker::parseClass(const Defined *classSym) { // Given a Class struct, get its corresponding Methods struct auto getMethodsIsec = [&](const InputSection *classIsec) -> ConcatInputSection * { if (const auto *r = classIsec->getRelocAt(classLayout.roDataOffset)) { if (const auto *roIsec = cast_or_null(r->getReferentInputSection())) { if (const auto *r = roIsec->getRelocAt(roClassLayout.baseMethodsOffset)) { if (auto *methodsIsec = cast_or_null( r->getReferentInputSection())) return methodsIsec; } } } return nullptr; }; const auto *classIsec = cast(classSym->isec); // Parse instance methods. if (const auto *instanceMethodsIsec = getMethodsIsec(classIsec)) parseMethods(instanceMethodsIsec, classSym, classIsec, MCK_Class, MK_Instance); // Class methods are contained in the metaclass. if (const auto *r = classSym->isec->getRelocAt(classLayout.metaClassOffset)) if (const auto *classMethodsIsec = getMethodsIsec( cast(r->getReferentInputSection()))) parseMethods(classMethodsIsec, classSym, classIsec, MCK_Class, MK_Static); } void objc::checkCategories() { ObjcCategoryChecker checker; for (const InputSection *isec : inputSections) { if (isec->getName() == section_names::objcCatList) for (const Reloc &r : isec->relocs) { auto *catIsec = cast(r.getReferentInputSection()); checker.parseCategory(catIsec); } } }