From 79320a0c3f82c47ffd68df5eae7c26a1a4269077 Mon Sep 17 00:00:00 2001 From: Cyndy Ishida Date: Fri, 17 Feb 2023 19:55:47 -0800 Subject: [PATCH] Reland "[TextAPI] Implement TBDv5 Reader" Introduce initial reader for TBDv5 which is in JSON. This captures all the currently understood fields within the internal structure `InterfaceFile`. New fields will be followed up in future PRs. Reviewed By: pete Differential Revision: https://reviews.llvm.org/D144156 --- lld/test/MachO/Inputs/libStubLink.tbd | 1 + lld/test/MachO/invalid/invalid-stub.s | 5 +- lld/test/MachO/tapi-link.s | 5 +- llvm/include/llvm/TextAPI/InterfaceFile.h | 3 + llvm/lib/TextAPI/CMakeLists.txt | 1 + llvm/lib/TextAPI/TextStub.cpp | 49 +- llvm/lib/TextAPI/TextStubCommon.cpp | 2 +- llvm/lib/TextAPI/TextStubCommon.h | 20 +- llvm/lib/TextAPI/TextStubV5.cpp | 700 ++++++++++++++++++++++++++ llvm/test/Object/Inputs/tapi-v4-watchos.tbd | 1 + llvm/test/tools/llvm-tapi-diff/Inputs/v4A.tbd | 1 + llvm/test/tools/llvm-tapi-diff/Inputs/v4B.tbd | 1 + llvm/test/tools/llvm-tapi-diff/Inputs/v4C.tbd | 1 + llvm/test/tools/llvm-tapi-diff/Inputs/v4D.tbd | 1 + llvm/test/tools/llvm-tapi-diff/Inputs/v4E.tbd | 1 + llvm/unittests/TextAPI/CMakeLists.txt | 1 + llvm/unittests/TextAPI/TextStubHelpers.h | 13 +- llvm/unittests/TextAPI/TextStubV4Tests.cpp | 3 +- llvm/unittests/TextAPI/TextStubV5Tests.cpp | 489 ++++++++++++++++++ 19 files changed, 1271 insertions(+), 27 deletions(-) create mode 100644 llvm/lib/TextAPI/TextStubV5.cpp create mode 100644 llvm/unittests/TextAPI/TextStubV5Tests.cpp diff --git a/lld/test/MachO/Inputs/libStubLink.tbd b/lld/test/MachO/Inputs/libStubLink.tbd index 6e6237ed..eaf216d 100644 --- a/lld/test/MachO/Inputs/libStubLink.tbd +++ b/lld/test/MachO/Inputs/libStubLink.tbd @@ -22,3 +22,4 @@ current-version: 1.0.0 exports: - targets: [ arm64-ios-simulator ] symbols: [ _arm64_sim_only ] +... diff --git a/lld/test/MachO/invalid/invalid-stub.s b/lld/test/MachO/invalid/invalid-stub.s index 9975949..a80dfa6 100644 --- a/lld/test/MachO/invalid/invalid-stub.s +++ b/lld/test/MachO/invalid/invalid-stub.s @@ -7,9 +7,8 @@ # RUN: not %lld -L%t -linvalidYAML %t/test.o -o %t/test 2>&1 | FileCheck %s -DDIR=%t # RUN: not %lld -F%t -framework invalidYAML %t/test.o -o %t/test 2>&1 | FileCheck %s -DDIR=%t --check-prefix=CHECK-FRAMEWORK -# CHECK: could not load TAPI file at [[DIR]]{{[\\/]}}libinvalidYAML.tbd: malformed file -# CHECK-FRAMEWORK: could not load TAPI file at [[DIR]]{{[\\/]}}invalidYAML.framework{{[\\/]}}invalidYAML.tbd: malformed file - +# CHECK: could not load TAPI file at [[DIR]]{{[\\/]}}libinvalidYAML.tbd: unsupported file type +# CHECK-FRAMEWORK: could not load TAPI file at [[DIR]]{{[\\/]}}invalidYAML.framework{{[\\/]}}invalidYAML.tbd: unsupported file type .globl _main _main: ret diff --git a/lld/test/MachO/tapi-link.s b/lld/test/MachO/tapi-link.s index b2aa2f2..af205b6c 100644 --- a/lld/test/MachO/tapi-link.s +++ b/lld/test/MachO/tapi-link.s @@ -121,7 +121,6 @@ exports: re-exports: [ 'libNested.dylib' ] ... -## This tests that weak and thread-local symbols are imported as such. #--- libTlvWeak.tbd --- !tapi-tbd tbd-version: 4 @@ -131,8 +130,8 @@ uuids: value: 00000000-0000-0000-0000-000000000000 install-name: '/usr/lib/libTlvWeak.dylib' current-version: 0001.001.1 -exports: +exports: # Validate weak & thread-local symbols - targets: [ x86_64-macos ] weak-symbols: [ _weak ] thread-local-symbols: [ _tlv ] ---- +... diff --git a/llvm/include/llvm/TextAPI/InterfaceFile.h b/llvm/include/llvm/TextAPI/InterfaceFile.h index 5f07397..cb8ec4b 100644 --- a/llvm/include/llvm/TextAPI/InterfaceFile.h +++ b/llvm/include/llvm/TextAPI/InterfaceFile.h @@ -66,6 +66,9 @@ enum FileType : unsigned { /// Text-based stub file (.tbd) version 4.0 TBD_V4 = 1U << 3, + /// Text-based stub file (.tbd) version 5.0 + TBD_V5 = 1U << 4, + All = ~0U, LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/All), diff --git a/llvm/lib/TextAPI/CMakeLists.txt b/llvm/lib/TextAPI/CMakeLists.txt index c7e9e10..1714ce3 100644 --- a/llvm/lib/TextAPI/CMakeLists.txt +++ b/llvm/lib/TextAPI/CMakeLists.txt @@ -2,6 +2,7 @@ add_llvm_component_library(LLVMTextAPI Architecture.cpp ArchitectureSet.cpp InterfaceFile.cpp + TextStubV5.cpp PackedVersion.cpp Platform.cpp Symbol.cpp diff --git a/llvm/lib/TextAPI/TextStub.cpp b/llvm/lib/TextAPI/TextStub.cpp index ff93e43..73cb614 100644 --- a/llvm/lib/TextAPI/TextStub.cpp +++ b/llvm/lib/TextAPI/TextStub.cpp @@ -258,16 +258,6 @@ struct UUIDv4 { UUIDv4(const Target &TargetID, const std::string &Value) : TargetID(TargetID), Value(Value) {} }; - -// clang-format off -enum TBDFlags : unsigned { - None = 0U, - FlatNamespace = 1U << 0, - NotApplicationExtensionSafe = 1U << 1, - InstallAPI = 1U << 2, - LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/InstallAPI), -}; -// clang-format on } // end anonymous namespace. LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(Architecture) @@ -1105,10 +1095,49 @@ static void DiagHandler(const SMDiagnostic &Diag, void *Context) { File->ErrorMessage = ("malformed file\n" + Message).str(); } +namespace { + +Expected canReadFileType(MemoryBufferRef InputBuffer) { + auto TAPIFile = InputBuffer.getBuffer().trim(); + if (TAPIFile.startswith("{") && TAPIFile.endswith("}")) + return FileType::TBD_V5; + + if (!TAPIFile.endswith("...")) + return createStringError(std::errc::not_supported, "unsupported file type"); + + if (TAPIFile.startswith("--- !tapi-tbd\n")) + return FileType::TBD_V4; + + if (TAPIFile.startswith("--- !tapi-tbd-v3\n")) + return FileType::TBD_V3; + + if (TAPIFile.startswith("--- !tapi-tbd-v2\n")) + return FileType::TBD_V2; + + if (TAPIFile.startswith("--- !tapi-tbd-v1\n") || + TAPIFile.startswith("---\narchs:")) + return FileType::TBD_V1; + + return createStringError(std::errc::not_supported, "unsupported file type"); +} +} // namespace + Expected> TextAPIReader::get(MemoryBufferRef InputBuffer) { TextAPIContext Ctx; Ctx.Path = std::string(InputBuffer.getBufferIdentifier()); + if (auto FTOrErr = canReadFileType(InputBuffer)) + Ctx.FileKind = *FTOrErr; + else + return FTOrErr.takeError(); + + // Handle JSON Format. + if (Ctx.FileKind >= FileType::TBD_V5) { + auto FileOrErr = getInterfaceFileFromJSON(InputBuffer.getBuffer()); + if (!FileOrErr) + return FileOrErr.takeError(); + return std::move(*FileOrErr); + } yaml::Input YAMLIn(InputBuffer.getBuffer(), &Ctx, DiagHandler, &Ctx); // Fill vector with interface file objects created by parsing the YAML file. diff --git a/llvm/lib/TextAPI/TextStubCommon.cpp b/llvm/lib/TextAPI/TextStubCommon.cpp index 01a9007..8959af4 100644 --- a/llvm/lib/TextAPI/TextStubCommon.cpp +++ b/llvm/lib/TextAPI/TextStubCommon.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// // -// Implememts common Text Stub YAML mappings. +// Implements common Text Stub YAML mappings. // //===----------------------------------------------------------------------===// diff --git a/llvm/lib/TextAPI/TextStubCommon.h b/llvm/lib/TextAPI/TextStubCommon.h index aac2722..51b4231 100644 --- a/llvm/lib/TextAPI/TextStubCommon.h +++ b/llvm/lib/TextAPI/TextStubCommon.h @@ -22,6 +22,16 @@ using UUID = std::pair; +// clang-format off +enum TBDFlags : unsigned { + None = 0U, + FlatNamespace = 1U << 0, + NotApplicationExtensionSafe = 1U << 1, + InstallAPI = 1U << 2, + LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue=*/InstallAPI), +}; +// clang-format on + LLVM_YAML_STRONG_TYPEDEF(llvm::StringRef, FlowStringRef) LLVM_YAML_STRONG_TYPEDEF(uint8_t, SwiftVersion) LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(UUID) @@ -30,9 +40,13 @@ LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(FlowStringRef) namespace llvm { namespace MachO { - class ArchitectureSet; - class PackedVersion; -} +class ArchitectureSet; +class PackedVersion; + +Expected> +getInterfaceFileFromJSON(StringRef JSON); +} // namespace MachO + namespace yaml { template <> struct ScalarTraits { diff --git a/llvm/lib/TextAPI/TextStubV5.cpp b/llvm/lib/TextAPI/TextStubV5.cpp new file mode 100644 index 0000000..990cb79 --- /dev/null +++ b/llvm/lib/TextAPI/TextStubV5.cpp @@ -0,0 +1,700 @@ +//===- TextStubV5.cpp -----------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Implements Text Stub JSON mappings. +// +//===----------------------------------------------------------------------===// +#include "TextStubCommon.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/Support/JSON.h" + +// clang-format off +/* + +JSON Format specification. + +All library level keys, accept target values and are defaulted if not specified. + +{ +"tapi_tbd_version": 5, # Required: TBD version for all documents in file +"main_library": { # Required: top level library + "target_info": [ # Required: target information + { + "target": "x86_64-macos", + "min_deployment": "10.14" # Required: minimum OS deployment version + }, + { + "target": "arm64-macos", + "min_deployment": "10.14" + }, + { + "target": "arm64-maccatalyst", + "min_deployment": "12.1" + }], + "flags":[{"attributes": ["flat_namespace"]}], # Optional: + "install_names":[{"name":"/S/L/F/Foo.fwk/Foo"}], # Required: library install name + "current_versions":[{"version": "1.2"}], # Optional: defaults to 1 + "compatibility_versions":[{ "version": "1.1"}], # Optional: defaults to 1 + "rpaths": [ # Optional: + { + "targets": ["x86_64-macos"], # Optional: defaults to targets in `target-info` + "paths": ["@executable_path/.../Frameworks"] + }], + "parent_umbrellas": [{"umbrella": "System"}], + "allowable_clients": [{"clients": ["ClientA"]}], + "reexported_libraries": [{"names": ["/u/l/l/foo.dylib"]}], + "exported_symbols": [{ # List of export symbols section + "targets": ["x86_64-macos", "arm64-macos"], # Optional: defaults to targets in `target-info` + "text": { # List of Text segment symbols + "global": [ "_func" ], + "weak": [], + "thread_local": [] + }, + "data": { ... }, # List of Data segment symbols + }], + "reexported_symbols": [{ ... }], # List of reexported symbols section + "undefined_symbols": [{ ... }] # List of undefined symbols section +}, +"libraries": [ # Optional: Array of inlined libraries + {...}, {...}, {...} +] +} +*/ +// clang-format on + +using namespace llvm; +using namespace llvm::json; +using namespace llvm::MachO; + +struct JSONSymbol { + SymbolKind Kind; + std::string Name; + SymbolFlags Flags; +}; + +using AttrToTargets = std::map; +using TargetsToSymbols = + SmallVector>>; + +enum TBDKey : size_t { + TBDVersion = 0U, + MainLibrary, + Documents, + TargetInfo, + Targets, + Target, + Deployment, + Flags, + Attributes, + InstallName, + CurrentVersion, + CompatibilityVersion, + Version, + SwiftABI, + ABI, + ParentUmbrella, + Umbrella, + AllowableClients, + Clients, + ReexportLibs, + Names, + Name, + Exports, + Reexports, + Undefineds, + Data, + Text, + Weak, + ThreadLocal, + Globals, + ObjCClass, + ObjCEHType, + ObjCIvar, +}; + +std::array Keys = { + "tapi_tbd_version", + "main_library", + "libraries", + "target_info", + "targets", + "target", + "min_deployment", + "flags", + "attributes", + "install_names", + "current_versions", + "compatibility_versions", + "version", + "swift_abi", + "abi", + "parent_umbrellas", + "umbrella", + "allowable_clients", + "clients", + "reexported_libraries", + "names", + "name", + "exported_symbols", + "reexported_symbols", + "undefined_symbols", + "data", + "text", + "weak", + "thread_local", + "global", + "objc_class", + "objc_eh_type", + "objc_ivar", +}; + +static llvm::SmallString<128> getParseErrorMsg(TBDKey Key) { + return {"invalid ", Keys[Key], " section"}; +} + +class JSONStubError : public llvm::ErrorInfo { +public: + JSONStubError(Twine ErrMsg) : Message(ErrMsg.str()) {} + + void log(llvm::raw_ostream &OS) const override { OS << Message << "\n"; } + std::error_code convertToErrorCode() const override { + return llvm::inconvertibleErrorCode(); + } + +private: + std::string Message; +}; + +template +Expected getRequiredValue( + TBDKey Key, const Object *Obj, + std::function(const Object *, StringRef)> GetValue, + std::function(JsonT)> Validate = nullptr) { + std::optional Val = GetValue(Obj, Keys[Key]); + if (!Val) + return make_error(getParseErrorMsg(Key)); + + if (Validate == nullptr) + return static_cast(*Val); + + std::optional Result = Validate(*Val); + if (!Result.has_value()) + return make_error(getParseErrorMsg(Key)); + return Result.value(); +} + +template +Expected getRequiredValue( + TBDKey Key, const Object *Obj, + std::function(const Object *, StringRef)> GetValue, + StubT DefaultValue, std::function(JsonT)> Validate) { + std::optional Val = GetValue(Obj, Keys[Key]); + if (!Val) + return DefaultValue; + + std::optional Result; + Result = Validate(*Val); + if (!Result.has_value()) + return make_error(getParseErrorMsg(Key)); + return Result.value(); +} + +Error collectFromArray(TBDKey Key, const Object *Obj, + std::function Append, + bool IsRequired = false) { + const auto *Values = Obj->getArray(Keys[Key]); + if (!Values) { + if (IsRequired) + return make_error(getParseErrorMsg(Key)); + return Error::success(); + } + + for (Value Val : *Values) { + auto ValStr = Val.getAsString(); + if (!ValStr.has_value()) + return make_error(getParseErrorMsg(Key)); + Append(ValStr.value()); + } + + return Error::success(); +} + +namespace StubParser { + +Expected getVersion(const Object *File) { + auto VersionOrErr = getRequiredValue( + TBDKey::TBDVersion, File, &Object::getInteger, + [](int64_t Val) -> std::optional { + unsigned Result = Val; + if (Result != 5) + return std::nullopt; + return FileType::TBD_V5; + }); + + if (!VersionOrErr) + return VersionOrErr.takeError(); + return *VersionOrErr; +} + +Expected getTargets(const Object *Section) { + const auto *Targets = Section->getArray(Keys[TBDKey::Targets]); + if (!Targets) + return make_error(getParseErrorMsg(TBDKey::Targets)); + + TargetList IFTargets; + for (Value JSONTarget : *Targets) { + auto TargetStr = JSONTarget.getAsString(); + if (!TargetStr.has_value()) + return make_error(getParseErrorMsg(TBDKey::Target)); + auto TargetOrErr = Target::create(TargetStr.value()); + if (!TargetOrErr) + return make_error(getParseErrorMsg(TBDKey::Target)); + IFTargets.push_back(*TargetOrErr); + } + return std::move(IFTargets); +} + +Expected getTargetsSection(const Object *Section) { + const Array *Targets = Section->getArray(Keys[TBDKey::TargetInfo]); + if (!Targets) + return make_error(getParseErrorMsg(TBDKey::Targets)); + + TargetList IFTargets; + for (const Value &JSONTarget : *Targets) { + const auto *Obj = JSONTarget.getAsObject(); + if (!Obj) + return make_error(getParseErrorMsg(TBDKey::Target)); + auto TargetStr = + getRequiredValue(TBDKey::Target, Obj, &Object::getString); + if (!TargetStr) + return make_error(getParseErrorMsg(TBDKey::Target)); + auto TargetOrErr = Target::create(*TargetStr); + if (!TargetOrErr) + return make_error(getParseErrorMsg(TBDKey::Target)); + IFTargets.push_back(*TargetOrErr); + // TODO: Implement Deployment Version. + } + return std::move(IFTargets); +} + +Error collectSymbolsFromSegment(const Object *Segment, TargetsToSymbols &Result, + SymbolFlags SectionFlag) { + auto Err = collectFromArray( + TBDKey::Globals, Segment, [&Result, &SectionFlag](StringRef Name) { + JSONSymbol Sym = {SymbolKind::GlobalSymbol, Name.str(), SectionFlag}; + Result.back().second.emplace_back(Sym); + }); + if (Err) + return Err; + + Err = collectFromArray( + TBDKey::ObjCClass, Segment, [&Result, &SectionFlag](StringRef Name) { + JSONSymbol Sym = {SymbolKind::ObjectiveCClass, Name.str(), SectionFlag}; + Result.back().second.emplace_back(Sym); + }); + if (Err) + return Err; + + Err = collectFromArray(TBDKey::ObjCEHType, Segment, + [&Result, &SectionFlag](StringRef Name) { + JSONSymbol Sym = {SymbolKind::ObjectiveCClassEHType, + Name.str(), SectionFlag}; + Result.back().second.emplace_back(Sym); + }); + if (Err) + return Err; + + Err = collectFromArray( + TBDKey::ObjCIvar, Segment, [&Result, &SectionFlag](StringRef Name) { + JSONSymbol Sym = {SymbolKind::ObjectiveCInstanceVariable, Name.str(), + SectionFlag}; + Result.back().second.emplace_back(Sym); + }); + if (Err) + return Err; + + SymbolFlags WeakFlag = SectionFlag | (SectionFlag == SymbolFlags::Undefined + ? SymbolFlags::WeakReferenced + : SymbolFlags::WeakDefined); + Err = collectFromArray(TBDKey::Weak, Segment, + [&Result, WeakFlag](StringRef Name) { + JSONSymbol Sym = { + SymbolKind::GlobalSymbol, + Name.str(), + WeakFlag, + }; + Result.back().second.emplace_back(Sym); + }); + if (Err) + return Err; + + Err = collectFromArray( + TBDKey::ThreadLocal, Segment, [&Result, SectionFlag](StringRef Name) { + JSONSymbol Sym = {SymbolKind::GlobalSymbol, Name.str(), + SymbolFlags::ThreadLocalValue | SectionFlag}; + Result.back().second.emplace_back(Sym); + }); + if (Err) + return Err; + + return Error::success(); +} + +Expected getNameSection(const Object *File) { + const Array *Section = File->getArray(Keys[TBDKey::InstallName]); + if (!Section) + return make_error(getParseErrorMsg(TBDKey::InstallName)); + + assert(!Section->empty() && "unexpected missing install name"); + // TODO: Just take first for now. + const auto *Obj = Section->front().getAsObject(); + if (!Obj) + return make_error(getParseErrorMsg(TBDKey::InstallName)); + + return getRequiredValue(TBDKey::Name, Obj, &Object::getString); +} + +Expected getSymbolSection(const Object *File, TBDKey Key, + TargetList &Targets) { + + const Array *Section = File->getArray(Keys[Key]); + if (!Section) + return TargetsToSymbols(); + + SymbolFlags SectionFlag; + switch (Key) { + case TBDKey::Reexports: + SectionFlag = SymbolFlags::Rexported; + break; + case TBDKey::Undefineds: + SectionFlag = SymbolFlags::Undefined; + break; + default: + SectionFlag = SymbolFlags::None; + break; + }; + + TargetsToSymbols Result; + TargetList MappedTargets; + for (auto Val : *Section) { + auto *Obj = Val.getAsObject(); + if (!Obj) + continue; + + auto TargetsOrErr = getTargets(Obj); + if (!TargetsOrErr) { + MappedTargets = Targets; + consumeError(TargetsOrErr.takeError()); + } else { + MappedTargets = *TargetsOrErr; + } + Result.emplace_back(std::make_pair(Targets, std::vector())); + + auto *DataSection = Obj->getObject(Keys[TBDKey::Data]); + auto *TextSection = Obj->getObject(Keys[TBDKey::Text]); + // There should be at least one valid section. + if (!DataSection && !TextSection) + return make_error(getParseErrorMsg(Key)); + + if (DataSection) { + auto Err = collectSymbolsFromSegment(DataSection, Result, SectionFlag); + if (Err) + return std::move(Err); + } + if (TextSection) { + auto Err = collectSymbolsFromSegment(TextSection, Result, SectionFlag); + if (Err) + return std::move(Err); + } + } + + return std::move(Result); +} + +Expected getLibSection(const Object *File, TBDKey Key, + TBDKey SubKey, + const TargetList &Targets) { + auto *Section = File->getArray(Keys[Key]); + if (!Section) + return AttrToTargets(); + + AttrToTargets Result; + TargetList MappedTargets; + for (auto Val : *Section) { + auto *Obj = Val.getAsObject(); + if (!Obj) + continue; + + auto TargetsOrErr = getTargets(Obj); + if (!TargetsOrErr) { + MappedTargets = Targets; + consumeError(TargetsOrErr.takeError()); + } else { + MappedTargets = *TargetsOrErr; + } + auto Err = + collectFromArray(SubKey, Obj, [&Result, &MappedTargets](StringRef Key) { + Result[Key.str()] = MappedTargets; + }); + if (Err) + return std::move(Err); + } + + return std::move(Result); +} + +Expected getUmbrellaSection(const Object *File, + const TargetList &Targets) { + const auto *Umbrella = File->getArray(Keys[TBDKey::ParentUmbrella]); + if (!Umbrella) + return AttrToTargets(); + + AttrToTargets Result; + TargetList MappedTargets; + for (auto Val : *Umbrella) { + auto *Obj = Val.getAsObject(); + if (!Obj) + return make_error( + getParseErrorMsg(TBDKey::ParentUmbrella)); + + // Get Targets section. + auto TargetsOrErr = getTargets(Obj); + if (!TargetsOrErr) { + MappedTargets = Targets; + consumeError(TargetsOrErr.takeError()); + } else { + MappedTargets = *TargetsOrErr; + } + + auto UmbrellaOrErr = + getRequiredValue(TBDKey::Umbrella, Obj, &Object::getString); + if (!UmbrellaOrErr) + return UmbrellaOrErr.takeError(); + Result[UmbrellaOrErr->str()] = Targets; + } + return std::move(Result); +} + +Expected getSwiftVersion(const Object *File) { + const Array *Versions = File->getArray(Keys[TBDKey::SwiftABI]); + if (!Versions) + return 0; + + for (const auto &Val : *Versions) { + const auto *Obj = Val.getAsObject(); + if (!Obj) + return make_error(getParseErrorMsg(TBDKey::SwiftABI)); + + // TODO: Take first for now. + return getRequiredValue(TBDKey::ABI, Obj, + &Object::getInteger); + } + + return 0; +} + +Expected getPackedVersion(const Object *File, TBDKey Key) { + const Array *Versions = File->getArray(Keys[Key]); + if (!Versions) + return PackedVersion(1, 0, 0); + + for (const auto &Val : *Versions) { + const auto *Obj = Val.getAsObject(); + if (!Obj) + return make_error(getParseErrorMsg(Key)); + + auto ValidatePV = [](StringRef Version) -> std::optional { + PackedVersion PV; + auto [success, truncated] = PV.parse64(Version); + if (!success || truncated) + return std::nullopt; + return PV; + }; + // TODO: Take first for now. + return getRequiredValue( + TBDKey::Version, Obj, &Object::getString, PackedVersion(1, 0, 0), + ValidatePV); + } + + return PackedVersion(1, 0, 0); +} + +Expected getFlags(const Object *File) { + TBDFlags Flags = TBDFlags::None; + const Array *Section = File->getArray(Keys[TBDKey::Flags]); + if (!Section) + return Flags; + + for (auto &Val : *Section) { + // TODO: Just take first for now. + const auto *Obj = Val.getAsObject(); + if (!Obj) + return make_error(getParseErrorMsg(TBDKey::Flags)); + + auto FlagsOrErr = + collectFromArray(TBDKey::Attributes, Obj, [&Flags](StringRef Flag) { + TBDFlags TBDFlag = + StringSwitch(Flag) + .Case("flat_namespace", TBDFlags::FlatNamespace) + .Case("not_app_extension_safe", + TBDFlags::NotApplicationExtensionSafe) + .Default(TBDFlags::None); + Flags |= TBDFlag; + }); + + if (FlagsOrErr) + return std::move(FlagsOrErr); + + return Flags; + } + + return Flags; +} + +using IFPtr = std::unique_ptr; +Expected parseToInterfaceFile(const Object *File) { + auto TargetsOrErr = getTargetsSection(File); + if (!TargetsOrErr) + return TargetsOrErr.takeError(); + TargetList Targets = *TargetsOrErr; + + auto NameOrErr = getNameSection(File); + if (!NameOrErr) + return NameOrErr.takeError(); + StringRef Name = *NameOrErr; + + auto CurrVersionOrErr = getPackedVersion(File, TBDKey::CurrentVersion); + if (!CurrVersionOrErr) + return CurrVersionOrErr.takeError(); + PackedVersion CurrVersion = *CurrVersionOrErr; + + auto CompVersionOrErr = getPackedVersion(File, TBDKey::CompatibilityVersion); + if (!CompVersionOrErr) + return CompVersionOrErr.takeError(); + PackedVersion CompVersion = *CompVersionOrErr; + + auto SwiftABIOrErr = getSwiftVersion(File); + if (!SwiftABIOrErr) + return SwiftABIOrErr.takeError(); + uint8_t SwiftABI = *SwiftABIOrErr; + + auto FlagsOrErr = getFlags(File); + if (!FlagsOrErr) + return FlagsOrErr.takeError(); + TBDFlags Flags = *FlagsOrErr; + + auto UmbrellasOrErr = getUmbrellaSection(File, Targets); + if (!UmbrellasOrErr) + return UmbrellasOrErr.takeError(); + AttrToTargets Umbrellas = *UmbrellasOrErr; + + auto ClientsOrErr = + getLibSection(File, TBDKey::AllowableClients, TBDKey::Clients, Targets); + if (!ClientsOrErr) + return ClientsOrErr.takeError(); + AttrToTargets Clients = *ClientsOrErr; + + auto RLOrErr = + getLibSection(File, TBDKey::ReexportLibs, TBDKey::Names, Targets); + if (!RLOrErr) + return RLOrErr.takeError(); + AttrToTargets ReexportLibs = std::move(*RLOrErr); + + auto ExportsOrErr = getSymbolSection(File, TBDKey::Exports, Targets); + if (!ExportsOrErr) + return ExportsOrErr.takeError(); + TargetsToSymbols Exports = std::move(*ExportsOrErr); + + auto ReexportsOrErr = getSymbolSection(File, TBDKey::Reexports, Targets); + if (!ReexportsOrErr) + return ReexportsOrErr.takeError(); + TargetsToSymbols Reexports = std::move(*ReexportsOrErr); + + auto UndefinedsOrErr = getSymbolSection(File, TBDKey::Undefineds, Targets); + if (!UndefinedsOrErr) + return UndefinedsOrErr.takeError(); + TargetsToSymbols Undefineds = std::move(*UndefinedsOrErr); + + IFPtr F(new InterfaceFile); + F->setInstallName(Name); + F->setCurrentVersion(CurrVersion); + F->setCompatibilityVersion(CompVersion); + F->setSwiftABIVersion(SwiftABI); + F->setTwoLevelNamespace(!(Flags & TBDFlags::FlatNamespace)); + F->setApplicationExtensionSafe( + !(Flags & TBDFlags::NotApplicationExtensionSafe)); + for (auto &T : Targets) + F->addTarget(T); + for (auto &[Lib, Targets] : Clients) + for (auto Target : Targets) + F->addAllowableClient(Lib, Target); + for (auto &[Lib, Targets] : ReexportLibs) + for (auto Target : Targets) + F->addReexportedLibrary(Lib, Target); + for (auto &[Lib, Targets] : Umbrellas) + for (auto Target : Targets) + F->addParentUmbrella(Target, Lib); + for (auto &[Targets, Symbols] : Exports) + for (auto &Sym : Symbols) + F->addSymbol(Sym.Kind, Sym.Name, Targets, Sym.Flags); + for (auto &[Targets, Symbols] : Reexports) + for (auto &Sym : Symbols) + F->addSymbol(Sym.Kind, Sym.Name, Targets, Sym.Flags); + for (auto &[Targets, Symbols] : Undefineds) + for (auto &Sym : Symbols) + F->addSymbol(Sym.Kind, Sym.Name, Targets, Sym.Flags); + + return std::move(F); +} + +Expected> getInlinedLibs(const Object *File) { + std::vector IFs; + const Array *Files = File->getArray(Keys[TBDKey::Documents]); + if (!Files) + return std::move(IFs); + + for (auto Lib : *Files) { + auto IFOrErr = parseToInterfaceFile(Lib.getAsObject()); + if (!IFOrErr) + return IFOrErr.takeError(); + auto IF = std::move(*IFOrErr); + IFs.emplace_back(std::move(IF)); + } + return std::move(IFs); +} + +} // namespace StubParser + +Expected> +MachO::getInterfaceFileFromJSON(StringRef JSON) { + auto ValOrErr = parse(JSON); + if (!ValOrErr) + return ValOrErr.takeError(); + + auto *Root = ValOrErr->getAsObject(); + auto VersionOrErr = StubParser::getVersion(Root); + if (!VersionOrErr) + return VersionOrErr.takeError(); + FileType Version = *VersionOrErr; + + Object *MainLib = Root->getObject(Keys[TBDKey::MainLibrary]); + auto IFOrErr = StubParser::parseToInterfaceFile(MainLib); + if (!IFOrErr) + return IFOrErr.takeError(); + (*IFOrErr)->setFileType(Version); + std::unique_ptr IF(std::move(*IFOrErr)); + + auto IFsOrErr = StubParser::getInlinedLibs(Root); + if (!IFsOrErr) + return IFsOrErr.takeError(); + for (auto &File : *IFsOrErr) { + File->setFileType(Version); + IF->addDocument(std::shared_ptr(std::move(File))); + } + return std::move(IF); +} diff --git a/llvm/test/Object/Inputs/tapi-v4-watchos.tbd b/llvm/test/Object/Inputs/tapi-v4-watchos.tbd index df06922..1a22de4 100644 --- a/llvm/test/Object/Inputs/tapi-v4-watchos.tbd +++ b/llvm/test/Object/Inputs/tapi-v4-watchos.tbd @@ -11,3 +11,4 @@ current-version: 1 exports: - targets: [ armv7k-watchos-simulator, arm64_32-watchos-simulator ] symbols: [ '_sym1' ] +... diff --git a/llvm/test/tools/llvm-tapi-diff/Inputs/v4A.tbd b/llvm/test/tools/llvm-tapi-diff/Inputs/v4A.tbd index 7dba4d8..e4e6dcc 100644 --- a/llvm/test/tools/llvm-tapi-diff/Inputs/v4A.tbd +++ b/llvm/test/tools/llvm-tapi-diff/Inputs/v4A.tbd @@ -47,3 +47,4 @@ undefineds: objc-ivars: [] weak-symbols: [] thread-local-symbols: [] +... diff --git a/llvm/test/tools/llvm-tapi-diff/Inputs/v4B.tbd b/llvm/test/tools/llvm-tapi-diff/Inputs/v4B.tbd index 6a83184..9ad435c 100644 --- a/llvm/test/tools/llvm-tapi-diff/Inputs/v4B.tbd +++ b/llvm/test/tools/llvm-tapi-diff/Inputs/v4B.tbd @@ -53,3 +53,4 @@ exports: weak-symbols: [ _symC ] - targets: [ x86_64-ios-simulator ] symbols: [ _symB ] +... diff --git a/llvm/test/tools/llvm-tapi-diff/Inputs/v4C.tbd b/llvm/test/tools/llvm-tapi-diff/Inputs/v4C.tbd index b2641e2..0ce4fb3 100644 --- a/llvm/test/tools/llvm-tapi-diff/Inputs/v4C.tbd +++ b/llvm/test/tools/llvm-tapi-diff/Inputs/v4C.tbd @@ -47,3 +47,4 @@ undefineds: objc-ivars: [] weak-symbols: [] thread-local-symbols: [] +... diff --git a/llvm/test/tools/llvm-tapi-diff/Inputs/v4D.tbd b/llvm/test/tools/llvm-tapi-diff/Inputs/v4D.tbd index 1338f62..13b1416 100644 --- a/llvm/test/tools/llvm-tapi-diff/Inputs/v4D.tbd +++ b/llvm/test/tools/llvm-tapi-diff/Inputs/v4D.tbd @@ -102,3 +102,4 @@ undefineds: objc-ivars: [] weak-symbols: [] thread-local-symbols: [] +... diff --git a/llvm/test/tools/llvm-tapi-diff/Inputs/v4E.tbd b/llvm/test/tools/llvm-tapi-diff/Inputs/v4E.tbd index 1d34b2d..b1b2d16 100644 --- a/llvm/test/tools/llvm-tapi-diff/Inputs/v4E.tbd +++ b/llvm/test/tools/llvm-tapi-diff/Inputs/v4E.tbd @@ -53,3 +53,4 @@ exports: weak-symbols: [ _symC ] - targets: [ x86_64-ios-simulator ] symbols: [ _symB ] +... diff --git a/llvm/unittests/TextAPI/CMakeLists.txt b/llvm/unittests/TextAPI/CMakeLists.txt index d575d57..2cb5be6 100644 --- a/llvm/unittests/TextAPI/CMakeLists.txt +++ b/llvm/unittests/TextAPI/CMakeLists.txt @@ -7,6 +7,7 @@ add_llvm_unittest(TextAPITests TextStubV2Tests.cpp TextStubV3Tests.cpp TextStubV4Tests.cpp + TextStubV5Tests.cpp ) target_link_libraries(TextAPITests PRIVATE LLVMTestingSupport) diff --git a/llvm/unittests/TextAPI/TextStubHelpers.h b/llvm/unittests/TextAPI/TextStubHelpers.h index 67bfa1a..c59252c 100644 --- a/llvm/unittests/TextAPI/TextStubHelpers.h +++ b/llvm/unittests/TextAPI/TextStubHelpers.h @@ -16,10 +16,11 @@ namespace llvm { struct ExportedSymbol { - llvm::MachO::SymbolKind Kind; - std::string Name; - bool WeakDefined; - bool ThreadLocalValue; + MachO::SymbolKind Kind = MachO::SymbolKind::GlobalSymbol; + std::string Name = {}; + bool Weak = false; + bool ThreadLocalValue = false; + MachO::TargetList Targets = {}; }; using ExportedSymbolSeq = std::vector; @@ -32,8 +33,8 @@ inline bool operator<(const ExportedSymbol &LHS, const ExportedSymbol &RHS) { } inline bool operator==(const ExportedSymbol &LHS, const ExportedSymbol &RHS) { - return std::tie(LHS.Kind, LHS.Name, LHS.WeakDefined, LHS.ThreadLocalValue) == - std::tie(RHS.Kind, RHS.Name, RHS.WeakDefined, RHS.ThreadLocalValue); + return std::tie(LHS.Kind, LHS.Name, LHS.Weak, LHS.ThreadLocalValue) == + std::tie(RHS.Kind, RHS.Name, RHS.Weak, RHS.ThreadLocalValue); } inline std::string stripWhitespace(std::string S) { diff --git a/llvm/unittests/TextAPI/TextStubV4Tests.cpp b/llvm/unittests/TextAPI/TextStubV4Tests.cpp index 570e853..ac51950 100644 --- a/llvm/unittests/TextAPI/TextStubV4Tests.cpp +++ b/llvm/unittests/TextAPI/TextStubV4Tests.cpp @@ -903,7 +903,8 @@ TEST(TBDv4, MalformedFile2) { "tbd-version: 4\n" "targets: [ x86_64-macos ]\n" "install-name: Test.dylib\n" - "foobar: \"unsupported key\"\n"; + "foobar: \"unsupported key\"\n" + "...\n"; Expected Result = TextAPIReader::get(MemoryBufferRef(TBDv4MalformedFile2, "Test.tbd")); diff --git a/llvm/unittests/TextAPI/TextStubV5Tests.cpp b/llvm/unittests/TextAPI/TextStubV5Tests.cpp new file mode 100644 index 0000000..1edaca3 --- /dev/null +++ b/llvm/unittests/TextAPI/TextStubV5Tests.cpp @@ -0,0 +1,489 @@ +//===-- TextStubV5Tests.cpp - TBD V5 File Test ----------------------------===// +// +// 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 +// +//===-----------------------------------------------------------------------===/ + +#include "TextStubHelpers.h" +#include "llvm/TextAPI/InterfaceFile.h" +#include "llvm/TextAPI/TextAPIReader.h" +#include "llvm/TextAPI/TextAPIWriter.h" +#include "gtest/gtest.h" +#include +#include + +using namespace llvm; +using namespace llvm::MachO; + +namespace TBDv5 { + +TEST(TBDv5, ReadFile) { + static const char TBDv5File[] = R"({ +"tapi_tbd_version": 5, +"main_library": { + "target_info": [ + { + "target": "x86_64-macos", + "min_deployment": "10.14" + }, + { + "target": "arm64-macos", + "min_deployment": "10.14" + }, + { + "target": "arm64-maccatalyst", + "min_deployment": "12.1" + } + ], + "flags": [ + { + "targets": [ + "x86_64-macos" + ], + "attributes": [ + "flat_namespace" + ] + } + ], + "install_names": [ + { + "name": "/S/L/F/Foo.framework/Foo" + } + ], + "current_versions": [ + { + "version": "1.2" + } + ], + "compatibility_versions": [ + { "version": "1.1" } + ], + "rpaths": [ + { + "targets": [ + "x86_64-macos" + ], + "paths": [ + "@executable_path/.../Frameworks" + ] + } + ], + "parent_umbrellas": [ + { + "umbrella": "System" + } + ], + "allowable_clients": [ + { + "clients": [ + "ClientA", + "ClientB" + ] + } + ], + "reexported_libraries": [ + { + "names": [ + "/u/l/l/libfoo.dylib", + "/u/l/l/libbar.dylib" + ] + } + ], + "exported_symbols": [ + { + "targets": [ + "x86_64-macos", + "arm64-macos" + ], + "data": { + "global": [ + "_global" + ], + "objc_class": [ + "ClassA" + ], + "weak": [], + "thread_local": [] + }, + "text": { + "global": [ + "_func" + ], + "weak": [], + "thread_local": [] + } + }, + { + "targets": [ + "x86_64-macos" + ], + "data": { + "global": [ + "_globalVar" + ], + "objc_class": [ + "ClassData" + ], + "objc_eh_type": [ + "ClassA", + "ClassB" + ], + "objc_ivar": [ + "ClassA.ivar1", + "ClassA.ivar2", + "ClassC.ivar1" + ] + }, + "text": { + "global": [ + "_funcFoo" + ] + } + } + ], + "reexported_symbols": [ + { + "targets": [ + "x86_64-macos", + "arm64-macos" + ], + "data": { + "global": [ + "_globalRe" + ], + "objc_class": [ + "ClassRexport" + ] + }, + "text": { + "global": [ + "_funcA" + ] + } + } + ], + "undefined_symbols": [ + { + "targets": [ + "x86_64-macos" + ], + "data": { + "global": [ + "_globalBind" + ], + "weak": [ + "referenced_sym" + ] + } + } + ] +}, +"libraries": [] +})"; + + Expected Result = + TextAPIReader::get(MemoryBufferRef(TBDv5File, "Test.tbd")); + EXPECT_TRUE(!!Result); + TBDFile File = std::move(Result.get()); + EXPECT_EQ(FileType::TBD_V5, File->getFileType()); + EXPECT_EQ(std::string("/S/L/F/Foo.framework/Foo"), File->getInstallName()); + + TargetList AllTargets = { + Target(AK_x86_64, PLATFORM_MACOS), + Target(AK_arm64, PLATFORM_MACOS), + Target(AK_arm64, PLATFORM_MACCATALYST), + }; + EXPECT_EQ(mapToPlatformSet(AllTargets), File->getPlatforms()); + EXPECT_EQ(mapToArchitectureSet(AllTargets), File->getArchitectures()); + + EXPECT_EQ(PackedVersion(1, 2, 0), File->getCurrentVersion()); + EXPECT_EQ(PackedVersion(1, 1, 0), File->getCompatibilityVersion()); + EXPECT_TRUE(File->isApplicationExtensionSafe()); + EXPECT_FALSE(File->isTwoLevelNamespace()); + EXPECT_EQ(0U, File->documents().size()); + + InterfaceFileRef ClientA("ClientA", AllTargets); + InterfaceFileRef ClientB("ClientB", AllTargets); + EXPECT_EQ(2U, File->allowableClients().size()); + EXPECT_EQ(ClientA, File->allowableClients().at(0)); + EXPECT_EQ(ClientB, File->allowableClients().at(1)); + + InterfaceFileRef ReexportA("/u/l/l/libbar.dylib", AllTargets); + InterfaceFileRef ReexportB("/u/l/l/libfoo.dylib", AllTargets); + EXPECT_EQ(2U, File->reexportedLibraries().size()); + EXPECT_EQ(ReexportA, File->reexportedLibraries().at(0)); + EXPECT_EQ(ReexportB, File->reexportedLibraries().at(1)); + + std::vector> Umbrellas = { + {Target(AK_x86_64, PLATFORM_MACOS), "System"}, + {Target(AK_arm64, PLATFORM_MACOS), "System"}, + {Target(AK_arm64, PLATFORM_MACCATALYST), "System"}}; + EXPECT_EQ(Umbrellas, File->umbrellas()); + + ExportedSymbolSeq Exports, Reexports, Undefineds; + for (const auto *Sym : File->symbols()) { + TargetList SymTargets{Sym->targets().begin(), Sym->targets().end()}; + ExportedSymbol Temp = + ExportedSymbol{Sym->getKind(), std::string(Sym->getName()), + Sym->isWeakDefined() || Sym->isWeakReferenced(), + Sym->isThreadLocalValue(), SymTargets}; + if (Sym->isUndefined()) + Undefineds.emplace_back(std::move(Temp)); + else + Sym->isReexported() ? Reexports.emplace_back(std::move(Temp)) + : Exports.emplace_back(std::move(Temp)); + } + llvm::sort(Exports); + llvm::sort(Reexports); + llvm::sort(Undefineds); + + TargetList MacOSTargets = {Target(AK_x86_64, PLATFORM_MACOS), + Target(AK_arm64, PLATFORM_MACOS)}; + + std::vector ExpectedExportedSymbols = { + {SymbolKind::GlobalSymbol, "_func", false, false, MacOSTargets}, + {SymbolKind::GlobalSymbol, + "_funcFoo", + false, + false, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::GlobalSymbol, "_global", false, false, MacOSTargets}, + {SymbolKind::GlobalSymbol, + "_globalVar", + false, + false, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCClass, "ClassA", false, false, MacOSTargets}, + {SymbolKind::ObjectiveCClass, + "ClassData", + false, + false, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCClassEHType, + "ClassA", + false, + false, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCClassEHType, + "ClassB", + false, + false, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCInstanceVariable, + "ClassA.ivar1", + false, + false, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCInstanceVariable, + "ClassA.ivar2", + false, + false, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::ObjectiveCInstanceVariable, + "ClassC.ivar1", + false, + false, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + }; + std::vector ExpectedReexportedSymbols = { + {SymbolKind::GlobalSymbol, "_funcA", false, false, MacOSTargets}, + {SymbolKind::GlobalSymbol, "_globalRe", false, false, MacOSTargets}, + {SymbolKind::ObjectiveCClass, "ClassRexport", false, false, MacOSTargets}, + }; + + std::vector ExpectedUndefinedSymbols = { + {SymbolKind::GlobalSymbol, + "_globalBind", + false, + false, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + {SymbolKind::GlobalSymbol, + "referenced_sym", + true, + false, + {Target(AK_x86_64, PLATFORM_MACOS)}}, + }; + + EXPECT_EQ(ExpectedExportedSymbols.size(), Exports.size()); + EXPECT_EQ(ExpectedReexportedSymbols.size(), Reexports.size()); + EXPECT_EQ(ExpectedUndefinedSymbols.size(), Undefineds.size()); + EXPECT_TRUE(std::equal(Exports.begin(), Exports.end(), + std::begin(ExpectedExportedSymbols))); + EXPECT_TRUE(std::equal(Reexports.begin(), Reexports.end(), + std::begin(ExpectedReexportedSymbols))); + EXPECT_TRUE(std::equal(Undefineds.begin(), Undefineds.end(), + std::begin(ExpectedUndefinedSymbols))); +} + +TEST(TBDv5, ReadMultipleTargets) { + static const char TBDv5File[] = R"({ +"tapi_tbd_version": 5, +"main_library": { + "target_info": [ + { + "target": "x86_64-macos", + "min_deployment": "10.14" + }, + { + "target": "arm64-macos", + "min_deployment": "10.14" + }, + { + "target": "arm64-maccatalyst", + "min_deployment": "12.1" + } + ], + "install_names":[ + { "name":"/usr/lib/libFoo.dylib" } + ], + "swift_abi":[ { "abi":8 } ], + "reexported_libraries": [ + { + "targets": [ "x86_64-maccatalyst" ], + "names": [ + "/u/l/l/libfoo.dylib", + "/u/l/l/libbar.dylib" + ] + }, + { + "targets": [ "arm64-maccatalyst" ], + "names": [ "/u/l/l/libArmOnly.dylib" ] + } + ] +} +})"; + + Expected Result = + TextAPIReader::get(MemoryBufferRef(TBDv5File, "Test.tbd")); + EXPECT_TRUE(!!Result); + TBDFile File = std::move(Result.get()); + EXPECT_EQ(FileType::TBD_V5, File->getFileType()); + EXPECT_EQ(std::string("/usr/lib/libFoo.dylib"), File->getInstallName()); + EXPECT_TRUE(File->isApplicationExtensionSafe()); + EXPECT_TRUE(File->isTwoLevelNamespace()); + EXPECT_EQ(PackedVersion(1, 0, 0), File->getCurrentVersion()); + EXPECT_EQ(PackedVersion(1, 0, 0), File->getCompatibilityVersion()); + EXPECT_EQ(8U, File->getSwiftABIVersion()); + + TargetList AllTargets = { + Target(AK_x86_64, PLATFORM_MACOS), + Target(AK_arm64, PLATFORM_MACOS), + Target(AK_arm64, PLATFORM_MACCATALYST), + }; + EXPECT_EQ(mapToPlatformSet(AllTargets), File->getPlatforms()); + EXPECT_EQ(mapToArchitectureSet(AllTargets), File->getArchitectures()); + + InterfaceFileRef ReexportA("/u/l/l/libArmOnly.dylib", + {Target(AK_arm64, PLATFORM_MACCATALYST)}); + InterfaceFileRef ReexportB("/u/l/l/libbar.dylib", + {Target(AK_x86_64, PLATFORM_MACCATALYST)}); + InterfaceFileRef ReexportC("/u/l/l/libfoo.dylib", + {Target(AK_x86_64, PLATFORM_MACCATALYST)}); + EXPECT_EQ(3U, File->reexportedLibraries().size()); + EXPECT_EQ(ReexportA, File->reexportedLibraries().at(0)); + EXPECT_EQ(ReexportB, File->reexportedLibraries().at(1)); + EXPECT_EQ(ReexportC, File->reexportedLibraries().at(2)); +} + +TEST(TBDv5, ReadMultipleDocuments) { + static const char TBDv5File[] = R"({ +"tapi_tbd_version": 5, +"main_library": { + "target_info": [ + { + "target": "armv7-ios", + "min_deployment": "11.0" + } + ], + "install_names":[ + { "name":"/S/L/F/Foo.framework/Foo" } + ], + "reexported_libraries": [ + { "names": ["/u/l/l/libfoo.dylib"] } + ] +}, +"libraries": [ + { + "target_info": [ + { + "target": "armv7-ios", + "min_deployment": "11.0" + } + ], + "install_names":[ + { "name":"/u/l/l/libfoo.dylib" } + ], + "flags":[ + { "attributes": ["not_app_extension_safe"] } + ], + "exported_symbols": [ + { + "data": { + "thread_local": [ "_globalVar" ], + "objc_class": [ "ClassData" ], + "objc_eh_type": [ "ClassA", "ClassB" ] + }, + "text": { + "global": [ "_funcFoo" ] + } + } + ] + } +]})"; + + Expected Result = + TextAPIReader::get(MemoryBufferRef(TBDv5File, "Test.tbd")); + EXPECT_TRUE(!!Result); + TBDFile File = std::move(Result.get()); + EXPECT_EQ(FileType::TBD_V5, File->getFileType()); + EXPECT_EQ(std::string("/S/L/F/Foo.framework/Foo"), File->getInstallName()); + EXPECT_TRUE(File->isTwoLevelNamespace()); + EXPECT_TRUE(File->isApplicationExtensionSafe()); + + TargetList Targets(File->targets().begin(), File->targets().end()); + Target iOSTarget(AK_armv7, PLATFORM_IOS); + EXPECT_EQ(TargetList{iOSTarget}, Targets); + std::vector Symbols(File->symbols().begin(), + File->symbols().end()); + EXPECT_EQ(0U, Symbols.size()); + + InterfaceFileRef Reexport("/u/l/l/libfoo.dylib", {iOSTarget}); + EXPECT_EQ(1U, File->reexportedLibraries().size()); + EXPECT_EQ(Reexport, File->reexportedLibraries().at(0)); + + // Check inlined library. + EXPECT_EQ(1U, File->documents().size()); + TBDReexportFile Document = File->documents().front(); + Targets = {Document->targets().begin(), Document->targets().end()}; + EXPECT_EQ(TargetList{iOSTarget}, Targets); + EXPECT_EQ(std::string("/u/l/l/libfoo.dylib"), Document->getInstallName()); + EXPECT_EQ(0U, Document->getSwiftABIVersion()); + EXPECT_TRUE(Document->isTwoLevelNamespace()); + EXPECT_FALSE(Document->isApplicationExtensionSafe()); + + ExportedSymbolSeq Exports; + for (const auto *Sym : Document->symbols()) + Exports.emplace_back( + ExportedSymbol{Sym->getKind(), + std::string(Sym->getName()), + Sym->isWeakDefined() || Sym->isWeakReferenced(), + Sym->isThreadLocalValue(), + {iOSTarget}}); + + llvm::sort(Exports); + ExportedSymbolSeq ExpectedExports = { + {SymbolKind::GlobalSymbol, "_funcFoo", false, false, {iOSTarget}}, + {SymbolKind::GlobalSymbol, "_globalVar", false, true, {iOSTarget}}, + {SymbolKind::ObjectiveCClass, "ClassData", false, false, {iOSTarget}}, + {SymbolKind::ObjectiveCClassEHType, "ClassA", false, false, {iOSTarget}}, + {SymbolKind::ObjectiveCClassEHType, "ClassB", false, false, {iOSTarget}}, + }; + + EXPECT_EQ(ExpectedExports.size(), Exports.size()); + EXPECT_TRUE( + std::equal(Exports.begin(), Exports.end(), std::begin(ExpectedExports))); +} + +} // end namespace TBDv5 -- 2.7.4