mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-18 09:06:43 +00:00
[lld-macho][re-land] Warn on method name collisions from category definitions
This implements ld64's checks for duplicate method names in categories & classes. In addition, this sets us up for implementing Obj-C category merging. This diff handles the most of the parsing work; what's left is rewriting those category / class structures. Numbers for chromium_framework: base diff difference (95% CI) sys_time 2.182 ± 0.027 2.200 ± 0.047 [ -0.2% .. +1.8%] user_time 6.451 ± 0.034 6.479 ± 0.062 [ -0.0% .. +0.9%] wall_time 6.841 ± 0.048 6.885 ± 0.105 [ -0.1% .. +1.4%] samples 33 22 Fixes https://github.com/llvm/llvm-project/issues/54912. Issues seen with the previous land will be fixed in the next commit. Reviewed By: #lld-macho, thevinster, oontvoo Differential Revision: https://reviews.llvm.org/D142916
This commit is contained in:
parent
3fbc6fd493
commit
453102a028
@ -1921,6 +1921,8 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
|
||||
if (config->deadStrip)
|
||||
markLive();
|
||||
|
||||
objc::checkCategories();
|
||||
|
||||
// ICF assumes that all literals have been folded already, so we must run
|
||||
// foldIdenticalLiterals before foldIdenticalSections.
|
||||
foldIdenticalLiterals();
|
||||
|
@ -1254,13 +1254,10 @@ static CIE parseCIE(const InputSection *isec, const EhReader &reader,
|
||||
}
|
||||
}
|
||||
if (personalityAddrOff != 0) {
|
||||
auto personalityRelocIt =
|
||||
llvm::find_if(isec->relocs, [=](const macho::Reloc &r) {
|
||||
return r.offset == personalityAddrOff;
|
||||
});
|
||||
if (personalityRelocIt == isec->relocs.end())
|
||||
const auto *personalityReloc = isec->getRelocAt(personalityAddrOff);
|
||||
if (!personalityReloc)
|
||||
reader.failOn(off, "Failed to locate relocation for personality symbol");
|
||||
cie.personalitySymbol = personalityRelocIt->referent.get<macho::Symbol *>();
|
||||
cie.personalitySymbol = personalityReloc->referent.get<macho::Symbol *>();
|
||||
}
|
||||
return cie;
|
||||
}
|
||||
|
@ -135,6 +135,14 @@ std::string InputSection::getSourceLocation(uint64_t off) const {
|
||||
return {};
|
||||
}
|
||||
|
||||
const Reloc *InputSection::getRelocAt(uint32_t off) const {
|
||||
auto it = llvm::find_if(
|
||||
relocs, [=](const macho::Reloc &r) { return r.offset == off; });
|
||||
if (it == relocs.end())
|
||||
return nullptr;
|
||||
return &*it;
|
||||
}
|
||||
|
||||
void ConcatInputSection::foldIdentical(ConcatInputSection *copy) {
|
||||
align = std::max(align, copy->align);
|
||||
copy->live = false;
|
||||
@ -259,6 +267,15 @@ const StringPiece &CStringInputSection::getStringPiece(uint64_t off) const {
|
||||
return const_cast<CStringInputSection *>(this)->getStringPiece(off);
|
||||
}
|
||||
|
||||
size_t CStringInputSection::getStringPieceIndex(uint64_t off) const {
|
||||
if (off >= data.size())
|
||||
fatal(toString(this) + ": offset is outside the section");
|
||||
|
||||
auto it =
|
||||
partition_point(pieces, [=](StringPiece p) { return p.inSecOff <= off; });
|
||||
return std::distance(pieces.begin(), it) - 1;
|
||||
}
|
||||
|
||||
uint64_t CStringInputSection::getOffset(uint64_t off) const {
|
||||
const StringPiece &piece = getStringPiece(off);
|
||||
uint64_t addend = off - piece.inSecOff;
|
||||
|
@ -55,6 +55,8 @@ public:
|
||||
// Return the source line corresponding to an address, or the empty string.
|
||||
// Format: Source.cpp:123 (/path/to/Source.cpp:123)
|
||||
std::string getSourceLocation(uint64_t off) const;
|
||||
// Return the relocation at \p off, if it exists. This does a linear search.
|
||||
const Reloc *getRelocAt(uint32_t off) const;
|
||||
// Whether the data at \p off in this InputSection is live.
|
||||
virtual bool isLive(uint64_t off) const = 0;
|
||||
virtual void markLive(uint64_t off) = 0;
|
||||
@ -218,6 +220,10 @@ public:
|
||||
return toStringRef(data.slice(begin, end - begin));
|
||||
}
|
||||
|
||||
StringRef getStringRefAtOffset(uint64_t off) const {
|
||||
return getStringRef(getStringPieceIndex(off));
|
||||
}
|
||||
|
||||
// Returns i'th piece as a CachedHashStringRef. This function is very hot when
|
||||
// string merging is enabled, so we want to inline.
|
||||
LLVM_ATTRIBUTE_ALWAYS_INLINE
|
||||
@ -232,6 +238,9 @@ public:
|
||||
|
||||
bool deduplicateLiterals = false;
|
||||
std::vector<StringPiece> pieces;
|
||||
|
||||
private:
|
||||
size_t getStringPieceIndex(uint64_t off) const;
|
||||
};
|
||||
|
||||
class WordLiteralInputSection final : public InputSection {
|
||||
|
74
lld/MachO/Layout.h
Normal file
74
lld/MachO/Layout.h
Normal file
@ -0,0 +1,74 @@
|
||||
//===- Layout.h -----------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// Convenience macros for obtaining offsets of members in structs.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// #define FOR_EACH_FOO_FIELD(DO) \
|
||||
// DO(Ptr, bar) \
|
||||
// DO(uint32_t, baz) \
|
||||
// CREATE_LAYOUT_CLASS(Foo, FOR_EACH_FOO_FIELD)
|
||||
// #undef FOR_EACH_FOO_FIELD
|
||||
//
|
||||
// This will generate
|
||||
//
|
||||
// struct FooLayout {
|
||||
// uint32_t barOffset;
|
||||
// uint32_t bazOffset;
|
||||
// uint32_t totalSize;
|
||||
//
|
||||
// FooLayout(size_t wordSize) {
|
||||
// if (wordSize == 8)
|
||||
// init<uint64_t>();
|
||||
// else {
|
||||
// assert(wordSize == 4);
|
||||
// init<uint32_t>();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private:
|
||||
// template <class Ptr> void init() {
|
||||
// FOR_EACH_FIELD(_INIT_OFFSET);
|
||||
// barOffset = offsetof(Layout<Ptr>, bar);
|
||||
// bazOffset = offsetof(Layout<Ptr>, baz);
|
||||
// totalSize = sizeof(Layout<Ptr>);
|
||||
// }
|
||||
// template <class Ptr> struct Layout {
|
||||
// Ptr bar;
|
||||
// uint32_t baz;
|
||||
// };
|
||||
// };
|
||||
|
||||
#define _OFFSET_FOR_FIELD(_, name) uint32_t name##Offset;
|
||||
#define _INIT_OFFSET(type, name) name##Offset = offsetof(Layout<Ptr>, name);
|
||||
#define _LAYOUT_ENTRY(type, name) type name;
|
||||
|
||||
#define CREATE_LAYOUT_CLASS(className, FOR_EACH_FIELD) \
|
||||
struct className##Layout { \
|
||||
FOR_EACH_FIELD(_OFFSET_FOR_FIELD) \
|
||||
uint32_t totalSize; \
|
||||
\
|
||||
className##Layout(size_t wordSize) { \
|
||||
if (wordSize == 8) \
|
||||
init<uint64_t>(); \
|
||||
else { \
|
||||
assert(wordSize == 4); \
|
||||
init<uint32_t>(); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
private: \
|
||||
template <class Ptr> void init() { \
|
||||
FOR_EACH_FIELD(_INIT_OFFSET); \
|
||||
totalSize = sizeof(Layout<Ptr>); \
|
||||
} \
|
||||
template <class Ptr> struct Layout { \
|
||||
FOR_EACH_FIELD(_LAYOUT_ENTRY) \
|
||||
}; \
|
||||
}
|
@ -9,10 +9,12 @@
|
||||
#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"
|
||||
|
||||
@ -66,3 +68,229 @@ bool macho::hasObjCSection(MemoryBufferRef mb) {
|
||||
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));
|
||||
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)) {
|
||||
const auto *roIsec =
|
||||
cast<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,9 @@ constexpr const char metaclass[] = "_OBJC_METACLASS_$_";
|
||||
constexpr const char ehtype[] = "_OBJC_EHTYPE_$_";
|
||||
constexpr const char ivar[] = "_OBJC_IVAR_$_";
|
||||
|
||||
// Check for duplicate method names within related categories / classes.
|
||||
void checkCategories();
|
||||
|
||||
} // namespace objc
|
||||
|
||||
bool hasObjCSection(llvm::MemoryBufferRef);
|
||||
|
@ -21,6 +21,16 @@ using namespace lld::macho;
|
||||
static_assert(sizeof(void *) != 8 || sizeof(Reloc) == 24,
|
||||
"Try to minimize Reloc's size; we create many instances");
|
||||
|
||||
InputSection *Reloc::getReferentInputSection() const {
|
||||
if (const auto *sym = referent.dyn_cast<Symbol *>()) {
|
||||
if (const auto *d = dyn_cast<Defined>(sym))
|
||||
return d->isec;
|
||||
return nullptr;
|
||||
} else {
|
||||
return referent.get<InputSection *>();
|
||||
}
|
||||
}
|
||||
|
||||
bool macho::validateSymbolRelocation(const Symbol *sym,
|
||||
const InputSection *isec, const Reloc &r) {
|
||||
const RelocAttrs &relocAttrs = target->getRelocAttrs(r.type);
|
||||
|
@ -67,6 +67,8 @@ struct Reloc {
|
||||
int64_t addend, llvm::PointerUnion<Symbol *, InputSection *> referent)
|
||||
: type(type), pcrel(pcrel), length(length), offset(offset),
|
||||
addend(addend), referent(referent) {}
|
||||
|
||||
InputSection *getReferentInputSection() const;
|
||||
};
|
||||
|
||||
bool validateSymbolRelocation(const Symbol *, const InputSection *,
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "UnwindInfoSection.h"
|
||||
#include "InputSection.h"
|
||||
#include "Layout.h"
|
||||
#include "OutputSection.h"
|
||||
#include "OutputSegment.h"
|
||||
#include "SymbolTable.h"
|
||||
@ -88,41 +89,18 @@ using namespace lld::macho;
|
||||
|
||||
// TODO(gkm): how do we align the 2nd-level pages?
|
||||
|
||||
// The offsets of various fields in the on-disk representation of each compact
|
||||
// unwind entry.
|
||||
struct CompactUnwindOffsets {
|
||||
uint32_t functionAddress;
|
||||
uint32_t functionLength;
|
||||
uint32_t encoding;
|
||||
uint32_t personality;
|
||||
uint32_t lsda;
|
||||
// The various fields in the on-disk representation of each compact unwind
|
||||
// entry.
|
||||
#define FOR_EACH_CU_FIELD(DO) \
|
||||
DO(Ptr, functionAddress) \
|
||||
DO(uint32_t, functionLength) \
|
||||
DO(compact_unwind_encoding_t, encoding) \
|
||||
DO(Ptr, personality) \
|
||||
DO(Ptr, lsda)
|
||||
|
||||
CompactUnwindOffsets(size_t wordSize) {
|
||||
if (wordSize == 8)
|
||||
init<uint64_t>();
|
||||
else {
|
||||
assert(wordSize == 4);
|
||||
init<uint32_t>();
|
||||
}
|
||||
}
|
||||
CREATE_LAYOUT_CLASS(CompactUnwind, FOR_EACH_CU_FIELD);
|
||||
|
||||
private:
|
||||
template <class Ptr> void init() {
|
||||
functionAddress = offsetof(Layout<Ptr>, functionAddress);
|
||||
functionLength = offsetof(Layout<Ptr>, functionLength);
|
||||
encoding = offsetof(Layout<Ptr>, encoding);
|
||||
personality = offsetof(Layout<Ptr>, personality);
|
||||
lsda = offsetof(Layout<Ptr>, lsda);
|
||||
}
|
||||
|
||||
template <class Ptr> struct Layout {
|
||||
Ptr functionAddress;
|
||||
uint32_t functionLength;
|
||||
compact_unwind_encoding_t encoding;
|
||||
Ptr personality;
|
||||
Ptr lsda;
|
||||
};
|
||||
};
|
||||
#undef FOR_EACH_CU_FIELD
|
||||
|
||||
// LLD's internal representation of a compact unwind entry.
|
||||
struct CompactUnwindEntry {
|
||||
@ -148,7 +126,7 @@ struct SecondLevelPage {
|
||||
// lengthy definition of UnwindInfoSection.
|
||||
class UnwindInfoSectionImpl final : public UnwindInfoSection {
|
||||
public:
|
||||
UnwindInfoSectionImpl() : cuOffsets(target->wordSize) {}
|
||||
UnwindInfoSectionImpl() : cuLayout(target->wordSize) {}
|
||||
uint64_t getSize() const override { return unwindInfoSize; }
|
||||
void prepare() override;
|
||||
void finalize() override;
|
||||
@ -162,7 +140,7 @@ private:
|
||||
|
||||
uint64_t unwindInfoSize = 0;
|
||||
std::vector<decltype(symbols)::value_type> symbolsVec;
|
||||
CompactUnwindOffsets cuOffsets;
|
||||
CompactUnwindLayout cuLayout;
|
||||
std::vector<std::pair<compact_unwind_encoding_t, size_t>> commonEncodings;
|
||||
EncodingMap commonEncodingIndexes;
|
||||
// The entries here will be in the same order as their originating symbols
|
||||
@ -261,7 +239,7 @@ void UnwindInfoSectionImpl::prepareRelocations(ConcatInputSection *isec) {
|
||||
// compact unwind entries that references them, and thus appear as section
|
||||
// relocs. There is no need to prepare them. We only prepare relocs for
|
||||
// personality functions.
|
||||
if (r.offset != cuOffsets.personality)
|
||||
if (r.offset != cuLayout.personalityOffset)
|
||||
continue;
|
||||
|
||||
if (auto *s = r.referent.dyn_cast<Symbol *>()) {
|
||||
@ -373,17 +351,13 @@ void UnwindInfoSectionImpl::relocateCompactUnwind(
|
||||
auto buf = reinterpret_cast<const uint8_t *>(d->unwindEntry->data.data()) -
|
||||
target->wordSize;
|
||||
cu.functionLength =
|
||||
support::endian::read32le(buf + cuOffsets.functionLength);
|
||||
cu.encoding = support::endian::read32le(buf + cuOffsets.encoding);
|
||||
support::endian::read32le(buf + cuLayout.functionLengthOffset);
|
||||
cu.encoding = support::endian::read32le(buf + cuLayout.encodingOffset);
|
||||
for (const Reloc &r : d->unwindEntry->relocs) {
|
||||
if (r.offset == cuOffsets.personality) {
|
||||
if (r.offset == cuLayout.personalityOffset)
|
||||
cu.personality = r.referent.get<Symbol *>();
|
||||
} else if (r.offset == cuOffsets.lsda) {
|
||||
if (auto *referentSym = r.referent.dyn_cast<Symbol *>())
|
||||
cu.lsda = cast<Defined>(referentSym)->isec;
|
||||
else
|
||||
cu.lsda = r.referent.get<InputSection *>();
|
||||
}
|
||||
else if (r.offset == cuLayout.lsdaOffset)
|
||||
cu.lsda = r.getReferentInputSection();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
10
lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libobjc.tbd
Normal file
10
lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libobjc.tbd
Normal file
@ -0,0 +1,10 @@
|
||||
--- !tapi-tbd-v3
|
||||
archs: [ i386, x86_64, arm64 ]
|
||||
uuids: [ 'i386: 00000000-0000-0000-0000-000000000000', 'x86_64: 00000000-0000-0000-0000-000000000001', 'arm64: 00000000-0000-0000-0000-000000000002' ]
|
||||
platform: macosx
|
||||
install-name: '/usr/lib/libobjc.dylib'
|
||||
current-version: 1281
|
||||
exports:
|
||||
- archs: [ i386, x86_64, arm64 ]
|
||||
symbols: [ __objc_empty_cache ]
|
||||
...
|
252
lld/test/MachO/objc-category-conflicts.s
Normal file
252
lld/test/MachO/objc-category-conflicts.s
Normal file
@ -0,0 +1,252 @@
|
||||
# REQUIRES: x86
|
||||
# RUN: rm -rf %t; split-file %s %t
|
||||
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos11.0 -I %t %t/cat1.s -o %t/cat1.o
|
||||
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos11.0 -I %t %t/cat2.s -o %t/cat2.o
|
||||
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos11.0 -I %t %t/klass.s -o %t/klass.o
|
||||
# RUN: %lld -dylib -lobjc %t/klass.o -o %t/libklass.dylib
|
||||
|
||||
# RUN: %no-fatal-warnings-lld -dylib -lobjc %t/klass.o %t/cat1.o %t/cat2.o -o \
|
||||
# RUN: /dev/null 2>&1 | FileCheck %s --check-prefixes=CATCLS,CATCAT
|
||||
# RUN: %no-fatal-warnings-lld -dylib -lobjc %t/libklass.dylib %t/cat1.o \
|
||||
# RUN: %t/cat2.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=CATCAT
|
||||
|
||||
# CATCLS: warning: method '+s1' has conflicting definitions:
|
||||
# CATCLS-NEXT: >>> defined in category Cat1 from {{.*}}cat1.o
|
||||
# CATCLS-NEXT: >>> defined in class Foo from {{.*}}klass.o
|
||||
|
||||
# CATCLS: warning: method '-m1' has conflicting definitions:
|
||||
# CATCLS-NEXT: >>> defined in category Cat1 from {{.*}}cat1.o
|
||||
# CATCLS-NEXT: >>> defined in class Foo from {{.*}}klass.o
|
||||
|
||||
# CATCAT: warning: method '+s2' has conflicting definitions:
|
||||
# CATCAT-NEXT: >>> defined in category Cat2 from {{.*}}cat2.o
|
||||
# CATCAT-NEXT: >>> defined in category Cat1 from {{.*}}cat1.o
|
||||
|
||||
# CATCAT: warning: method '-m2' has conflicting definitions:
|
||||
# CATCAT-NEXT: >>> defined in category Cat2 from {{.*}}cat2.o
|
||||
# CATCAT-NEXT: >>> defined in category Cat1 from {{.*}}cat1.o
|
||||
|
||||
#--- cat1.s
|
||||
|
||||
.include "objc-macros.s"
|
||||
|
||||
## @interface Foo(Cat1)
|
||||
## -(void) m1;
|
||||
## -(void) m2;
|
||||
## +(void) s1;
|
||||
## +(void) s2;
|
||||
## @end
|
||||
##
|
||||
## @implementation Foo(Cat1)
|
||||
## -(void) m1 {}
|
||||
## -(void) m2 {}
|
||||
## +(void) s1 {}
|
||||
## +(void) s2 {}
|
||||
## @end
|
||||
|
||||
.section __DATA,__objc_catlist,regular,no_dead_strip
|
||||
.quad __OBJC_$_CATEGORY_Foo_$_Cat1
|
||||
|
||||
.section __DATA,__objc_const
|
||||
__OBJC_$_CATEGORY_Foo_$_Cat1:
|
||||
.objc_classname "Cat1"
|
||||
.quad _OBJC_CLASS_$_Foo
|
||||
.quad __OBJC_$_CATEGORY_INSTANCE_METHODS_Foo_$_Cat1
|
||||
.quad __OBJC_$_CATEGORY_CLASS_METHODS_Foo_$_Cat1
|
||||
.quad 0
|
||||
.quad 0
|
||||
.quad 0
|
||||
.long 64
|
||||
.space 4
|
||||
|
||||
__OBJC_$_CATEGORY_INSTANCE_METHODS_Foo_$_Cat1:
|
||||
.long 24 # size of method entry
|
||||
.long 2 # number of methods
|
||||
.empty_objc_method "m1", "v16@0:8", "-[Foo(Cat1) m1]"
|
||||
.empty_objc_method "m2", "v16@0:8", "-[Foo(Cat2) m2]"
|
||||
|
||||
__OBJC_$_CATEGORY_CLASS_METHODS_Foo_$_Cat1:
|
||||
.long 24
|
||||
.long 2
|
||||
.empty_objc_method "s1", "v16@0:8", "+[Foo(Cat1) s1]"
|
||||
.empty_objc_method "s2", "v16@0:8", "+[Foo(Cat1) s2]"
|
||||
|
||||
.section __DATA,__objc_imageinfo,regular,no_dead_strip
|
||||
.long 0
|
||||
.long 64
|
||||
|
||||
.subsections_via_symbols
|
||||
|
||||
#--- cat2.s
|
||||
|
||||
.include "objc-macros.s"
|
||||
|
||||
## @interface Foo(Cat2)
|
||||
## -(void) m2;
|
||||
## +(void) s2;
|
||||
## @end
|
||||
##
|
||||
## @implementation Foo(Cat2)
|
||||
## -(void) m2 {}
|
||||
## +(void) s2 {}
|
||||
## @end
|
||||
|
||||
.section __DATA,__objc_catlist,regular,no_dead_strip
|
||||
.quad __OBJC_$_CATEGORY_Foo_$_Cat2
|
||||
|
||||
.section __DATA,__objc_const
|
||||
__OBJC_$_CATEGORY_Foo_$_Cat2:
|
||||
.objc_classname "Cat2"
|
||||
.quad _OBJC_CLASS_$_Foo
|
||||
.quad __OBJC_$_CATEGORY_INSTANCE_METHODS_Foo_$_Cat2
|
||||
.quad __OBJC_$_CATEGORY_CLASS_METHODS_Foo_$_Cat2
|
||||
.quad 0
|
||||
.quad 0
|
||||
.quad 0
|
||||
.long 64
|
||||
.space 4
|
||||
|
||||
__OBJC_$_CATEGORY_INSTANCE_METHODS_Foo_$_Cat2:
|
||||
.long 24
|
||||
.long 1
|
||||
.empty_objc_method "m2", "v16@0:8", "-[Foo(Cat2) m2]"
|
||||
|
||||
__OBJC_$_CATEGORY_CLASS_METHODS_Foo_$_Cat2:
|
||||
.long 24
|
||||
.long 1
|
||||
.empty_objc_method "s2", "v16@0:8", "+[Foo(Cat2) m2]"
|
||||
|
||||
.section __DATA,__objc_imageinfo,regular,no_dead_strip
|
||||
.long 0
|
||||
.long 64
|
||||
|
||||
.subsections_via_symbols
|
||||
|
||||
#--- klass.s
|
||||
|
||||
.include "objc-macros.s"
|
||||
|
||||
## @interface Foo
|
||||
## -(void) m1;
|
||||
## +(void) s1;
|
||||
## @end
|
||||
##
|
||||
## @implementation Foo
|
||||
## -(void) m1 {}
|
||||
## +(void) s1 {}
|
||||
## @end
|
||||
|
||||
.globl _OBJC_CLASS_$_Foo, _OBJC_METACLASS_$_Foo
|
||||
|
||||
.section __DATA,__objc_data
|
||||
_OBJC_CLASS_$_Foo:
|
||||
.quad _OBJC_METACLASS_$_Foo
|
||||
.quad 0
|
||||
.quad __objc_empty_cache
|
||||
.quad 0
|
||||
.quad __OBJC_CLASS_RO_$_Foo
|
||||
|
||||
_OBJC_METACLASS_$_Foo:
|
||||
.quad _OBJC_METACLASS_$_Foo
|
||||
.quad _OBJC_CLASS_$_Foo
|
||||
.quad __objc_empty_cache
|
||||
.quad 0
|
||||
.quad __OBJC_METACLASS_RO_$_Foo
|
||||
|
||||
.section __DATA,__objc_const
|
||||
__OBJC_METACLASS_RO_$_Foo:
|
||||
.long 3
|
||||
.long 40
|
||||
.long 40
|
||||
.space 4
|
||||
.quad 0
|
||||
.objc_classname "Foo"
|
||||
.quad __OBJC_$_CLASS_METHODS_Foo
|
||||
.quad 0
|
||||
.quad 0
|
||||
.quad 0
|
||||
.quad 0
|
||||
|
||||
__OBJC_CLASS_RO_$_Foo:
|
||||
.long 2
|
||||
.long 0
|
||||
.long 0
|
||||
.space 4
|
||||
.quad 0
|
||||
.objc_classname "Foo"
|
||||
.quad __OBJC_$_INSTANCE_METHODS_Foo
|
||||
.quad 0
|
||||
.quad 0
|
||||
.quad 0
|
||||
.quad 0
|
||||
|
||||
__OBJC_$_CLASS_METHODS_Foo:
|
||||
.long 24
|
||||
.long 1
|
||||
.empty_objc_method "s1", "v16@0:8", "+[Foo s1]"
|
||||
|
||||
__OBJC_$_INSTANCE_METHODS_Foo:
|
||||
.long 24
|
||||
.long 1
|
||||
.empty_objc_method "m1", "v16@0:8", "-[Foo m1]"
|
||||
|
||||
.section __DATA,__objc_classlist,regular,no_dead_strip
|
||||
.quad _OBJC_CLASS_$_Foo
|
||||
|
||||
.section __DATA,__objc_imageinfo,regular,no_dead_strip
|
||||
.long 0
|
||||
.long 64
|
||||
|
||||
.subsections_via_symbols
|
||||
|
||||
#--- objc-macros.s
|
||||
|
||||
# Macros for taking some of the boilerplate out of defining objc structs.
|
||||
|
||||
# NOTE: \@ below is a variable that gets auto-incremented by the assembler on
|
||||
# each macro invocation. It serves as a mechanism for generating unique symbol
|
||||
# names for each macro call.
|
||||
|
||||
.macro .objc_classname name
|
||||
|
||||
.section __TEXT,__objc_classname,cstring_literals
|
||||
L_OBJC_CLASS_NAME_.\@:
|
||||
.asciz "\name"
|
||||
|
||||
.section __DATA,__objc_const
|
||||
.quad L_OBJC_CLASS_NAME_.\@
|
||||
|
||||
.endm
|
||||
|
||||
# struct method_t {
|
||||
# const char *name;
|
||||
# const char *type;
|
||||
# void *impl;
|
||||
# }
|
||||
.macro .objc_method name, type, impl
|
||||
|
||||
.section __TEXT,__objc_methname,cstring_literals
|
||||
L_OBJC_METH_VAR_NAME_.\@:
|
||||
.asciz "\name"
|
||||
|
||||
.section __TEXT,__objc_methtype,cstring_literals
|
||||
L_OBJC_METH_VAR_TYPE_.\@:
|
||||
.asciz "\type"
|
||||
|
||||
.section __DATA,__objc_const
|
||||
.quad L_OBJC_METH_VAR_NAME_.\@
|
||||
.quad L_OBJC_METH_VAR_TYPE_.\@
|
||||
.quad "\impl"
|
||||
|
||||
.endm
|
||||
|
||||
# Generate a method_t with a basic impl that just contains `ret`.
|
||||
.macro .empty_objc_method name, type, impl
|
||||
|
||||
.text
|
||||
"\impl":
|
||||
ret
|
||||
|
||||
.objc_method "\name", "\type", "\impl"
|
||||
|
||||
.endm
|
@ -152,14 +152,11 @@ _OBJC_CLASS_$_FooClass:
|
||||
.space 40
|
||||
|
||||
.section __DATA,__objc_const
|
||||
.p2align 3
|
||||
__OBJC_$_CATEGORY_INSTANCE_METHODS_FooClass_$_barcat:
|
||||
|
||||
.p2align 3
|
||||
__OBJC_$_CATEGORY_FooClass_$_barcat:
|
||||
.quad L_CAT_NAME
|
||||
.quad _OBJC_CLASS_$_FooClass
|
||||
.quad __OBJC_$_CATEGORY_INSTANCE_METHODS_FooClass_$_barcat
|
||||
.quad 0
|
||||
.quad 0
|
||||
.quad 0
|
||||
.quad 0
|
||||
|
Loading…
x
Reference in New Issue
Block a user