1//===- ObjC.cpp -----------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "ObjC.h"
10#include "ConcatOutputSection.h"
11#include "InputFiles.h"
12#include "InputSection.h"
13#include "Layout.h"
14#include "OutputSegment.h"
15#include "SyntheticSections.h"
16#include "Target.h"
17
18#include "lld/Common/ErrorHandler.h"
19#include "llvm/ADT/DenseMap.h"
20#include "llvm/Bitcode/BitcodeReader.h"
21#include "llvm/Support/TimeProfiler.h"
22
23using namespace llvm;
24using namespace llvm::MachO;
25using namespace lld;
26using namespace lld::macho;
27
28template <class LP> static bool objectHasObjCSection(MemoryBufferRef mb) {
29 using SectionHeader = typename LP::section;
30
31 auto *hdr =
32 reinterpret_cast<const typename LP::mach_header *>(mb.getBufferStart());
33 if (hdr->magic != LP::magic)
34 return false;
35
36 if (const auto *c =
37 findCommand<typename LP::segment_command>(hdr, LP::segmentLCType)) {
38 auto sectionHeaders = ArrayRef<SectionHeader>{
39 reinterpret_cast<const SectionHeader *>(c + 1), c->nsects};
40 for (const SectionHeader &secHead : sectionHeaders) {
41 StringRef sectname(secHead.sectname,
42 strnlen(secHead.sectname, sizeof(secHead.sectname)));
43 StringRef segname(secHead.segname,
44 strnlen(secHead.segname, sizeof(secHead.segname)));
45 if ((segname == segment_names::data &&
46 sectname == section_names::objcCatList) ||
47 (segname == segment_names::text &&
48 sectname.starts_with(Prefix: section_names::swift))) {
49 return true;
50 }
51 }
52 }
53 return false;
54}
55
56static bool objectHasObjCSection(MemoryBufferRef mb) {
57 if (target->wordSize == 8)
58 return ::objectHasObjCSection<LP64>(mb);
59 else
60 return ::objectHasObjCSection<ILP32>(mb);
61}
62
63bool macho::hasObjCSection(MemoryBufferRef mb) {
64 switch (identify_magic(magic: mb.getBuffer())) {
65 case file_magic::macho_object:
66 return objectHasObjCSection(mb);
67 case file_magic::bitcode:
68 return check(e: isBitcodeContainingObjCCategory(Buffer: mb));
69 default:
70 return false;
71 }
72}
73
74namespace {
75
76#define FOR_EACH_CATEGORY_FIELD(DO) \
77 DO(Ptr, name) \
78 DO(Ptr, klass) \
79 DO(Ptr, instanceMethods) \
80 DO(Ptr, classMethods) \
81 DO(Ptr, protocols) \
82 DO(Ptr, instanceProps) \
83 DO(Ptr, classProps) \
84 DO(uint32_t, size)
85
86CREATE_LAYOUT_CLASS(Category, FOR_EACH_CATEGORY_FIELD);
87
88#undef FOR_EACH_CATEGORY_FIELD
89
90#define FOR_EACH_CLASS_FIELD(DO) \
91 DO(Ptr, metaClass) \
92 DO(Ptr, superClass) \
93 DO(Ptr, methodCache) \
94 DO(Ptr, vtable) \
95 DO(Ptr, roData)
96
97CREATE_LAYOUT_CLASS(Class, FOR_EACH_CLASS_FIELD);
98
99#undef FOR_EACH_CLASS_FIELD
100
101#define FOR_EACH_RO_CLASS_FIELD(DO) \
102 DO(uint32_t, flags) \
103 DO(uint32_t, instanceStart) \
104 DO(Ptr, instanceSize) \
105 DO(Ptr, ivarLayout) \
106 DO(Ptr, name) \
107 DO(Ptr, baseMethods) \
108 DO(Ptr, baseProtocols) \
109 DO(Ptr, ivars) \
110 DO(Ptr, weakIvarLayout) \
111 DO(Ptr, baseProperties)
112
113CREATE_LAYOUT_CLASS(ROClass, FOR_EACH_RO_CLASS_FIELD);
114
115#undef FOR_EACH_RO_CLASS_FIELD
116
117#define FOR_EACH_LIST_HEADER(DO) \
118 DO(uint32_t, structSize) \
119 DO(uint32_t, structCount)
120
121CREATE_LAYOUT_CLASS(ListHeader, FOR_EACH_LIST_HEADER);
122
123#undef FOR_EACH_LIST_HEADER
124
125#define FOR_EACH_PROTOCOL_LIST_HEADER(DO) DO(Ptr, protocolCount)
126
127CREATE_LAYOUT_CLASS(ProtocolListHeader, FOR_EACH_PROTOCOL_LIST_HEADER);
128
129#undef FOR_EACH_PROTOCOL_LIST_HEADER
130
131#define FOR_EACH_METHOD(DO) \
132 DO(Ptr, name) \
133 DO(Ptr, type) \
134 DO(Ptr, impl)
135
136CREATE_LAYOUT_CLASS(Method, FOR_EACH_METHOD);
137
138#undef FOR_EACH_METHOD
139
140enum MethodContainerKind {
141 MCK_Class,
142 MCK_Category,
143};
144
145struct MethodContainer {
146 MethodContainerKind kind;
147 const ConcatInputSection *isec;
148};
149
150enum MethodKind {
151 MK_Instance,
152 MK_Static,
153};
154
155struct ObjcClass {
156 DenseMap<CachedHashStringRef, MethodContainer> instanceMethods;
157 DenseMap<CachedHashStringRef, MethodContainer> classMethods;
158};
159
160} // namespace
161
162class ObjcCategoryChecker {
163public:
164 ObjcCategoryChecker();
165 void parseCategory(const ConcatInputSection *catListIsec);
166
167private:
168 void parseClass(const Defined *classSym);
169 void parseMethods(const ConcatInputSection *methodsIsec,
170 const Symbol *methodContainer,
171 const ConcatInputSection *containerIsec,
172 MethodContainerKind, MethodKind);
173
174 CategoryLayout catLayout;
175 ClassLayout classLayout;
176 ROClassLayout roClassLayout;
177 ListHeaderLayout listHeaderLayout;
178 MethodLayout methodLayout;
179
180 DenseMap<const Symbol *, ObjcClass> classMap;
181};
182
183ObjcCategoryChecker::ObjcCategoryChecker()
184 : catLayout(target->wordSize), classLayout(target->wordSize),
185 roClassLayout(target->wordSize), listHeaderLayout(target->wordSize),
186 methodLayout(target->wordSize) {}
187
188void ObjcCategoryChecker::parseMethods(const ConcatInputSection *methodsIsec,
189 const Symbol *methodContainerSym,
190 const ConcatInputSection *containerIsec,
191 MethodContainerKind mcKind,
192 MethodKind mKind) {
193 ObjcClass &klass = classMap[methodContainerSym];
194 for (const Relocation &r : methodsIsec->relocs) {
195 if ((r.offset - listHeaderLayout.totalSize) % methodLayout.totalSize !=
196 methodLayout.nameOffset)
197 continue;
198
199 CachedHashStringRef methodName(r.getReferentString());
200 // +load methods are special: all implementations are called by the runtime
201 // even if they are part of the same class. Thus there is no need to check
202 // for duplicates.
203 // NOTE: Instead of specifically checking for this method name, ld64 simply
204 // checks whether a class / category is present in __objc_nlclslist /
205 // __objc_nlcatlist respectively. This will be the case if the class /
206 // category has a +load method. It skips optimizing the categories if there
207 // are multiple +load methods. Since it does dupe checking as part of the
208 // optimization process, this avoids spurious dupe messages around +load,
209 // but it also means that legit dupe issues for other methods are ignored.
210 if (mKind == MK_Static && methodName.val() == "load")
211 continue;
212
213 auto &methodMap =
214 mKind == MK_Instance ? klass.instanceMethods : klass.classMethods;
215 if (methodMap
216 .try_emplace(Key: methodName, Args: MethodContainer{.kind: mcKind, .isec: containerIsec})
217 .second)
218 continue;
219
220 // We have a duplicate; generate a warning message.
221 const auto &mc = methodMap.lookup(Val: methodName);
222 const Relocation *nameReloc = nullptr;
223 if (mc.kind == MCK_Category) {
224 nameReloc = mc.isec->getRelocAt(off: catLayout.nameOffset);
225 } else {
226 assert(mc.kind == MCK_Class);
227 const auto *roIsec = mc.isec->getRelocAt(off: classLayout.roDataOffset)
228 ->getReferentInputSection();
229 nameReloc = roIsec->getRelocAt(off: roClassLayout.nameOffset);
230 }
231 StringRef containerName = nameReloc->getReferentString();
232 StringRef methPrefix = mKind == MK_Instance ? "-" : "+";
233
234 // We should only ever encounter collisions when parsing category methods
235 // (since the Class struct is parsed before any of its categories).
236 assert(mcKind == MCK_Category);
237 StringRef newCatName =
238 containerIsec->getRelocAt(off: catLayout.nameOffset)->getReferentString();
239
240 auto formatObjAndSrcFileName = [](const InputSection *section) {
241 lld::macho::InputFile *inputFile = section->getFile();
242 std::string result = toString(file: inputFile);
243
244 auto objFile = dyn_cast_or_null<ObjFile>(Val: inputFile);
245 if (objFile && objFile->compileUnit)
246 result += " (" + objFile->sourceFile() + ")";
247
248 return result;
249 };
250
251 StringRef containerType = mc.kind == MCK_Category ? "category" : "class";
252 warn(msg: "method '" + methPrefix + methodName.val() +
253 "' has conflicting definitions:\n>>> defined in category " +
254 newCatName + " from " + formatObjAndSrcFileName(containerIsec) +
255 "\n>>> defined in " + containerType + " " + containerName + " from " +
256 formatObjAndSrcFileName(mc.isec));
257 }
258}
259
260void ObjcCategoryChecker::parseCategory(const ConcatInputSection *catIsec) {
261 auto *classReloc = catIsec->getRelocAt(off: catLayout.klassOffset);
262 if (!classReloc)
263 return;
264
265 auto *classSym = cast<Symbol *>(Val: classReloc->referent);
266 if (auto *d = dyn_cast<Defined>(Val: classSym))
267 if (!classMap.contains(Val: d))
268 parseClass(classSym: d);
269
270 if (const auto *r = catIsec->getRelocAt(off: catLayout.classMethodsOffset)) {
271 parseMethods(methodsIsec: cast<ConcatInputSection>(Val: r->getReferentInputSection()),
272 methodContainerSym: classSym, containerIsec: catIsec, mcKind: MCK_Category, mKind: MK_Static);
273 }
274
275 if (const auto *r = catIsec->getRelocAt(off: catLayout.instanceMethodsOffset)) {
276 parseMethods(methodsIsec: cast<ConcatInputSection>(Val: r->getReferentInputSection()),
277 methodContainerSym: classSym, containerIsec: catIsec, mcKind: MCK_Category, mKind: MK_Instance);
278 }
279}
280
281void ObjcCategoryChecker::parseClass(const Defined *classSym) {
282 // Given a Class struct, get its corresponding Methods struct
283 auto getMethodsIsec =
284 [&](const InputSection *classIsec) -> ConcatInputSection * {
285 if (const auto *r = classIsec->getRelocAt(off: classLayout.roDataOffset)) {
286 if (const auto *roIsec =
287 cast_or_null<ConcatInputSection>(Val: r->getReferentInputSection())) {
288 if (const auto *r =
289 roIsec->getRelocAt(off: roClassLayout.baseMethodsOffset)) {
290 if (auto *methodsIsec = cast_or_null<ConcatInputSection>(
291 Val: r->getReferentInputSection()))
292 return methodsIsec;
293 }
294 }
295 }
296 return nullptr;
297 };
298
299 const auto *classIsec = cast<ConcatInputSection>(Val: classSym->isec());
300
301 // Parse instance methods.
302 if (const auto *instanceMethodsIsec = getMethodsIsec(classIsec))
303 parseMethods(methodsIsec: instanceMethodsIsec, methodContainerSym: classSym, containerIsec: classIsec, mcKind: MCK_Class,
304 mKind: MK_Instance);
305
306 // Class methods are contained in the metaclass.
307 if (const auto *r = classSym->isec()->getRelocAt(off: classLayout.metaClassOffset))
308 if (const auto *classMethodsIsec = getMethodsIsec(
309 cast<ConcatInputSection>(Val: r->getReferentInputSection())))
310 parseMethods(methodsIsec: classMethodsIsec, methodContainerSym: classSym, containerIsec: classIsec, mcKind: MCK_Class, mKind: MK_Static);
311}
312
313void objc::checkCategories() {
314 TimeTraceScope timeScope("ObjcCategoryChecker");
315
316 ObjcCategoryChecker checker;
317 for (const InputSection *isec : inputSections) {
318 if (isec->getName() == section_names::objcCatList)
319 for (const Relocation &r : isec->relocs) {
320 auto *catIsec = cast<ConcatInputSection>(Val: r.getReferentInputSection());
321 checker.parseCategory(catIsec);
322 }
323 }
324}
325
326namespace {
327
328class ObjcCategoryMerger {
329 // Information about an input category
330 struct InfoInputCategory {
331 ConcatInputSection *catListIsec;
332 ConcatInputSection *catBodyIsec;
333 uint32_t offCatListIsec = 0;
334
335 bool wasMerged = false;
336 };
337
338 // To write new (merged) categories or classes, we will try make limited
339 // assumptions about the alignment and the sections the various class/category
340 // info are stored in and . So we'll just reuse the same sections and
341 // alignment as already used in existing (input) categories. To do this we
342 // have InfoCategoryWriter which contains the various sections that the
343 // generated categories will be written to.
344 struct InfoWriteSection {
345 bool valid = false; // Data has been successfully collected from input
346 uint32_t align = 0;
347 Section *inputSection;
348 Relocation relocTemplate;
349 OutputSection *outputSection;
350 };
351
352 struct InfoCategoryWriter {
353 InfoWriteSection catListInfo;
354 InfoWriteSection catBodyInfo;
355 InfoWriteSection catNameInfo;
356 InfoWriteSection catPtrListInfo;
357 };
358
359 // Information about a pointer list in the original categories or class(method
360 // lists, protocol lists, etc)
361 struct PointerListInfo {
362 PointerListInfo() = default;
363 PointerListInfo(const PointerListInfo &) = default;
364 PointerListInfo(const char *_categoryPrefix, uint32_t _pointersPerStruct)
365 : categoryPrefix(_categoryPrefix),
366 pointersPerStruct(_pointersPerStruct) {}
367
368 inline bool operator==(const PointerListInfo &cmp) const {
369 return pointersPerStruct == cmp.pointersPerStruct &&
370 structSize == cmp.structSize && structCount == cmp.structCount &&
371 allPtrs == cmp.allPtrs;
372 }
373
374 const char *categoryPrefix;
375
376 uint32_t pointersPerStruct = 0;
377
378 uint32_t structSize = 0;
379 uint32_t structCount = 0;
380
381 std::vector<Symbol *> allPtrs;
382 };
383
384 // Full information describing an ObjC class . This will include all the
385 // additional methods, protocols, and properties that are contained in the
386 // class and all the categories that extend a particular class.
387 struct ClassExtensionInfo {
388 ClassExtensionInfo(CategoryLayout &_catLayout) : catLayout(_catLayout){};
389
390 // Merged names of containers. Ex: base|firstCategory|secondCategory|...
391 std::string mergedContainerName;
392 std::string baseClassName;
393 const Symbol *baseClass = nullptr;
394 int64_t baseClassAddend = 0;
395
396 CategoryLayout &catLayout;
397
398 // In case we generate new data, mark the new data as belonging to this file
399 ObjFile *objFileForMergeData = nullptr;
400
401 PointerListInfo instanceMethods = {objc::symbol_names::instanceMethods,
402 /*pointersPerStruct=*/3};
403 PointerListInfo classMethods = {objc::symbol_names::categoryClassMethods,
404 /*pointersPerStruct=*/3};
405 PointerListInfo protocols = {objc::symbol_names::categoryProtocols,
406 /*pointersPerStruct=*/0};
407 PointerListInfo instanceProps = {objc::symbol_names::listProprieties,
408 /*pointersPerStruct=*/2};
409 PointerListInfo classProps = {objc::symbol_names::klassPropList,
410 /*pointersPerStruct=*/2};
411 };
412
413public:
414 ObjcCategoryMerger(std::vector<ConcatInputSection *> &_allInputSections);
415 void doMerge();
416 static void doCleanup();
417
418private:
419 DenseSet<const Symbol *> collectNlCategories();
420 void collectAndValidateCategoriesData();
421 bool
422 mergeCategoriesIntoSingleCategory(std::vector<InfoInputCategory> &categories);
423
424 void eraseISec(ConcatInputSection *isec);
425 void eraseMergedCategories();
426
427 void generateCatListForNonErasedCategories(
428 MapVector<ConcatInputSection *, std::set<uint64_t>>
429 catListToErasedOffsets);
430 void collectSectionWriteInfoFromIsec(const InputSection *isec,
431 InfoWriteSection &catWriteInfo);
432 bool collectCategoryWriterInfoFromCategory(const InfoInputCategory &catInfo);
433 bool parseCatInfoToExtInfo(const InfoInputCategory &catInfo,
434 ClassExtensionInfo &extInfo);
435
436 void parseProtocolListInfo(const ConcatInputSection *isec, uint32_t secOffset,
437 PointerListInfo &ptrList);
438
439 PointerListInfo parseProtocolListInfo(const ConcatInputSection *isec,
440 uint32_t secOffset);
441
442 bool parsePointerListInfo(const ConcatInputSection *isec, uint32_t secOffset,
443 PointerListInfo &ptrList);
444
445 void emitAndLinkPointerList(Defined *parentSym, uint32_t linkAtOffset,
446 const ClassExtensionInfo &extInfo,
447 const PointerListInfo &ptrList);
448
449 Defined *emitAndLinkProtocolList(Defined *parentSym, uint32_t linkAtOffset,
450 const ClassExtensionInfo &extInfo,
451 const PointerListInfo &ptrList);
452
453 Defined *emitCategory(const ClassExtensionInfo &extInfo);
454 Defined *emitCatListEntrySec(const std::string &forCategoryName,
455 const std::string &forBaseClassName,
456 ObjFile *objFile);
457 Defined *emitCategoryBody(const std::string &name, const Defined *nameSym,
458 const Symbol *baseClassSym, int64_t baseClassAddend,
459 const std::string &baseClassName, ObjFile *objFile);
460 Defined *emitCategoryName(const std::string &name, ObjFile *objFile);
461 void createSymbolReference(Defined *refFrom, const Symbol *refTo,
462 uint32_t offset, const Relocation &relocTemplate,
463 int64_t addend = 0);
464 Defined *tryFindDefinedOnIsec(const InputSection *isec, uint32_t offset);
465 std::pair<Symbol *, int64_t>
466 tryGetSymbolReferenceAtIsecOffset(const ConcatInputSection *isec,
467 uint32_t offset);
468 Symbol *tryGetSymbolAtIsecOffset(const ConcatInputSection *isec,
469 uint32_t offset);
470 Defined *tryGetDefinedAtIsecOffset(const ConcatInputSection *isec,
471 uint32_t offset);
472 Defined *getClassRo(const Defined *classSym, int64_t classAddend,
473 bool getMetaRo);
474 bool mergeCategoriesIntoBaseClass(const Defined *baseClass,
475 int64_t baseClassAddend,
476 std::vector<InfoInputCategory> &categories);
477 void eraseSymbolAtIsecOffset(ConcatInputSection *isec, uint32_t offset);
478 void tryEraseDefinedAtIsecOffset(const ConcatInputSection *isec,
479 uint32_t offset);
480
481 // Allocate a null-terminated StringRef backed by generatedSectionData
482 StringRef newStringData(const char *str);
483 // Allocate section data, backed by generatedSectionData
484 SmallVector<uint8_t> &newSectionData(uint32_t size);
485
486 CategoryLayout catLayout;
487 ClassLayout classLayout;
488 ROClassLayout roClassLayout;
489 ListHeaderLayout listHeaderLayout;
490 MethodLayout methodLayout;
491 ProtocolListHeaderLayout protocolListHeaderLayout;
492
493 InfoCategoryWriter infoCategoryWriter;
494 std::vector<ConcatInputSection *> &allInputSections;
495 // Map of base class Symbol + residual addend to its categories. Swift class
496 // address points can be interior to a larger metadata symbol after LTO.
497 MapVector<std::pair<const Symbol *, int64_t>, std::vector<InfoInputCategory>>
498 categoryMap;
499
500 // Normally, the binary data comes from the input files, but since we're
501 // generating binary data ourselves, we use the below array to store it in.
502 // Need this to be 'static' so the data survives past the ObjcCategoryMerger
503 // object, as the data will be read by the Writer when the final binary is
504 // generated.
505 static SmallVector<std::unique_ptr<SmallVector<uint8_t>>>
506 generatedSectionData;
507};
508
509SmallVector<std::unique_ptr<SmallVector<uint8_t>>>
510 ObjcCategoryMerger::generatedSectionData;
511
512ObjcCategoryMerger::ObjcCategoryMerger(
513 std::vector<ConcatInputSection *> &_allInputSections)
514 : catLayout(target->wordSize), classLayout(target->wordSize),
515 roClassLayout(target->wordSize), listHeaderLayout(target->wordSize),
516 methodLayout(target->wordSize),
517 protocolListHeaderLayout(target->wordSize),
518 allInputSections(_allInputSections) {}
519
520void ObjcCategoryMerger::collectSectionWriteInfoFromIsec(
521 const InputSection *isec, InfoWriteSection &catWriteInfo) {
522
523 catWriteInfo.inputSection = const_cast<Section *>(&isec->section);
524 catWriteInfo.align = isec->align;
525 catWriteInfo.outputSection = isec->parent;
526
527 assert(catWriteInfo.outputSection &&
528 "outputSection may not be null in collectSectionWriteInfoFromIsec.");
529
530 if (isec->relocs.size())
531 catWriteInfo.relocTemplate = isec->relocs[0];
532
533 catWriteInfo.valid = true;
534}
535
536std::pair<Symbol *, int64_t>
537ObjcCategoryMerger::tryGetSymbolReferenceAtIsecOffset(
538 const ConcatInputSection *isec, uint32_t offset) {
539 if (!isec)
540 return {nullptr, 0};
541 const Relocation *reloc = isec->getRelocAt(off: offset);
542
543 if (!reloc)
544 return {nullptr, 0};
545
546 Symbol *sym = dyn_cast_if_present<Symbol *>(Val: reloc->referent);
547
548 if (reloc->addend && sym) {
549 assert(isa<Defined>(sym) && "Expected defined for non-zero addend");
550 Defined *definedSym = cast<Defined>(Val: sym);
551 uint64_t targetOffset = definedSym->value + reloc->addend;
552 if (Defined *targetSym =
553 tryFindDefinedOnIsec(isec: definedSym->isec(), offset: targetOffset))
554 return {targetSym, targetOffset - targetSym->value};
555 }
556
557 return {sym, reloc->addend};
558}
559
560Symbol *
561ObjcCategoryMerger::tryGetSymbolAtIsecOffset(const ConcatInputSection *isec,
562 uint32_t offset) {
563 return tryGetSymbolReferenceAtIsecOffset(isec, offset).first;
564}
565
566Defined *ObjcCategoryMerger::tryFindDefinedOnIsec(const InputSection *isec,
567 uint32_t offset) {
568 Defined *containing = nullptr;
569 for (Defined *sym : isec->symbols) {
570 if (sym->value == offset)
571 return sym;
572 if (sym->value < offset && sym->value + sym->size > offset &&
573 (!containing || sym->value > containing->value))
574 containing = sym;
575 }
576
577 return containing;
578}
579
580Defined *
581ObjcCategoryMerger::tryGetDefinedAtIsecOffset(const ConcatInputSection *isec,
582 uint32_t offset) {
583 Symbol *sym = tryGetSymbolAtIsecOffset(isec, offset);
584 return dyn_cast_or_null<Defined>(Val: sym);
585}
586
587// Get the class's ro_data symbol. If getMetaRo is true, then we will return
588// the meta-class's ro_data symbol. Otherwise, we will return the class
589// (instance) ro_data symbol.
590Defined *ObjcCategoryMerger::getClassRo(const Defined *classSym,
591 int64_t classAddend, bool getMetaRo) {
592 ConcatInputSection *isec = dyn_cast<ConcatInputSection>(Val: classSym->isec());
593 if (!isec)
594 return nullptr;
595
596 uint64_t classOffset = classSym->value + classAddend;
597 if (!getMetaRo)
598 return tryGetDefinedAtIsecOffset(isec,
599 offset: classLayout.roDataOffset + classOffset);
600
601 auto [metaClassSym, metaClassAddend] = tryGetSymbolReferenceAtIsecOffset(
602 isec, offset: classLayout.metaClassOffset + classOffset);
603 Defined *metaClass = dyn_cast_or_null<Defined>(Val: metaClassSym);
604 if (!metaClass)
605 return nullptr;
606
607 return tryGetDefinedAtIsecOffset(
608 isec: dyn_cast<ConcatInputSection>(Val: metaClass->isec()),
609 offset: classLayout.roDataOffset + metaClass->value + metaClassAddend);
610}
611
612// Given an ConcatInputSection or CStringInputSection and an offset, if there is
613// a symbol(Defined) at that offset, then erase the symbol (mark it not live)
614void ObjcCategoryMerger::tryEraseDefinedAtIsecOffset(
615 const ConcatInputSection *isec, uint32_t offset) {
616 const Relocation *reloc = isec->getRelocAt(off: offset);
617
618 if (!reloc)
619 return;
620
621 Defined *sym = dyn_cast_or_null<Defined>(Val: cast<Symbol *>(Val: reloc->referent));
622 if (!sym)
623 return;
624
625 if (auto *cisec = dyn_cast_or_null<ConcatInputSection>(Val: sym->isec()))
626 eraseISec(isec: cisec);
627 else if (auto *csisec = dyn_cast_or_null<CStringInputSection>(Val: sym->isec())) {
628 uint32_t totalOffset = sym->value + reloc->addend;
629 StringPiece &piece = csisec->getStringPiece(off: totalOffset);
630 piece.live = false;
631 } else {
632 llvm_unreachable("erased symbol has to be Defined or CStringInputSection");
633 }
634}
635
636bool ObjcCategoryMerger::collectCategoryWriterInfoFromCategory(
637 const InfoInputCategory &catInfo) {
638
639 if (!infoCategoryWriter.catListInfo.valid)
640 collectSectionWriteInfoFromIsec(isec: catInfo.catListIsec,
641 catWriteInfo&: infoCategoryWriter.catListInfo);
642 if (!infoCategoryWriter.catBodyInfo.valid)
643 collectSectionWriteInfoFromIsec(isec: catInfo.catBodyIsec,
644 catWriteInfo&: infoCategoryWriter.catBodyInfo);
645
646 if (!infoCategoryWriter.catNameInfo.valid) {
647 lld::macho::Defined *catNameSym =
648 tryGetDefinedAtIsecOffset(isec: catInfo.catBodyIsec, offset: catLayout.nameOffset);
649
650 if (!catNameSym) {
651 // This is an unhandled case where the category name is not a symbol but
652 // instead points to an CStringInputSection (that doesn't have any symbol)
653 // TODO: Find a small repro and either fix or add a test case for this
654 // scenario
655 return false;
656 }
657
658 collectSectionWriteInfoFromIsec(isec: catNameSym->isec(),
659 catWriteInfo&: infoCategoryWriter.catNameInfo);
660 }
661
662 // Collect writer info from all the category lists (we're assuming they all
663 // would provide the same info)
664 if (!infoCategoryWriter.catPtrListInfo.valid) {
665 for (uint32_t off = catLayout.instanceMethodsOffset;
666 off <= catLayout.classPropsOffset; off += target->wordSize) {
667 if (Defined *ptrList =
668 tryGetDefinedAtIsecOffset(isec: catInfo.catBodyIsec, offset: off)) {
669 collectSectionWriteInfoFromIsec(isec: ptrList->isec(),
670 catWriteInfo&: infoCategoryWriter.catPtrListInfo);
671 // we've successfully collected data, so we can break
672 break;
673 }
674 }
675 }
676
677 return true;
678}
679
680// Parse a protocol list that might be linked to ConcatInputSection at a given
681// offset. The format of the protocol list is different than other lists (prop
682// lists, method lists) so we need to parse it differently
683void ObjcCategoryMerger::parseProtocolListInfo(const ConcatInputSection *isec,
684 uint32_t secOffset,
685 PointerListInfo &ptrList) {
686 assert((isec && (secOffset + target->wordSize <= isec->data.size())) &&
687 "Tried to read pointer list beyond protocol section end");
688
689 const Relocation *reloc = isec->getRelocAt(off: secOffset);
690 if (!reloc)
691 return;
692
693 auto *ptrListSym = dyn_cast_or_null<Defined>(Val: cast<Symbol *>(Val: reloc->referent));
694 assert(ptrListSym && "Protocol list reloc does not have a valid Defined");
695
696 // Theoretically protocol count can be either 32b or 64b, depending on
697 // platform pointer size, but to simplify implementation we always just read
698 // the lower 32b which should be good enough.
699 uint32_t protocolCount = *reinterpret_cast<const uint32_t *>(
700 ptrListSym->isec()->data.data() + listHeaderLayout.structSizeOffset);
701
702 ptrList.structCount += protocolCount;
703 ptrList.structSize = target->wordSize;
704
705 [[maybe_unused]] uint32_t expectedListSize =
706 (protocolCount * target->wordSize) +
707 /*header(count)*/ protocolListHeaderLayout.totalSize +
708 /*extra null value*/ target->wordSize;
709
710 uint32_t off = protocolListHeaderLayout.totalSize;
711 for (uint32_t inx = 0; inx < protocolCount; ++inx) {
712 const Relocation *reloc = ptrListSym->isec()->getRelocAt(off);
713 assert(reloc && "No reloc found at protocol list offset");
714
715 auto *listSym = dyn_cast_or_null<Defined>(Val: cast<Symbol *>(Val: reloc->referent));
716 assert(listSym && "Protocol list reloc does not have a valid Defined");
717
718 ptrList.allPtrs.push_back(x: listSym);
719 off += target->wordSize;
720 }
721 assert((ptrListSym->isec()->getRelocAt(off) == nullptr) &&
722 "expected null terminating protocol");
723 assert(off + /*extra null value*/ target->wordSize == expectedListSize &&
724 "Protocol list end offset does not match expected size");
725}
726
727// Parse a protocol list and return the PointerListInfo for it
728ObjcCategoryMerger::PointerListInfo
729ObjcCategoryMerger::parseProtocolListInfo(const ConcatInputSection *isec,
730 uint32_t secOffset) {
731 PointerListInfo ptrList;
732 parseProtocolListInfo(isec, secOffset, ptrList);
733 return ptrList;
734}
735
736// Parse a pointer list that might be linked to ConcatInputSection at a given
737// offset. This can be used for instance methods, class methods, instance props
738// and class props since they have the same format.
739bool ObjcCategoryMerger::parsePointerListInfo(const ConcatInputSection *isec,
740 uint32_t secOffset,
741 PointerListInfo &ptrList) {
742 assert(ptrList.pointersPerStruct == 2 || ptrList.pointersPerStruct == 3);
743 assert(isec && "Trying to parse pointer list from null isec");
744 assert(secOffset + target->wordSize <= isec->data.size() &&
745 "Trying to read pointer list beyond section end");
746
747 const Relocation *reloc = isec->getRelocAt(off: secOffset);
748 // Empty list is a valid case, return true.
749 if (!reloc)
750 return true;
751
752 auto *ptrListSym = dyn_cast_or_null<Defined>(Val: cast<Symbol *>(Val: reloc->referent));
753 assert(ptrListSym && "Reloc does not have a valid Defined");
754
755 uint32_t thisStructSize = *reinterpret_cast<const uint32_t *>(
756 ptrListSym->isec()->data.data() + listHeaderLayout.structSizeOffset);
757 uint32_t thisStructCount = *reinterpret_cast<const uint32_t *>(
758 ptrListSym->isec()->data.data() + listHeaderLayout.structCountOffset);
759 assert(thisStructSize == ptrList.pointersPerStruct * target->wordSize);
760
761 assert(!ptrList.structSize || (thisStructSize == ptrList.structSize));
762
763 ptrList.structCount += thisStructCount;
764 ptrList.structSize = thisStructSize;
765
766 uint32_t expectedListSize =
767 listHeaderLayout.totalSize + (thisStructSize * thisStructCount);
768 assert(expectedListSize == ptrListSym->isec()->data.size() &&
769 "Pointer list does not match expected size");
770
771 for (uint32_t off = listHeaderLayout.totalSize; off < expectedListSize;
772 off += target->wordSize) {
773 const Relocation *reloc = ptrListSym->isec()->getRelocAt(off);
774 assert(reloc && "No reloc found at pointer list offset");
775
776 auto *listSym =
777 dyn_cast_or_null<Defined>(Val: reloc->referent.dyn_cast<Symbol *>());
778 // Sometimes, the reloc points to a StringPiece (InputSection + addend)
779 // instead of a symbol.
780 // TODO: Skip these cases for now, but we should fix this.
781 if (!listSym)
782 return false;
783
784 ptrList.allPtrs.push_back(x: listSym);
785 }
786
787 return true;
788}
789
790// Here we parse all the information of an input category (catInfo) and
791// append the parsed info into the structure which will contain all the
792// information about how a class is extended (extInfo)
793bool ObjcCategoryMerger::parseCatInfoToExtInfo(const InfoInputCategory &catInfo,
794 ClassExtensionInfo &extInfo) {
795 const Relocation *catNameReloc =
796 catInfo.catBodyIsec->getRelocAt(off: catLayout.nameOffset);
797
798 // Parse name
799 assert(catNameReloc && "Category does not have a reloc at 'nameOffset'");
800
801 // is this the first category we are parsing?
802 if (extInfo.mergedContainerName.empty())
803 extInfo.objFileForMergeData =
804 dyn_cast_or_null<ObjFile>(Val: catInfo.catBodyIsec->getFile());
805 else
806 extInfo.mergedContainerName += "|";
807
808 assert(extInfo.objFileForMergeData &&
809 "Expected to already have valid objextInfo.objFileForMergeData");
810
811 StringRef catName = catNameReloc->getReferentString();
812 extInfo.mergedContainerName += catName.str();
813
814 // Parse base class
815 if (!extInfo.baseClass) {
816 auto [classSym, classAddend] = tryGetSymbolReferenceAtIsecOffset(
817 isec: catInfo.catBodyIsec, offset: catLayout.klassOffset);
818 assert(extInfo.baseClassName.empty());
819 extInfo.baseClass = classSym;
820 extInfo.baseClassAddend = classAddend;
821 llvm::StringRef classPrefix(objc::symbol_names::klass);
822 if (classSym->getName().starts_with(Prefix: classPrefix))
823 extInfo.baseClassName =
824 classSym->getName().substr(Start: classPrefix.size()).str();
825 else
826 extInfo.baseClassName = classSym->getName().str();
827 } else {
828 [[maybe_unused]] auto [classSym, classAddend] =
829 tryGetSymbolReferenceAtIsecOffset(isec: catInfo.catBodyIsec,
830 offset: catLayout.klassOffset);
831 assert((extInfo.baseClass == classSym &&
832 extInfo.baseClassAddend == classAddend) &&
833 "Trying to parse category info into container with different base "
834 "class");
835 }
836
837 if (!parsePointerListInfo(isec: catInfo.catBodyIsec,
838 secOffset: catLayout.instanceMethodsOffset,
839 ptrList&: extInfo.instanceMethods))
840 return false;
841
842 if (!parsePointerListInfo(isec: catInfo.catBodyIsec, secOffset: catLayout.classMethodsOffset,
843 ptrList&: extInfo.classMethods))
844 return false;
845
846 parseProtocolListInfo(isec: catInfo.catBodyIsec, secOffset: catLayout.protocolsOffset,
847 ptrList&: extInfo.protocols);
848
849 if (!parsePointerListInfo(isec: catInfo.catBodyIsec, secOffset: catLayout.instancePropsOffset,
850 ptrList&: extInfo.instanceProps))
851 return false;
852
853 if (!parsePointerListInfo(isec: catInfo.catBodyIsec, secOffset: catLayout.classPropsOffset,
854 ptrList&: extInfo.classProps))
855 return false;
856
857 return true;
858}
859
860// Generate a protocol list (including header) and link it into the parent at
861// the specified offset.
862Defined *ObjcCategoryMerger::emitAndLinkProtocolList(
863 Defined *parentSym, uint32_t linkAtOffset,
864 const ClassExtensionInfo &extInfo, const PointerListInfo &ptrList) {
865 if (ptrList.allPtrs.empty())
866 return nullptr;
867
868 assert(ptrList.allPtrs.size() == ptrList.structCount);
869
870 uint32_t bodySize = (ptrList.structCount * target->wordSize) +
871 /*header(count)*/ protocolListHeaderLayout.totalSize +
872 /*extra null value*/ target->wordSize;
873 llvm::ArrayRef<uint8_t> bodyData = newSectionData(size: bodySize);
874
875 // This theoretically can be either 32b or 64b, but writing just the first 32b
876 // is good enough
877 const uint32_t *ptrProtoCount = reinterpret_cast<const uint32_t *>(
878 bodyData.data() + protocolListHeaderLayout.protocolCountOffset);
879
880 *const_cast<uint32_t *>(ptrProtoCount) = ptrList.allPtrs.size();
881
882 ConcatInputSection *listSec = make<ConcatInputSection>(
883 args&: *infoCategoryWriter.catPtrListInfo.inputSection, args&: bodyData,
884 args&: infoCategoryWriter.catPtrListInfo.align);
885 listSec->parent = infoCategoryWriter.catPtrListInfo.outputSection;
886 listSec->live = true;
887
888 listSec->parent = infoCategoryWriter.catPtrListInfo.outputSection;
889
890 std::string symName = ptrList.categoryPrefix;
891 symName += extInfo.baseClassName + "(" + extInfo.mergedContainerName + ")";
892
893 Defined *ptrListSym = make<Defined>(
894 args: newStringData(str: symName.c_str()), /*file=*/args: parentSym->getObjectFile(),
895 args&: listSec, /*value=*/args: 0, args: bodyData.size(), /*isWeakDef=*/args: false,
896 /*isExternal=*/args: false, /*isPrivateExtern=*/args: false, /*includeInSymtab=*/args: true,
897 /*isReferencedDynamically=*/args: false, /*noDeadStrip=*/args: false,
898 /*isWeakDefCanBeHidden=*/args: false);
899
900 ptrListSym->used = true;
901 parentSym->getObjectFile()->symbols.push_back(x: ptrListSym);
902 addInputSection(inputSection: listSec);
903
904 createSymbolReference(refFrom: parentSym, refTo: ptrListSym, offset: linkAtOffset,
905 relocTemplate: infoCategoryWriter.catBodyInfo.relocTemplate);
906
907 uint32_t offset = protocolListHeaderLayout.totalSize;
908 for (Symbol *symbol : ptrList.allPtrs) {
909 createSymbolReference(refFrom: ptrListSym, refTo: symbol, offset,
910 relocTemplate: infoCategoryWriter.catPtrListInfo.relocTemplate);
911 offset += target->wordSize;
912 }
913
914 return ptrListSym;
915}
916
917// Generate a pointer list (including header) and link it into the parent at the
918// specified offset. This is used for instance and class methods and
919// proprieties.
920void ObjcCategoryMerger::emitAndLinkPointerList(
921 Defined *parentSym, uint32_t linkAtOffset,
922 const ClassExtensionInfo &extInfo, const PointerListInfo &ptrList) {
923 if (ptrList.allPtrs.empty())
924 return;
925
926 assert(ptrList.allPtrs.size() * target->wordSize ==
927 ptrList.structCount * ptrList.structSize);
928
929 // Generate body
930 uint32_t bodySize =
931 listHeaderLayout.totalSize + (ptrList.structSize * ptrList.structCount);
932 llvm::ArrayRef<uint8_t> bodyData = newSectionData(size: bodySize);
933
934 const uint32_t *ptrStructSize = reinterpret_cast<const uint32_t *>(
935 bodyData.data() + listHeaderLayout.structSizeOffset);
936 const uint32_t *ptrStructCount = reinterpret_cast<const uint32_t *>(
937 bodyData.data() + listHeaderLayout.structCountOffset);
938
939 *const_cast<uint32_t *>(ptrStructSize) = ptrList.structSize;
940 *const_cast<uint32_t *>(ptrStructCount) = ptrList.structCount;
941
942 ConcatInputSection *listSec = make<ConcatInputSection>(
943 args&: *infoCategoryWriter.catPtrListInfo.inputSection, args&: bodyData,
944 args&: infoCategoryWriter.catPtrListInfo.align);
945 listSec->parent = infoCategoryWriter.catPtrListInfo.outputSection;
946 listSec->live = true;
947
948 listSec->parent = infoCategoryWriter.catPtrListInfo.outputSection;
949
950 std::string symName = ptrList.categoryPrefix;
951 symName += extInfo.baseClassName + "(" + extInfo.mergedContainerName + ")";
952
953 Defined *ptrListSym = make<Defined>(
954 args: newStringData(str: symName.c_str()), /*file=*/args: parentSym->getObjectFile(),
955 args&: listSec, /*value=*/args: 0, args: bodyData.size(), /*isWeakDef=*/args: false,
956 /*isExternal=*/args: false, /*isPrivateExtern=*/args: false, /*includeInSymtab=*/args: true,
957 /*isReferencedDynamically=*/args: false, /*noDeadStrip=*/args: false,
958 /*isWeakDefCanBeHidden=*/args: false);
959
960 ptrListSym->used = true;
961 parentSym->getObjectFile()->symbols.push_back(x: ptrListSym);
962 addInputSection(inputSection: listSec);
963
964 createSymbolReference(refFrom: parentSym, refTo: ptrListSym, offset: linkAtOffset,
965 relocTemplate: infoCategoryWriter.catBodyInfo.relocTemplate);
966
967 uint32_t offset = listHeaderLayout.totalSize;
968 for (Symbol *symbol : ptrList.allPtrs) {
969 createSymbolReference(refFrom: ptrListSym, refTo: symbol, offset,
970 relocTemplate: infoCategoryWriter.catPtrListInfo.relocTemplate);
971 offset += target->wordSize;
972 }
973}
974
975// This method creates an __objc_catlist ConcatInputSection with a single slot
976Defined *
977ObjcCategoryMerger::emitCatListEntrySec(const std::string &forCategoryName,
978 const std::string &forBaseClassName,
979 ObjFile *objFile) {
980 uint32_t sectionSize = target->wordSize;
981 llvm::ArrayRef<uint8_t> bodyData = newSectionData(size: sectionSize);
982
983 ConcatInputSection *newCatList =
984 make<ConcatInputSection>(args&: *infoCategoryWriter.catListInfo.inputSection,
985 args&: bodyData, args&: infoCategoryWriter.catListInfo.align);
986 newCatList->parent = infoCategoryWriter.catListInfo.outputSection;
987 newCatList->live = true;
988
989 newCatList->parent = infoCategoryWriter.catListInfo.outputSection;
990
991 std::string catSymName = "<__objc_catlist slot for merged category ";
992 catSymName += forBaseClassName + "(" + forCategoryName + ")>";
993
994 Defined *catListSym = make<Defined>(
995 args: newStringData(str: catSymName.c_str()), /*file=*/args&: objFile, args&: newCatList,
996 /*value=*/args: 0, args: bodyData.size(), /*isWeakDef=*/args: false, /*isExternal=*/args: false,
997 /*isPrivateExtern=*/args: false, /*includeInSymtab=*/args: false,
998 /*isReferencedDynamically=*/args: false, /*noDeadStrip=*/args: false,
999 /*isWeakDefCanBeHidden=*/args: false);
1000
1001 catListSym->used = true;
1002 objFile->symbols.push_back(x: catListSym);
1003 addInputSection(inputSection: newCatList);
1004 return catListSym;
1005}
1006
1007// Here we generate the main category body and link the name and base class into
1008// it. We don't link any other info yet like the protocol and class/instance
1009// methods/props.
1010Defined *ObjcCategoryMerger::emitCategoryBody(const std::string &name,
1011 const Defined *nameSym,
1012 const Symbol *baseClassSym,
1013 int64_t baseClassAddend,
1014 const std::string &baseClassName,
1015 ObjFile *objFile) {
1016 llvm::ArrayRef<uint8_t> bodyData = newSectionData(size: catLayout.totalSize);
1017
1018 uint32_t *ptrSize = (uint32_t *)(const_cast<uint8_t *>(bodyData.data()) +
1019 catLayout.sizeOffset);
1020 *ptrSize = catLayout.totalSize;
1021
1022 ConcatInputSection *newBodySec =
1023 make<ConcatInputSection>(args&: *infoCategoryWriter.catBodyInfo.inputSection,
1024 args&: bodyData, args&: infoCategoryWriter.catBodyInfo.align);
1025 newBodySec->parent = infoCategoryWriter.catBodyInfo.outputSection;
1026 newBodySec->live = true;
1027
1028 std::string symName =
1029 objc::symbol_names::category + baseClassName + "(" + name + ")";
1030 Defined *catBodySym = make<Defined>(
1031 args: newStringData(str: symName.c_str()), /*file=*/args&: objFile, args&: newBodySec,
1032 /*value=*/args: 0, args: bodyData.size(), /*isWeakDef=*/args: false, /*isExternal=*/args: false,
1033 /*isPrivateExtern=*/args: false, /*includeInSymtab=*/args: true,
1034 /*isReferencedDynamically=*/args: false, /*noDeadStrip=*/args: false,
1035 /*isWeakDefCanBeHidden=*/args: false);
1036
1037 catBodySym->used = true;
1038 objFile->symbols.push_back(x: catBodySym);
1039 addInputSection(inputSection: newBodySec);
1040
1041 createSymbolReference(refFrom: catBodySym, refTo: nameSym, offset: catLayout.nameOffset,
1042 relocTemplate: infoCategoryWriter.catBodyInfo.relocTemplate);
1043
1044 // Create a reloc to the base class (either external or internal)
1045 createSymbolReference(refFrom: catBodySym, refTo: baseClassSym, offset: catLayout.klassOffset,
1046 relocTemplate: infoCategoryWriter.catBodyInfo.relocTemplate,
1047 addend: baseClassAddend);
1048
1049 return catBodySym;
1050}
1051
1052// This writes the new category name (for the merged category) into the binary
1053// and returns the sybmol for it.
1054Defined *ObjcCategoryMerger::emitCategoryName(const std::string &name,
1055 ObjFile *objFile) {
1056 StringRef nameStrData = newStringData(str: name.c_str());
1057 // We use +1 below to include the null terminator
1058 llvm::ArrayRef<uint8_t> nameData(
1059 reinterpret_cast<const uint8_t *>(nameStrData.data()),
1060 nameStrData.size() + 1);
1061
1062 auto *parentSection = infoCategoryWriter.catNameInfo.inputSection;
1063 CStringInputSection *newStringSec = make<CStringInputSection>(
1064 args&: *infoCategoryWriter.catNameInfo.inputSection, args&: nameData,
1065 args&: infoCategoryWriter.catNameInfo.align, /*dedupLiterals=*/args: true);
1066
1067 parentSection->subsections.push_back(x: {.offset: 0, .isec: newStringSec});
1068
1069 newStringSec->splitIntoPieces();
1070 newStringSec->pieces[0].live = true;
1071 newStringSec->parent = infoCategoryWriter.catNameInfo.outputSection;
1072 in.cStringSection->addInput(newStringSec);
1073 assert(newStringSec->pieces.size() == 1);
1074
1075 Defined *catNameSym = make<Defined>(
1076 args: "<merged category name>", /*file=*/args&: objFile, args&: newStringSec,
1077 /*value=*/args: 0, args: nameData.size(),
1078 /*isWeakDef=*/args: false, /*isExternal=*/args: false, /*isPrivateExtern=*/args: false,
1079 /*includeInSymtab=*/args: false, /*isReferencedDynamically=*/args: false,
1080 /*noDeadStrip=*/args: false, /*isWeakDefCanBeHidden=*/args: false);
1081
1082 catNameSym->used = true;
1083 objFile->symbols.push_back(x: catNameSym);
1084 return catNameSym;
1085}
1086
1087// This method fully creates a new category from the given ClassExtensionInfo.
1088// It creates the category name, body and method/protocol/prop lists and links
1089// them all together. Then it creates a new __objc_catlist entry and adds the
1090// category to it. Calling this method will fully generate a category which will
1091// be available in the final binary.
1092Defined *ObjcCategoryMerger::emitCategory(const ClassExtensionInfo &extInfo) {
1093 Defined *catNameSym = emitCategoryName(name: extInfo.mergedContainerName,
1094 objFile: extInfo.objFileForMergeData);
1095
1096 Defined *catBodySym =
1097 emitCategoryBody(name: extInfo.mergedContainerName, nameSym: catNameSym,
1098 baseClassSym: extInfo.baseClass, baseClassAddend: extInfo.baseClassAddend,
1099 baseClassName: extInfo.baseClassName, objFile: extInfo.objFileForMergeData);
1100
1101 Defined *catListSym =
1102 emitCatListEntrySec(forCategoryName: extInfo.mergedContainerName, forBaseClassName: extInfo.baseClassName,
1103 objFile: extInfo.objFileForMergeData);
1104
1105 // Add the single category body to the category list at the offset 0.
1106 createSymbolReference(refFrom: catListSym, refTo: catBodySym, /*offset=*/0,
1107 relocTemplate: infoCategoryWriter.catListInfo.relocTemplate);
1108
1109 emitAndLinkPointerList(parentSym: catBodySym, linkAtOffset: catLayout.instanceMethodsOffset, extInfo,
1110 ptrList: extInfo.instanceMethods);
1111
1112 emitAndLinkPointerList(parentSym: catBodySym, linkAtOffset: catLayout.classMethodsOffset, extInfo,
1113 ptrList: extInfo.classMethods);
1114
1115 emitAndLinkProtocolList(parentSym: catBodySym, linkAtOffset: catLayout.protocolsOffset, extInfo,
1116 ptrList: extInfo.protocols);
1117
1118 emitAndLinkPointerList(parentSym: catBodySym, linkAtOffset: catLayout.instancePropsOffset, extInfo,
1119 ptrList: extInfo.instanceProps);
1120
1121 emitAndLinkPointerList(parentSym: catBodySym, linkAtOffset: catLayout.classPropsOffset, extInfo,
1122 ptrList: extInfo.classProps);
1123
1124 return catBodySym;
1125}
1126
1127// This method merges all the categories (sharing a base class) into a single
1128// category.
1129bool ObjcCategoryMerger::mergeCategoriesIntoSingleCategory(
1130 std::vector<InfoInputCategory> &categories) {
1131 assert(categories.size() > 1 && "Expected at least 2 categories");
1132
1133 ClassExtensionInfo extInfo(catLayout);
1134
1135 for (auto &catInfo : categories)
1136 if (!parseCatInfoToExtInfo(catInfo, extInfo))
1137 return false;
1138
1139 Defined *newCatDef = emitCategory(extInfo);
1140 assert(newCatDef && "Failed to create a new category");
1141
1142 // Suppress unsuded var warning
1143 (void)newCatDef;
1144
1145 for (auto &catInfo : categories)
1146 catInfo.wasMerged = true;
1147
1148 return true;
1149}
1150
1151void ObjcCategoryMerger::createSymbolReference(Defined *refFrom,
1152 const Symbol *refTo,
1153 uint32_t offset,
1154 const Relocation &relocTemplate,
1155 int64_t addend) {
1156 Relocation r = relocTemplate;
1157 r.offset = offset;
1158 r.addend = addend;
1159 r.referent = const_cast<Symbol *>(refTo);
1160 refFrom->isec()->relocs.push_back(x: r);
1161}
1162
1163// Get the list of categories in the '__objc_nlcatlist' section. We can't
1164// optimize these as they have a '+load' method that has to be called at
1165// runtime.
1166DenseSet<const Symbol *> ObjcCategoryMerger::collectNlCategories() {
1167 DenseSet<const Symbol *> nlCategories;
1168
1169 for (InputSection *sec : allInputSections) {
1170 if (sec->getName() != section_names::objcNonLazyCatList)
1171 continue;
1172
1173 for (auto &r : sec->relocs) {
1174 const Symbol *sym = r.referent.dyn_cast<Symbol *>();
1175 nlCategories.insert(V: sym);
1176 }
1177 }
1178 return nlCategories;
1179}
1180
1181void ObjcCategoryMerger::collectAndValidateCategoriesData() {
1182 auto nlCategories = collectNlCategories();
1183
1184 for (InputSection *sec : allInputSections) {
1185 if (sec->getName() != section_names::objcCatList)
1186 continue;
1187 ConcatInputSection *catListCisec = dyn_cast<ConcatInputSection>(Val: sec);
1188 assert(catListCisec &&
1189 "__objc_catList InputSection is not a ConcatInputSection");
1190
1191 for (uint32_t off = 0; off < catListCisec->getSize();
1192 off += target->wordSize) {
1193 Defined *categorySym = tryGetDefinedAtIsecOffset(isec: catListCisec, offset: off);
1194 assert(categorySym &&
1195 "Failed to get a valid category at __objc_catlit offset");
1196
1197 if (nlCategories.contains(V: categorySym))
1198 continue;
1199
1200 auto *catBodyIsec = dyn_cast<ConcatInputSection>(Val: categorySym->isec());
1201 assert(catBodyIsec &&
1202 "Category data section is not an ConcatInputSection");
1203
1204 InfoInputCategory catInputInfo{.catListIsec: catListCisec, .catBodyIsec: catBodyIsec, .offCatListIsec: off};
1205
1206 // Check that the category has a reloc at 'klassOffset' (which is
1207 // a pointer to the class symbol)
1208
1209 auto [classSym, classAddend] =
1210 tryGetSymbolReferenceAtIsecOffset(isec: catBodyIsec, offset: catLayout.klassOffset);
1211 assert(classSym && "Category does not have a valid base class");
1212
1213 if (!collectCategoryWriterInfoFromCategory(catInfo: catInputInfo))
1214 continue;
1215
1216 categoryMap[{classSym, classAddend}].push_back(x: catInputInfo);
1217 }
1218 }
1219}
1220
1221// In the input we have multiple __objc_catlist InputSection, each of which may
1222// contain links to multiple categories. Of these categories, we will merge (and
1223// erase) only some. There will be some categories that will remain untouched
1224// (not erased). For these not erased categories, we generate new __objc_catlist
1225// entries since the parent __objc_catlist entry will be erased
1226void ObjcCategoryMerger::generateCatListForNonErasedCategories(
1227 const MapVector<ConcatInputSection *, std::set<uint64_t>>
1228 catListToErasedOffsets) {
1229
1230 // Go through all offsets of all __objc_catlist's that we process and if there
1231 // are categories that we didn't process - generate a new __objc_catlist for
1232 // each.
1233 for (auto &mapEntry : catListToErasedOffsets) {
1234 ConcatInputSection *catListIsec = mapEntry.first;
1235 for (uint32_t catListIsecOffset = 0;
1236 catListIsecOffset < catListIsec->data.size();
1237 catListIsecOffset += target->wordSize) {
1238 // This slot was erased, we can just skip it
1239 if (mapEntry.second.count(x: catListIsecOffset))
1240 continue;
1241
1242 Defined *nonErasedCatBody =
1243 tryGetDefinedAtIsecOffset(isec: catListIsec, offset: catListIsecOffset);
1244 assert(nonErasedCatBody && "Failed to relocate non-deleted category");
1245
1246 // Allocate data for the new __objc_catlist slot
1247 llvm::ArrayRef<uint8_t> bodyData = newSectionData(size: target->wordSize);
1248
1249 // We mark the __objc_catlist slot as belonging to the same file as the
1250 // category
1251 ObjFile *objFile = dyn_cast<ObjFile>(Val: nonErasedCatBody->getFile());
1252
1253 ConcatInputSection *listSec = make<ConcatInputSection>(
1254 args&: *infoCategoryWriter.catListInfo.inputSection, args&: bodyData,
1255 args&: infoCategoryWriter.catListInfo.align);
1256 listSec->parent = infoCategoryWriter.catListInfo.outputSection;
1257 listSec->live = true;
1258
1259 std::string slotSymName = "<__objc_catlist slot for category ";
1260 slotSymName += nonErasedCatBody->getName();
1261 slotSymName += ">";
1262
1263 Defined *catListSlotSym = make<Defined>(
1264 args: newStringData(str: slotSymName.c_str()), /*file=*/args&: objFile, args&: listSec,
1265 /*value=*/args: 0, args: bodyData.size(),
1266 /*isWeakDef=*/args: false, /*isExternal=*/args: false, /*isPrivateExtern=*/args: false,
1267 /*includeInSymtab=*/args: false, /*isReferencedDynamically=*/args: false,
1268 /*noDeadStrip=*/args: false, /*isWeakDefCanBeHidden=*/args: false);
1269
1270 catListSlotSym->used = true;
1271 objFile->symbols.push_back(x: catListSlotSym);
1272 addInputSection(inputSection: listSec);
1273
1274 // Now link the category body into the newly created slot
1275 createSymbolReference(refFrom: catListSlotSym, refTo: nonErasedCatBody, offset: 0,
1276 relocTemplate: infoCategoryWriter.catListInfo.relocTemplate);
1277 }
1278 }
1279}
1280
1281void ObjcCategoryMerger::eraseISec(ConcatInputSection *isec) {
1282 isec->live = false;
1283 for (auto &sym : isec->symbols)
1284 sym->used = false;
1285}
1286
1287// This fully erases the merged categories, including their body, their names,
1288// their method/protocol/prop lists and the __objc_catlist entries that link to
1289// them.
1290void ObjcCategoryMerger::eraseMergedCategories() {
1291 // Map of InputSection to a set of offsets of the categories that were merged
1292 MapVector<ConcatInputSection *, std::set<uint64_t>> catListToErasedOffsets;
1293
1294 for (auto &mapEntry : categoryMap) {
1295 for (InfoInputCategory &catInfo : mapEntry.second) {
1296 if (catInfo.wasMerged) {
1297 eraseISec(isec: catInfo.catListIsec);
1298 catListToErasedOffsets[catInfo.catListIsec].insert(
1299 x: catInfo.offCatListIsec);
1300 }
1301 }
1302 }
1303
1304 // If there were categories that we did not erase, we need to generate a new
1305 // __objc_catList that contains only the un-merged categories, and get rid of
1306 // the references to the ones we merged.
1307 generateCatListForNonErasedCategories(catListToErasedOffsets);
1308
1309 // Erase the old method lists & names of the categories that were merged
1310 for (auto &mapEntry : categoryMap) {
1311 for (InfoInputCategory &catInfo : mapEntry.second) {
1312 if (!catInfo.wasMerged)
1313 continue;
1314
1315 eraseISec(isec: catInfo.catBodyIsec);
1316
1317 // We can't erase 'catLayout.nameOffset' for either Swift or ObjC
1318 // categories because the name will sometimes also be used for other
1319 // purposes.
1320 // For Swift, see usages of 'l_.str.11.SimpleClass' in
1321 // objc-category-merging-swift.s
1322 // For ObjC, see usages of 'l_OBJC_CLASS_NAME_.1' in
1323 // objc-category-merging-erase-objc-name-test.s
1324 // TODO: handle the above in a smarter way
1325
1326 tryEraseDefinedAtIsecOffset(isec: catInfo.catBodyIsec,
1327 offset: catLayout.instanceMethodsOffset);
1328 tryEraseDefinedAtIsecOffset(isec: catInfo.catBodyIsec,
1329 offset: catLayout.classMethodsOffset);
1330 tryEraseDefinedAtIsecOffset(isec: catInfo.catBodyIsec,
1331 offset: catLayout.protocolsOffset);
1332 tryEraseDefinedAtIsecOffset(isec: catInfo.catBodyIsec,
1333 offset: catLayout.classPropsOffset);
1334 tryEraseDefinedAtIsecOffset(isec: catInfo.catBodyIsec,
1335 offset: catLayout.instancePropsOffset);
1336 }
1337 }
1338}
1339
1340void ObjcCategoryMerger::doMerge() {
1341 collectAndValidateCategoriesData();
1342
1343 for (auto &[baseClassRef, catInfos] : categoryMap) {
1344 const auto &[baseClass, baseClassAddend] = baseClassRef;
1345 bool merged = false;
1346 if (auto *baseClassDef = dyn_cast<Defined>(Val: baseClass)) {
1347 // Merge all categories into the base class
1348 merged =
1349 mergeCategoriesIntoBaseClass(baseClass: baseClassDef, baseClassAddend, categories&: catInfos);
1350 } else if (catInfos.size() > 1) {
1351 // Merge all categories into a new, single category
1352 merged = mergeCategoriesIntoSingleCategory(categories&: catInfos);
1353 }
1354 if (!merged)
1355 warn(msg: "ObjC category merging skipped for class symbol' " +
1356 baseClass->getName().str() + "'\n");
1357 }
1358
1359 // Erase all categories that were merged
1360 eraseMergedCategories();
1361}
1362
1363void ObjcCategoryMerger::doCleanup() { generatedSectionData.clear(); }
1364
1365StringRef ObjcCategoryMerger::newStringData(const char *str) {
1366 uint32_t len = strlen(s: str);
1367 uint32_t bufSize = len + 1;
1368 SmallVector<uint8_t> &data = newSectionData(size: bufSize);
1369 char *strData = reinterpret_cast<char *>(data.data());
1370 // Copy the string chars and null-terminator
1371 memcpy(dest: strData, src: str, n: bufSize);
1372 return StringRef(strData, len);
1373}
1374
1375SmallVector<uint8_t> &ObjcCategoryMerger::newSectionData(uint32_t size) {
1376 generatedSectionData.push_back(
1377 Elt: std::make_unique<SmallVector<uint8_t>>(args&: size, args: 0));
1378 return *generatedSectionData.back();
1379}
1380
1381} // namespace
1382
1383void objc::mergeCategories() {
1384 TimeTraceScope timeScope("ObjcCategoryMerger");
1385
1386 ObjcCategoryMerger merger(inputSections);
1387 merger.doMerge();
1388}
1389
1390void objc::doCleanup() { ObjcCategoryMerger::doCleanup(); }
1391
1392bool ObjcCategoryMerger::mergeCategoriesIntoBaseClass(
1393 const Defined *baseClass, int64_t baseClassAddend,
1394 std::vector<InfoInputCategory> &categories) {
1395 assert(categories.size() >= 1 && "Expected at least one category to merge");
1396
1397 // Collect all the info from the categories
1398 ClassExtensionInfo extInfo(catLayout);
1399 extInfo.baseClass = baseClass;
1400 extInfo.baseClassAddend = baseClassAddend;
1401
1402 for (auto &catInfo : categories)
1403 if (!parseCatInfoToExtInfo(catInfo, extInfo))
1404 return false;
1405
1406 // Get metadata for the base class
1407 Defined *metaRo = getClassRo(classSym: baseClass, classAddend: baseClassAddend, /*getMetaRo=*/true);
1408 Defined *classRo =
1409 getClassRo(classSym: baseClass, classAddend: baseClassAddend, /*getMetaRo=*/false);
1410 if (!metaRo || !classRo)
1411 return false;
1412
1413 ConcatInputSection *metaIsec = dyn_cast<ConcatInputSection>(Val: metaRo->isec());
1414 ConcatInputSection *classIsec = dyn_cast<ConcatInputSection>(Val: classRo->isec());
1415 if (!metaIsec || !classIsec)
1416 return false;
1417
1418 // Now collect the info from the base class from the various lists in the
1419 // class metadata
1420
1421 // Protocol lists are a special case - the same protocol list is in classRo
1422 // and metaRo, so we only need to parse it once
1423 parseProtocolListInfo(isec: classIsec, secOffset: roClassLayout.baseProtocolsOffset,
1424 ptrList&: extInfo.protocols);
1425
1426 // Check that the classRo and metaRo protocol lists are identical
1427 assert(
1428 parseProtocolListInfo(classIsec, roClassLayout.baseProtocolsOffset) ==
1429 parseProtocolListInfo(metaIsec, roClassLayout.baseProtocolsOffset) &&
1430 "Category merger expects classRo and metaRo to have the same protocol "
1431 "list");
1432
1433 parsePointerListInfo(isec: metaIsec, secOffset: roClassLayout.baseMethodsOffset,
1434 ptrList&: extInfo.classMethods);
1435 parsePointerListInfo(isec: classIsec, secOffset: roClassLayout.baseMethodsOffset,
1436 ptrList&: extInfo.instanceMethods);
1437
1438 parsePointerListInfo(isec: metaIsec, secOffset: roClassLayout.basePropertiesOffset,
1439 ptrList&: extInfo.classProps);
1440 parsePointerListInfo(isec: classIsec, secOffset: roClassLayout.basePropertiesOffset,
1441 ptrList&: extInfo.instanceProps);
1442
1443 // Erase the old lists - these will be generated and replaced
1444 eraseSymbolAtIsecOffset(isec: metaIsec, offset: roClassLayout.baseMethodsOffset);
1445 eraseSymbolAtIsecOffset(isec: metaIsec, offset: roClassLayout.baseProtocolsOffset);
1446 eraseSymbolAtIsecOffset(isec: metaIsec, offset: roClassLayout.basePropertiesOffset);
1447 eraseSymbolAtIsecOffset(isec: classIsec, offset: roClassLayout.baseMethodsOffset);
1448 eraseSymbolAtIsecOffset(isec: classIsec, offset: roClassLayout.baseProtocolsOffset);
1449 eraseSymbolAtIsecOffset(isec: classIsec, offset: roClassLayout.basePropertiesOffset);
1450
1451 // Emit the newly merged lists - first into the meta RO then into the class RO
1452 // First we emit and link the protocol list into the meta RO. Then we link it
1453 // in the classRo as well (they're supposed to be identical)
1454 if (Defined *protoListSym =
1455 emitAndLinkProtocolList(parentSym: metaRo, linkAtOffset: roClassLayout.baseProtocolsOffset,
1456 extInfo, ptrList: extInfo.protocols)) {
1457 createSymbolReference(refFrom: classRo, refTo: protoListSym,
1458 offset: roClassLayout.baseProtocolsOffset,
1459 relocTemplate: infoCategoryWriter.catBodyInfo.relocTemplate);
1460 }
1461
1462 emitAndLinkPointerList(parentSym: metaRo, linkAtOffset: roClassLayout.baseMethodsOffset, extInfo,
1463 ptrList: extInfo.classMethods);
1464 emitAndLinkPointerList(parentSym: classRo, linkAtOffset: roClassLayout.baseMethodsOffset, extInfo,
1465 ptrList: extInfo.instanceMethods);
1466
1467 emitAndLinkPointerList(parentSym: metaRo, linkAtOffset: roClassLayout.basePropertiesOffset, extInfo,
1468 ptrList: extInfo.classProps);
1469
1470 emitAndLinkPointerList(parentSym: classRo, linkAtOffset: roClassLayout.basePropertiesOffset, extInfo,
1471 ptrList: extInfo.instanceProps);
1472
1473 // Mark all the categories as merged - this will be used to erase them later
1474 for (auto &catInfo : categories)
1475 catInfo.wasMerged = true;
1476
1477 return true;
1478}
1479
1480// Erase the symbol at a given offset in an InputSection
1481void ObjcCategoryMerger::eraseSymbolAtIsecOffset(ConcatInputSection *isec,
1482 uint32_t offset) {
1483 Defined *sym = tryGetDefinedAtIsecOffset(isec, offset);
1484 if (!sym)
1485 return;
1486
1487 // Remove the symbol from isec->symbols
1488 assert(isa<Defined>(sym) && "Can only erase a Defined");
1489 llvm::erase(C&: isec->symbols, V: sym);
1490
1491 // Remove the relocs that refer to this symbol
1492 auto removeAtOff = [offset](Relocation const &r) {
1493 return r.offset == offset;
1494 };
1495 llvm::erase_if(C&: isec->relocs, P: removeAtOff);
1496
1497 // Now, if the symbol fully occupies a ConcatInputSection, we can also erase
1498 // the whole ConcatInputSection
1499 if (ConcatInputSection *cisec = dyn_cast<ConcatInputSection>(Val: sym->isec()))
1500 if (cisec->data.size() == sym->size)
1501 eraseISec(isec: cisec);
1502}
1503