diff --git a/llvm/include/llvm/TextAPI/MachO/InterfaceFile.h b/llvm/include/llvm/TextAPI/MachO/InterfaceFile.h --- a/llvm/include/llvm/TextAPI/MachO/InterfaceFile.h +++ b/llvm/include/llvm/TextAPI/MachO/InterfaceFile.h @@ -336,9 +336,7 @@ /// Add a library for inlining to top level library. /// ///\param Document The library to inline with top level library. - void addDocument(std::shared_ptr &&Document) { - Documents.emplace_back(std::move(Document)); - } + void addDocument(std::shared_ptr &&Document); /// Get the list of inlined libraries. /// @@ -397,6 +395,14 @@ fn); } + /// The equality is determined by attributes that impact linking + /// compatibilities. UUIDs, Path, & FileKind are irrelevant since these by + /// itself should not impact linking. + /// This is an expensive operation. + bool operator==(const InterfaceFile &O) const; + + bool operator!=(const InterfaceFile &O) const { return !(*this == O); } + private: llvm::BumpPtrAllocator Allocator; StringRef copyString(StringRef String) { @@ -427,6 +433,21 @@ SymbolMapType Symbols; }; +template +bool operator==(const DenseMapBase &LHS, + const DenseMapBase &RHS) { + if (LHS.size() != RHS.size()) + return false; + for (auto KV : LHS) { + auto I = RHS.find(KV.first); + if (I == RHS.end() || *I->second != *KV.second) + return false; + } + return true; +} + } // end namespace MachO. } // end namespace llvm. diff --git a/llvm/include/llvm/TextAPI/MachO/Symbol.h b/llvm/include/llvm/TextAPI/MachO/Symbol.h --- a/llvm/include/llvm/TextAPI/MachO/Symbol.h +++ b/llvm/include/llvm/TextAPI/MachO/Symbol.h @@ -104,6 +104,15 @@ void dump() const { dump(llvm::errs()); } #endif + bool operator==(const Symbol &O) const { + return (Kind == O.Kind) && (Name == O.Name) && (Targets == O.Targets) && + (Flags == O.Flags); + } + + bool operator!=(const Symbol &O) const { + return !(*this == O); + } + private: StringRef Name; TargetList Targets; diff --git a/llvm/lib/TextAPI/MachO/InterfaceFile.cpp b/llvm/lib/TextAPI/MachO/InterfaceFile.cpp --- a/llvm/lib/TextAPI/MachO/InterfaceFile.cpp +++ b/llvm/lib/TextAPI/MachO/InterfaceFile.cpp @@ -117,3 +117,46 @@ for (const auto &Target : Targets) result.first->second->addTarget(Target); } + +void InterfaceFile::addDocument(std::shared_ptr &&Document) { + auto Pos = llvm::lower_bound(Documents, Document, + [](const std::shared_ptr &LHS, + const std::shared_ptr &RHS) { + return LHS->InstallName < RHS->InstallName; + }); + Documents.insert(Pos, Document); +} + +bool InterfaceFile::operator==(const InterfaceFile &O) const { + if (Targets != O.Targets) + return false; + if (InstallName != O.InstallName) + return false; + if ((CurrentVersion != O.CurrentVersion) || + (CompatibilityVersion != O.CompatibilityVersion)) + return false; + if (SwiftABIVersion != O.SwiftABIVersion) + return false; + if (IsTwoLevelNamespace != O.IsTwoLevelNamespace) + return false; + if (IsAppExtensionSafe != O.IsAppExtensionSafe) + return false; + if (IsInstallAPI != O.IsInstallAPI) + return false; + if (ParentUmbrellas != O.ParentUmbrellas) + return false; + if (AllowableClients != O.AllowableClients) + return false; + if (ReexportedLibraries != O.ReexportedLibraries) + return false; + if (Symbols != O.Symbols) + return false; + if (!std::equal(Documents.begin(), Documents.end(), O.Documents.begin(), + O.Documents.end(), + [](const std::shared_ptr LHS, + const std::shared_ptr RHS) { + return *LHS == *RHS; + })) + return false; + return true; +} diff --git a/llvm/unittests/TextAPI/TextStubHelpers.h b/llvm/unittests/TextAPI/TextStubHelpers.h --- a/llvm/unittests/TextAPI/TextStubHelpers.h +++ b/llvm/unittests/TextAPI/TextStubHelpers.h @@ -40,5 +40,24 @@ S.erase(std::remove_if(S.begin(), S.end(), ::isspace), S.end()); return S; } + +// This will transform a single InterfaceFile then compare against the other +// InterfaceFile then transform the second InterfaceFile in the same way to +// regain equality. +inline bool +checkEqualityOnTransform(MachO::InterfaceFile &FileA, + MachO::InterfaceFile &FileB, + void (*Transform)(MachO::InterfaceFile *)) { + Transform(&FileA); + // Files should not be equal. + if (FileA == FileB) + return false; + Transform(&FileB); + // Files should be equal. + if (FileA != FileB) + return false; + return true; +} + } // namespace llvm #endif diff --git a/llvm/unittests/TextAPI/TextStubV3Tests.cpp b/llvm/unittests/TextAPI/TextStubV3Tests.cpp --- a/llvm/unittests/TextAPI/TextStubV3Tests.cpp +++ b/llvm/unittests/TextAPI/TextStubV3Tests.cpp @@ -836,4 +836,114 @@ ErrorMessage); } +TEST(TBDv3, InterfaceEquality) { + static const char TBDv3File[] = + "--- !tapi-tbd-v3\n" + "archs: [ armv7, arm64 ]\n" + "uuids: [ 'armv7: 00000000-0000-0000-0000-000000000000',\n" + " 'arm64: 11111111-1111-1111-1111-111111111111']\n" + "platform: ios\n" + "flags: [ installapi ]\n" + "install-name: Test.dylib\n" + "current-version: 2.3.4\n" + "compatibility-version: 1.0\n" + "swift-abi-version: 1.1\n" + "parent-umbrella: Umbrella.dylib\n" + "exports:\n" + " - archs: [ armv7, arm64 ]\n" + " allowable-clients: [ clientA ]\n" + " re-exports: [ /usr/lib/libfoo.dylib ]\n" + " symbols: [ _sym1, _sym2, _sym3, _sym4, $ld$hide$os9.0$_sym1 ]\n" + " objc-classes: [ class1, class2 ]\n" + " objc-eh-types: [ class1 ]\n" + " objc-ivars: [ class1._ivar1, class1._ivar2 ]\n" + " weak-def-symbols: [ _weak1, _weak2 ]\n" + " thread-local-symbols: [ _tlv1, _tlv3 ]\n" + " - archs: [ armv7 ]\n" + " symbols: [ _sym5 ]\n" + " objc-classes: [ class3 ]\n" + " objc-ivars: [ class1._ivar3 ]\n" + " weak-def-symbols: [ _weak3 ]\n" + " thread-local-symbols: [ _tlv3 ]\n" + "--- !tapi-tbd-v3\n" + "archs: [ i386 ]\n" + "platform: macosx\n" + "install-name: '/usr/lib/libbar.dylib'\n" + "current-version: 0\n" + "compatibility-version: 0\n" + "swift-abi-version: 5\n" + "objc-constraint: none\n" + "exports:\n" + " - archs: [ i386 ]\n" + " symbols: [ _sym3, _sym4 ]\n" + "...\n"; + Expected ResultA = + TextAPIReader::get(MemoryBufferRef(TBDv3File, "TestA.tbd")); + EXPECT_TRUE(!!ResultA); + InterfaceFile FileA = std::move(*ResultA.get()); + Expected ResultB = + TextAPIReader::get(MemoryBufferRef(TBDv3File, "TestB.tbd")); + EXPECT_TRUE(!!ResultB); + InterfaceFile FileB = std::move(*ResultB.get()); + EXPECT_FALSE(FileA.getPath() == FileB.getPath()); + EXPECT_TRUE(FileA == FileB); +} + + + +TEST(TBDv3, InterfaceInequality) { + static const char TBDv3File[] = "--- !tapi-tbd-v3\n" + "archs: [ armv7, arm64 ]\n" + "platform: ios\n" + "install-name: Test.dylib\n" + "...\n"; + + Expected ResultA = + TextAPIReader::get(MemoryBufferRef(TBDv3File, "TestA.tbd")); + EXPECT_TRUE(!!ResultA); + InterfaceFile FileA = std::move(*ResultA.get()); + Expected ResultB = + TextAPIReader::get(MemoryBufferRef(TBDv3File, "TestB.tbd")); + EXPECT_TRUE(!!ResultB); + InterfaceFile FileB = std::move(*ResultB.get()); + + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + File->addTarget(Target(AK_x86_64, PlatformKind::iOS)); + })); + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + File->setCurrentVersion(PackedVersion(1, 2, 3)); + File->setCompatibilityVersion(PackedVersion(1, 0, 0)); + })); + EXPECT_TRUE(checkEqualityOnTransform( + FileA, FileB, [](InterfaceFile *File) { File->setSwiftABIVersion(5); })); + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + File->setTwoLevelNamespace(false); + })); + EXPECT_TRUE(checkEqualityOnTransform( + FileA, FileB, [](InterfaceFile *File) { File->setInstallAPI(true); })); + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + File->setApplicationExtensionSafe(false); + })); + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + File->addParentUmbrella(Target(AK_armv7, PlatformKind::iOS), "Umbrella.dylib"); + })); + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + File->addAllowableClient("ClientA", Target(AK_armv7, PlatformKind::iOS)); + })); + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + File->addReexportedLibrary("/System/Library/Frameworks/A.framework/A", + Target(AK_armv7, PlatformKind::iOS)); + })); + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + File->addSymbol(SymbolKind::GlobalSymbol, "_symA", {Target(AK_arm64, PlatformKind::iOS)}); + })); + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + InterfaceFile Document; + Document.addTargets(TargetList{Target(AK_armv7, PlatformKind::iOS), + Target(AK_arm64, PlatformKind::iOS)}); + Document.setInstallName("/System/Library/Frameworks/A.framework/A"); + File->addDocument(std::make_shared(std::move(Document))); + })); +} + } // namespace TBDv3 diff --git a/llvm/unittests/TextAPI/TextStubV4Tests.cpp b/llvm/unittests/TextAPI/TextStubV4Tests.cpp --- a/llvm/unittests/TextAPI/TextStubV4Tests.cpp +++ b/llvm/unittests/TextAPI/TextStubV4Tests.cpp @@ -938,4 +938,238 @@ ErrorMessage); } +TEST(TBDv4, InterfaceEquality) { + static const char TBDv4File[] = + "--- !tapi-tbd\n" + "tbd-version: 4\n" + "targets: [ i386-macos, x86_64-macos, x86_64-ios ]\n" + "uuids:\n" + " - target: i386-macos\n" + " value: 00000000-0000-0000-0000-000000000000\n" + " - target: x86_64-macos\n" + " value: 11111111-1111-1111-1111-111111111111\n" + " - target: x86_64-ios\n" + " value: 11111111-1111-1111-1111-111111111111\n" + "flags: [ flat_namespace, installapi ]\n" + "install-name: Umbrella.framework/Umbrella\n" + "current-version: 1.2.3\n" + "compatibility-version: 1.2\n" + "swift-abi-version: 5\n" + "parent-umbrella:\n" + " - targets: [ i386-macos, x86_64-macos, x86_64-ios ]\n" + " umbrella: System\n" + "allowable-clients:\n" + " - targets: [ i386-macos, x86_64-macos, x86_64-ios ]\n" + " clients: [ ClientA ]\n" + "reexported-libraries:\n" + " - targets: [ i386-macos ]\n" + " libraries: [ /System/Library/Frameworks/A.framework/A ]\n" + "exports:\n" + " - targets: [ i386-macos ]\n" + " symbols: [ _symA ]\n" + " objc-classes: []\n" + " objc-eh-types: []\n" + " objc-ivars: []\n" + " weak-symbols: []\n" + " thread-local-symbols: []\n" + " - targets: [ x86_64-ios ]\n" + " symbols: [_symB]\n" + " - targets: [ x86_64-macos, x86_64-ios ]\n" + " symbols: [_symAB]\n" + "reexports:\n" + " - targets: [ i386-macos ]\n" + " symbols: [_symC]\n" + " objc-classes: []\n" + " objc-eh-types: []\n" + " objc-ivars: []\n" + " weak-symbols: []\n" + " thread-local-symbols: []\n" + "undefineds:\n" + " - targets: [ i386-macos ]\n" + " symbols: [ _symD ]\n" + " objc-classes: []\n" + " objc-eh-types: []\n" + " objc-ivars: []\n" + " weak-symbols: []\n" + " thread-local-symbols: []\n" + "tbd-version: 4\n" + "targets: [ i386-maccatalyst, x86_64-maccatalyst ]\n" + "uuids:\n" + " - target: i386-maccatalyst\n" + " value: 00000000-0000-0000-0000-000000000000\n" + " - target: x86_64-maccatalyst\n" + " value: 11111111-1111-1111-1111-111111111111\n" + "install-name: '/System/Library/Frameworks/A.framework/A'\n" + "exports:\n" + " - targets: [ i386-maccatalyst ]\n" + " weak-symbols: [ _symC ]\n" + " - targets: [ i386-maccatalyst, x86_64-maccatalyst ]\n" + " symbols: [ _symA ]\n" + " objc-classes: [ Class1 ]\n" + " - targets: [ x86_64-maccatalyst ]\n" + " symbols: [ _symAB ]\n" + "...\n"; + + Expected ResultA = + TextAPIReader::get(MemoryBufferRef(TBDv4File, "TestA.tbd")); + EXPECT_TRUE(!!ResultA); + InterfaceFile FileA = std::move(*ResultA.get()); + Expected ResultB = + TextAPIReader::get(MemoryBufferRef(TBDv4File, "TestB.tbd")); + EXPECT_TRUE(!!ResultB); + InterfaceFile FileB = std::move(*ResultB.get()); + EXPECT_TRUE(FileA == FileB); +} + +TEST(TBDv4, InterfaceDiffVersionsEquality) { + static const char TBDv4File[] = + "--- !tapi-tbd\n" + "tbd-version: 4\n" + "targets: [ i386-macos, x86_64-macos ]\n" + "uuids:\n" + " - target: i386-macos\n" + " value: 00000000-0000-0000-0000-000000000000\n" + " - target: x86_64-macos\n" + " value: 11111111-1111-1111-1111-111111111111\n" + "flags: [ installapi ]\n" + "install-name: Umbrella.framework/Umbrella\n" + "current-version: 1.2.3\n" + "compatibility-version: 1.0\n" + "swift-abi-version: 5\n" + "parent-umbrella:\n" + " - targets: [ i386-macos, x86_64-macos ]\n" + " umbrella: System\n" + "allowable-clients:\n" + " - targets: [ i386-macos, x86_64-macos ]\n" + " clients: [ ClientA ]\n" + "reexported-libraries:\n" + " - targets: [ i386-macos ]\n" + " libraries: [ /System/Library/Frameworks/A.framework/A ]\n" + "exports:\n" + " - targets: [ i386-macos ]\n" + " symbols: [ _sym5 ]\n" + " objc-classes: [ class3]\n" + " objc-eh-types: []\n" + " objc-ivars: [ class1._ivar3 ]\n" + " weak-symbols: [ _weak3 ]\n" + " - targets: [ x86_64-macos ]\n" + " symbols: [_symAB]\n" + " - targets: [ i386-macos, x86_64-macos ]\n" + " symbols: [_symA]\n" + " objc-classes: [ class1, class2 ]\n" + " objc-eh-types: [ class1 ]\n" + " objc-ivars: [ class1._ivar1, class1._ivar2 ]\n" + " weak-symbols: [ _weak1, _weak2 ]\n" + " thread-local-symbols: [ _tlv1, _tlv3 ]\n" + "undefineds:\n" + " - targets: [ i386-macos ]\n" + " symbols: [ _symC ]\n" + " objc-classes: []\n" + " objc-eh-types: []\n" + " objc-ivars: []\n" + " weak-symbols: []\n" + " thread-local-symbols: []\n" + "...\n"; + + static const char TBDv3File[] = + "--- !tapi-tbd-v3\n" + "archs: [ i386, x86_64 ]\n" + "uuids: [ 'i386: 00000000-0000-0000-0000-000000000000',\n" + " 'x86_64: 22222222-2222-2222-2222-222222222222']\n" + "platform: macosx\n" + "flags: [ installapi ]\n" + "install-name: Umbrella.framework/Umbrella\n" + "current-version: 1.2.3\n" + "compatibility-version: 1.0\n" + "swift-abi-version: 5\n" + "parent-umbrella: System\n" + "exports:\n" + " - archs: [ i386, x86_64 ]\n" + " allowable-clients: [ ClientA ]\n" + " symbols: [ _symA ]\n" + " objc-classes: [ class1, class2 ]\n" + " objc-eh-types: [ class1 ]\n" + " objc-ivars: [ class1._ivar1, class1._ivar2 ]\n" + " weak-def-symbols: [ _weak1, _weak2 ]\n" + " thread-local-symbols: [ _tlv1, _tlv3 ]\n" + " - archs: [ i386 ]\n" + " re-exports: [ /System/Library/Frameworks/A.framework/A ]\n" + " symbols: [ _sym5 ]\n" + " objc-classes: [ class3 ]\n" + " objc-ivars: [ class1._ivar3 ]\n" + " weak-def-symbols: [ _weak3 ]\n" + " - archs: [ x86_64 ]\n" + " symbols: [ _symAB ]\n" + "undefineds:\n" + " - archs: [ i386 ]\n" + " symbols: [ _symC ]\n" + "...\n"; + + Expected ResultA = + TextAPIReader::get(MemoryBufferRef(TBDv4File, "TestA.tbd")); + EXPECT_TRUE(!!ResultA); + InterfaceFile FileA = std::move(*ResultA.get()); + Expected ResultB = + TextAPIReader::get(MemoryBufferRef(TBDv3File, "TestB.tbd")); + EXPECT_TRUE(!!ResultB); + InterfaceFile FileB = std::move(*ResultB.get()); + EXPECT_NE(FileA.uuids(), FileB.uuids()); + EXPECT_TRUE(FileA == FileB); +} + +TEST(TBDv4, InterfaceInequality) { + static const char TBDv4File[] = "--- !tapi-tbd\n" + "tbd-version: 4\n" + "targets: [ i386-macos, x86_64-macos ]\n" + "install-name: Umbrella.framework/Umbrella\n" + "...\n"; + + Expected ResultA = + TextAPIReader::get(MemoryBufferRef(TBDv4File, "TestA.tbd")); + EXPECT_TRUE(!!ResultA); + InterfaceFile FileA = std::move(*ResultA.get()); + Expected ResultB = + TextAPIReader::get(MemoryBufferRef(TBDv4File, "TestB.tbd")); + EXPECT_TRUE(!!ResultB); + InterfaceFile FileB = std::move(*ResultB.get()); + + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + File->addTarget(Target(AK_x86_64, PlatformKind::iOS)); + })); + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + File->setCurrentVersion(PackedVersion(1, 2, 3)); + File->setCompatibilityVersion(PackedVersion(1, 0, 0)); + })); + EXPECT_TRUE(checkEqualityOnTransform( + FileA, FileB, [](InterfaceFile *File) { File->setSwiftABIVersion(5); })); + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + File->setTwoLevelNamespace(false); + })); + EXPECT_TRUE(checkEqualityOnTransform( + FileA, FileB, [](InterfaceFile *File) { File->setInstallAPI(true); })); + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + File->setApplicationExtensionSafe(false); + })); + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + File->addParentUmbrella(Target(AK_x86_64, PlatformKind::macOS), "System.dylib"); + })); + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + File->addAllowableClient("ClientA", Target(AK_i386, PlatformKind::macOS)); + })); + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + File->addReexportedLibrary("/System/Library/Frameworks/A.framework/A", + Target(AK_i386, PlatformKind::macOS)); + })); + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + File->addSymbol(SymbolKind::GlobalSymbol, "_symA", {Target(AK_x86_64, PlatformKind::macOS)}); + })); + EXPECT_TRUE(checkEqualityOnTransform(FileA, FileB, [](InterfaceFile *File) { + InterfaceFile Document; + Document.addTargets(TargetList {Target(AK_i386, PlatformKind::macOS), + Target(AK_x86_64, PlatformKind::macOS)}); + Document.setInstallName("/System/Library/Frameworks/A.framework/A"); + File->addDocument(std::make_shared(std::move(Document))); + })); +} + } // end namespace TBDv4