[lld] ef12275 - [lld-macho] Warn on method name collisions from category definitions

Jez Ng via llvm-commits llvm-commits at lists.llvm.org
Tue Mar 7 14:49:49 PST 2023


Author: Jez Ng
Date: 2023-03-07T14:48:56-08:00
New Revision: ef122753db7fe8e9a0b7bedd46d2f3668a780fcb

URL: https://github.com/llvm/llvm-project/commit/ef122753db7fe8e9a0b7bedd46d2f3668a780fcb
DIFF: https://github.com/llvm/llvm-project/commit/ef122753db7fe8e9a0b7bedd46d2f3668a780fcb.diff

LOG: [lld-macho] 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.

Reviewed By: #lld-macho, thevinster, oontvoo

Differential Revision: https://reviews.llvm.org/D142916

Added: 
    lld/MachO/Layout.h
    lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libobjc.tbd
    lld/test/MachO/objc-category-conflicts.s

Modified: 
    lld/MachO/Driver.cpp
    lld/MachO/InputFiles.cpp
    lld/MachO/InputSection.cpp
    lld/MachO/InputSection.h
    lld/MachO/ObjC.cpp
    lld/MachO/ObjC.h
    lld/MachO/Relocations.cpp
    lld/MachO/Relocations.h
    lld/MachO/UnwindInfoSection.cpp
    lld/test/MachO/objc-imageinfo.s

Removed: 
    


################################################################################
diff  --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index 0f2326b305b13..322d7de30b63f 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -1920,6 +1920,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();

diff  --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp
index ed0d98a2ececd..65e06a8557d9c 100644
--- a/lld/MachO/InputFiles.cpp
+++ b/lld/MachO/InputFiles.cpp
@@ -1272,13 +1272,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;
 }

diff  --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp
index 1d8d58477139f..ff5a15067adcd 100644
--- a/lld/MachO/InputSection.cpp
+++ b/lld/MachO/InputSection.cpp
@@ -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;

diff  --git a/lld/MachO/InputSection.h b/lld/MachO/InputSection.h
index 5a6a205f9047c..becb01017d633 100644
--- a/lld/MachO/InputSection.h
+++ b/lld/MachO/InputSection.h
@@ -55,6 +55,8 @@ class InputSection {
   // 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 @@ class CStringInputSection final : public InputSection {
     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 @@ class CStringInputSection final : public InputSection {
 
   bool deduplicateLiterals = false;
   std::vector<StringPiece> pieces;
+
+private:
+  size_t getStringPieceIndex(uint64_t off) const;
 };
 
 class WordLiteralInputSection final : public InputSection {

diff  --git a/lld/MachO/Layout.h b/lld/MachO/Layout.h
new file mode 100644
index 0000000000000..6a7653f315e3c
--- /dev/null
+++ b/lld/MachO/Layout.h
@@ -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)                                            \
+    };                                                                         \
+  }

diff  --git a/lld/MachO/ObjC.cpp b/lld/MachO/ObjC.cpp
index d484c4029f6be..bdb125859b719 100644
--- a/lld/MachO/ObjC.cpp
+++ b/lld/MachO/ObjC.cpp
@@ -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,228 @@ 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 *d = cast<Defined>(r.referent.get<Symbol *>());
+  return cast<CStringInputSection>(d->isec)->getStringRefAtOffset(d->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);
+      }
+  }
+}

diff  --git a/lld/MachO/ObjC.h b/lld/MachO/ObjC.h
index 67fa4114db007..560c5cc0bc509 100644
--- a/lld/MachO/ObjC.h
+++ b/lld/MachO/ObjC.h
@@ -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);

diff  --git a/lld/MachO/Relocations.cpp b/lld/MachO/Relocations.cpp
index 9e5ac69612cfd..4e840c6912cc5 100644
--- a/lld/MachO/Relocations.cpp
+++ b/lld/MachO/Relocations.cpp
@@ -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);

diff  --git a/lld/MachO/Relocations.h b/lld/MachO/Relocations.h
index 023d25a795a0d..5f161c8fcbfde 100644
--- a/lld/MachO/Relocations.h
+++ b/lld/MachO/Relocations.h
@@ -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 *,

diff  --git a/lld/MachO/UnwindInfoSection.cpp b/lld/MachO/UnwindInfoSection.cpp
index b7d0d563360a4..d480f7ed294a6 100644
--- a/lld/MachO/UnwindInfoSection.cpp
+++ b/lld/MachO/UnwindInfoSection.cpp
@@ -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;
-
-  CompactUnwindOffsets(size_t wordSize) {
-    if (wordSize == 8)
-      init<uint64_t>();
-    else {
-      assert(wordSize == 4);
-      init<uint32_t>();
-    }
-  }
+// 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)
 
-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);
-  }
+CREATE_LAYOUT_CLASS(CompactUnwind, FOR_EACH_CU_FIELD);
 
-  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 @@ class UnwindInfoSectionImpl final : public UnwindInfoSection {
 
   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();
     }
   });
 }

diff  --git a/lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libobjc.tbd b/lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libobjc.tbd
new file mode 100644
index 0000000000000..9340c4abf93cd
--- /dev/null
+++ b/lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libobjc.tbd
@@ -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 ]
+...

diff  --git a/lld/test/MachO/objc-category-conflicts.s b/lld/test/MachO/objc-category-conflicts.s
new file mode 100644
index 0000000000000..c88783e751fdb
--- /dev/null
+++ b/lld/test/MachO/objc-category-conflicts.s
@@ -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 at 0:8", "-[Foo(Cat1) m1]"
+  .empty_objc_method "m2", "v16 at 0:8", "-[Foo(Cat2) m2]"
+
+__OBJC_$_CATEGORY_CLASS_METHODS_Foo_$_Cat1:
+  .long 24
+  .long 2
+  .empty_objc_method "s1", "v16 at 0:8", "+[Foo(Cat1) s1]"
+  .empty_objc_method "s2", "v16 at 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 at 0:8", "-[Foo(Cat2) m2]"
+
+__OBJC_$_CATEGORY_CLASS_METHODS_Foo_$_Cat2:
+  .long 24
+  .long 1
+  .empty_objc_method "s2", "v16 at 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 at 0:8", "+[Foo s1]"
+
+__OBJC_$_INSTANCE_METHODS_Foo:
+  .long 24
+  .long 1
+  .empty_objc_method "m1", "v16 at 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

diff  --git a/lld/test/MachO/objc-imageinfo.s b/lld/test/MachO/objc-imageinfo.s
index 0523aac841c77..f83e93e7c0348 100644
--- a/lld/test/MachO/objc-imageinfo.s
+++ b/lld/test/MachO/objc-imageinfo.s
@@ -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


        


More information about the llvm-commits mailing list