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 | |
21 | using namespace llvm; |
22 | using namespace llvm::MachO; |
23 | using namespace lld; |
24 | using namespace lld::macho; |
25 | |
26 | template <class LP> static bool objectHasObjCSection(MemoryBufferRef mb) { |
27 | using = 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 = 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 | |
54 | static bool objectHasObjCSection(MemoryBufferRef mb) { |
55 | if (target->wordSize == 8) |
56 | return ::objectHasObjCSection<LP64>(mb); |
57 | else |
58 | return ::objectHasObjCSection<ILP32>(mb); |
59 | } |
60 | |
61 | bool 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 | |
72 | namespace { |
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 | |
83 | CREATE_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 | |
94 | CREATE_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 | |
110 | CREATE_LAYOUT_CLASS(ROClass, FOR_EACH_RO_CLASS_FIELD); |
111 | |
112 | #undef FOR_EACH_RO_CLASS_FIELD |
113 | |
114 | #define (DO) \ |
115 | DO(uint32_t, ) \ |
116 | DO(uint32_t, ) |
117 | |
118 | CREATE_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 | |
127 | CREATE_LAYOUT_CLASS(Method, FOR_EACH_METHOD); |
128 | |
129 | #undef FOR_EACH_METHOD |
130 | |
131 | enum MethodContainerKind { |
132 | MCK_Class, |
133 | MCK_Category, |
134 | }; |
135 | |
136 | struct MethodContainer { |
137 | MethodContainerKind kind; |
138 | const ConcatInputSection *isec; |
139 | }; |
140 | |
141 | enum MethodKind { |
142 | MK_Instance, |
143 | MK_Static, |
144 | }; |
145 | |
146 | struct ObjcClass { |
147 | DenseMap<CachedHashStringRef, MethodContainer> instanceMethods; |
148 | DenseMap<CachedHashStringRef, MethodContainer> classMethods; |
149 | }; |
150 | |
151 | } // namespace |
152 | |
153 | class ObjcCategoryChecker { |
154 | public: |
155 | ObjcCategoryChecker(); |
156 | void parseCategory(const ConcatInputSection *catListIsec); |
157 | |
158 | private: |
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 ; |
169 | MethodLayout methodLayout; |
170 | |
171 | DenseMap<const Symbol *, ObjcClass> classMap; |
172 | }; |
173 | |
174 | ObjcCategoryChecker::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. |
180 | static 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 | |
188 | void 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 | |
260 | void 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 | |
281 | void 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 | |
313 | void 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 | |