mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2024-11-27 15:41:46 +00:00
8d85c96e0e
The latter form is now preferred to be similar to C++20 starts_with. This replacement also removes one function call when startswith is not inlined.
312 lines
12 KiB
C++
312 lines
12 KiB
C++
//===- 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 <class LP> static bool objectHasObjCSection(MemoryBufferRef mb) {
|
|
using SectionHeader = typename LP::section;
|
|
|
|
auto *hdr =
|
|
reinterpret_cast<const typename LP::mach_header *>(mb.getBufferStart());
|
|
if (hdr->magic != LP::magic)
|
|
return false;
|
|
|
|
if (const auto *c =
|
|
findCommand<typename LP::segment_command>(hdr, LP::segmentLCType)) {
|
|
auto sectionHeaders = ArrayRef<SectionHeader>{
|
|
reinterpret_cast<const SectionHeader *>(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<LP64>(mb);
|
|
else
|
|
return ::objectHasObjCSection<ILP32>(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<CachedHashStringRef, MethodContainer> instanceMethods;
|
|
DenseMap<CachedHashStringRef, MethodContainer> 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<const Symbol *, ObjcClass> 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<InputSection *>())
|
|
return cast<CStringInputSection>(isec)->getStringRefAtOffset(r.addend);
|
|
auto *sym = cast<Defined>(r.referent.get<Symbol *>());
|
|
return cast<CStringInputSection>(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<Symbol *>();
|
|
if (auto *d = dyn_cast<Defined>(classSym))
|
|
if (!classMap.count(d))
|
|
parseClass(d);
|
|
|
|
if (const auto *r = catIsec->getRelocAt(catLayout.classMethodsOffset)) {
|
|
parseMethods(cast<ConcatInputSection>(r->getReferentInputSection()),
|
|
classSym, catIsec, MCK_Category, MK_Static);
|
|
}
|
|
|
|
if (const auto *r = catIsec->getRelocAt(catLayout.instanceMethodsOffset)) {
|
|
parseMethods(cast<ConcatInputSection>(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<ConcatInputSection>(r->getReferentInputSection())) {
|
|
if (const auto *r =
|
|
roIsec->getRelocAt(roClassLayout.baseMethodsOffset)) {
|
|
if (auto *methodsIsec = cast_or_null<ConcatInputSection>(
|
|
r->getReferentInputSection()))
|
|
return methodsIsec;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
};
|
|
|
|
const auto *classIsec = cast<ConcatInputSection>(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<ConcatInputSection>(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<ConcatInputSection>(r.getReferentInputSection());
|
|
checker.parseCategory(catIsec);
|
|
}
|
|
}
|
|
}
|