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 "InputFiles.h"
11#include "InputSection.h"
12#include "Layout.h"
13#include "OutputSegment.h"
14#include "Target.h"
15
16#include "lld/Common/ErrorHandler.h"
17#include "llvm/ADT/DenseMap.h"
18#include "llvm/BinaryFormat/MachO.h"
19#include "llvm/Bitcode/BitcodeReader.h"
20
21using namespace llvm;
22using namespace llvm::MachO;
23using namespace lld;
24using namespace lld::macho;
25
26template <class LP> static bool objectHasObjCSection(MemoryBufferRef mb) {
27 using SectionHeader = typename LP::section;
28
29 auto *hdr =
30 reinterpret_cast<const typename LP::mach_header *>(mb.getBufferStart());
31 if (hdr->magic != LP::magic)
32 return false;
33
34 if (const auto *c =
35 findCommand<typename LP::segment_command>(hdr, LP::segmentLCType)) {
36 auto sectionHeaders = ArrayRef<SectionHeader>{
37 reinterpret_cast<const SectionHeader *>(c + 1), c->nsects};
38 for (const SectionHeader &secHead : sectionHeaders) {
39 StringRef sectname(secHead.sectname,
40 strnlen(secHead.sectname, sizeof(secHead.sectname)));
41 StringRef segname(secHead.segname,
42 strnlen(secHead.segname, sizeof(secHead.segname)));
43 if ((segname == segment_names::data &&
44 sectname == section_names::objcCatList) ||
45 (segname == segment_names::text &&
46 sectname.starts_with(Prefix: section_names::swift))) {
47 return true;
48 }
49 }
50 }
51 return false;
52}
53
54static bool objectHasObjCSection(MemoryBufferRef mb) {
55 if (target->wordSize == 8)
56 return ::objectHasObjCSection<LP64>(mb);
57 else
58 return ::objectHasObjCSection<ILP32>(mb);
59}
60
61bool macho::hasObjCSection(MemoryBufferRef mb) {
62 switch (identify_magic(magic: mb.getBuffer())) {
63 case file_magic::macho_object:
64 return objectHasObjCSection(mb);
65 case file_magic::bitcode:
66 return check(e: isBitcodeContainingObjCCategory(Buffer: mb));
67 default:
68 return false;
69 }
70}
71
72namespace {
73
74#define FOR_EACH_CATEGORY_FIELD(DO) \
75 DO(Ptr, name) \
76 DO(Ptr, klass) \
77 DO(Ptr, instanceMethods) \
78 DO(Ptr, classMethods) \
79 DO(Ptr, protocols) \
80 DO(Ptr, instanceProps) \
81 DO(Ptr, classProps)
82
83CREATE_LAYOUT_CLASS(Category, FOR_EACH_CATEGORY_FIELD);
84
85#undef FOR_EACH_CATEGORY_FIELD
86
87#define FOR_EACH_CLASS_FIELD(DO) \
88 DO(Ptr, metaClass) \
89 DO(Ptr, superClass) \
90 DO(Ptr, methodCache) \
91 DO(Ptr, vtable) \
92 DO(Ptr, roData)
93
94CREATE_LAYOUT_CLASS(Class, FOR_EACH_CLASS_FIELD);
95
96#undef FOR_EACH_CLASS_FIELD
97
98#define FOR_EACH_RO_CLASS_FIELD(DO) \
99 DO(uint32_t, flags) \
100 DO(uint32_t, instanceStart) \
101 DO(Ptr, instanceSize) \
102 DO(Ptr, ivarLayout) \
103 DO(Ptr, name) \
104 DO(Ptr, baseMethods) \
105 DO(Ptr, baseProtocols) \
106 DO(Ptr, ivars) \
107 DO(Ptr, weakIvarLayout) \
108 DO(Ptr, baseProperties)
109
110CREATE_LAYOUT_CLASS(ROClass, FOR_EACH_RO_CLASS_FIELD);
111
112#undef FOR_EACH_RO_CLASS_FIELD
113
114#define FOR_EACH_LIST_HEADER(DO) \
115 DO(uint32_t, size) \
116 DO(uint32_t, count)
117
118CREATE_LAYOUT_CLASS(ListHeader, FOR_EACH_LIST_HEADER);
119
120#undef FOR_EACH_LIST_HEADER
121
122#define FOR_EACH_METHOD(DO) \
123 DO(Ptr, name) \
124 DO(Ptr, type) \
125 DO(Ptr, impl)
126
127CREATE_LAYOUT_CLASS(Method, FOR_EACH_METHOD);
128
129#undef FOR_EACH_METHOD
130
131enum MethodContainerKind {
132 MCK_Class,
133 MCK_Category,
134};
135
136struct MethodContainer {
137 MethodContainerKind kind;
138 const ConcatInputSection *isec;
139};
140
141enum MethodKind {
142 MK_Instance,
143 MK_Static,
144};
145
146struct ObjcClass {
147 DenseMap<CachedHashStringRef, MethodContainer> instanceMethods;
148 DenseMap<CachedHashStringRef, MethodContainer> classMethods;
149};
150
151} // namespace
152
153class ObjcCategoryChecker {
154public:
155 ObjcCategoryChecker();
156 void parseCategory(const ConcatInputSection *catListIsec);
157
158private:
159 void parseClass(const Defined *classSym);
160 void parseMethods(const ConcatInputSection *methodsIsec,
161 const Symbol *methodContainer,
162 const ConcatInputSection *containerIsec,
163 MethodContainerKind, MethodKind);
164
165 CategoryLayout catLayout;
166 ClassLayout classLayout;
167 ROClassLayout roClassLayout;
168 ListHeaderLayout listHeaderLayout;
169 MethodLayout methodLayout;
170
171 DenseMap<const Symbol *, ObjcClass> classMap;
172};
173
174ObjcCategoryChecker::ObjcCategoryChecker()
175 : catLayout(target->wordSize), classLayout(target->wordSize),
176 roClassLayout(target->wordSize), listHeaderLayout(target->wordSize),
177 methodLayout(target->wordSize) {}
178
179// \p r must point to an offset within a cstring section.
180static StringRef getReferentString(const Reloc &r) {
181 if (auto *isec = r.referent.dyn_cast<InputSection *>())
182 return cast<CStringInputSection>(Val: isec)->getStringRefAtOffset(off: r.addend);
183 auto *sym = cast<Defined>(Val: r.referent.get<Symbol *>());
184 return cast<CStringInputSection>(Val: sym->isec)->getStringRefAtOffset(off: sym->value +
185 r.addend);
186}
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 Reloc &r : methodsIsec->relocs) {
195 if ((r.offset - listHeaderLayout.totalSize) % methodLayout.totalSize !=
196 methodLayout.nameOffset)
197 continue;
198
199 CachedHashStringRef methodName(getReferentString(r));
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 Reloc *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 = getReferentString(r: *nameReloc);
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 getReferentString(r: *containerIsec->getRelocAt(off: catLayout.nameOffset));
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 = classReloc->referent.get<Symbol *>();
266 if (auto *d = dyn_cast<Defined>(Val: classSym))
267 if (!classMap.count(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 ObjcCategoryChecker checker;
315 for (const InputSection *isec : inputSections) {
316 if (isec->getName() == section_names::objcCatList)
317 for (const Reloc &r : isec->relocs) {
318 auto *catIsec = cast<ConcatInputSection>(Val: r.getReferentInputSection());
319 checker.parseCategory(catIsec);
320 }
321 }
322}
323

source code of lld/MachO/ObjC.cpp