| //===- 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" |
| #include <utility> |
| |
| // 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<std::string, TargetList>; |
| using TargetsToSymbols = |
| SmallVector<std::pair<TargetList, std::vector<JSONSymbol>>>; |
| |
| 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, |
| RPath, |
| Paths, |
| }; |
| |
| std::array<StringRef, 64> 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", |
| "rpaths", |
| "paths", |
| }; |
| |
| static llvm::SmallString<128> getParseErrorMsg(TBDKey Key) { |
| return {"invalid ", Keys[Key], " section"}; |
| } |
| |
| static llvm::SmallString<128> getSerializeErrorMsg(TBDKey Key) { |
| return {"missing ", Keys[Key], " information"}; |
| } |
| |
| class JSONStubError : public llvm::ErrorInfo<llvm::json::ParseError> { |
| 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 <typename JsonT, typename StubT = JsonT> |
| Expected<StubT> getRequiredValue( |
| TBDKey Key, const Object *Obj, |
| std::function<std::optional<JsonT>(const Object *, StringRef)> GetValue, |
| std::function<std::optional<StubT>(JsonT)> Validate = nullptr) { |
| std::optional<JsonT> Val = GetValue(Obj, Keys[Key]); |
| if (!Val) |
| return make_error<JSONStubError>(getParseErrorMsg(Key)); |
| |
| if (Validate == nullptr) |
| return static_cast<StubT>(*Val); |
| |
| std::optional<StubT> Result = Validate(*Val); |
| if (!Result.has_value()) |
| return make_error<JSONStubError>(getParseErrorMsg(Key)); |
| return Result.value(); |
| } |
| |
| template <typename JsonT, typename StubT = JsonT> |
| Expected<StubT> getRequiredValue( |
| TBDKey Key, const Object *Obj, |
| std::function<std::optional<JsonT>(const Object *, StringRef)> GetValue, |
| StubT DefaultValue, std::function<std::optional<StubT>(JsonT)> Validate) { |
| std::optional<JsonT> Val = GetValue(Obj, Keys[Key]); |
| if (!Val) |
| return DefaultValue; |
| |
| std::optional<StubT> Result; |
| Result = Validate(*Val); |
| if (!Result.has_value()) |
| return make_error<JSONStubError>(getParseErrorMsg(Key)); |
| return Result.value(); |
| } |
| |
| Error collectFromArray(TBDKey Key, const Object *Obj, |
| std::function<void(StringRef)> Append, |
| bool IsRequired = false) { |
| const auto *Values = Obj->getArray(Keys[Key]); |
| if (!Values) { |
| if (IsRequired) |
| return make_error<JSONStubError>(getParseErrorMsg(Key)); |
| return Error::success(); |
| } |
| |
| for (const Value &Val : *Values) { |
| auto ValStr = Val.getAsString(); |
| if (!ValStr.has_value()) |
| return make_error<JSONStubError>(getParseErrorMsg(Key)); |
| Append(ValStr.value()); |
| } |
| |
| return Error::success(); |
| } |
| |
| namespace StubParser { |
| |
| Expected<FileType> getVersion(const Object *File) { |
| auto VersionOrErr = getRequiredValue<int64_t, FileType>( |
| TBDKey::TBDVersion, File, &Object::getInteger, |
| [](int64_t Val) -> std::optional<FileType> { |
| unsigned Result = Val; |
| if (Result != 5) |
| return std::nullopt; |
| return FileType::TBD_V5; |
| }); |
| |
| if (!VersionOrErr) |
| return VersionOrErr.takeError(); |
| return *VersionOrErr; |
| } |
| |
| Expected<TargetList> getTargets(const Object *Section) { |
| const auto *Targets = Section->getArray(Keys[TBDKey::Targets]); |
| if (!Targets) |
| return make_error<JSONStubError>(getParseErrorMsg(TBDKey::Targets)); |
| |
| TargetList IFTargets; |
| for (const Value &JSONTarget : *Targets) { |
| auto TargetStr = JSONTarget.getAsString(); |
| if (!TargetStr.has_value()) |
| return make_error<JSONStubError>(getParseErrorMsg(TBDKey::Target)); |
| auto TargetOrErr = Target::create(TargetStr.value()); |
| if (!TargetOrErr) |
| return make_error<JSONStubError>(getParseErrorMsg(TBDKey::Target)); |
| IFTargets.push_back(*TargetOrErr); |
| } |
| return std::move(IFTargets); |
| } |
| |
| Expected<TargetList> getTargetsSection(const Object *Section) { |
| const Array *Targets = Section->getArray(Keys[TBDKey::TargetInfo]); |
| if (!Targets) |
| return make_error<JSONStubError>(getParseErrorMsg(TBDKey::Targets)); |
| |
| TargetList IFTargets; |
| for (const Value &JSONTarget : *Targets) { |
| const auto *Obj = JSONTarget.getAsObject(); |
| if (!Obj) |
| return make_error<JSONStubError>(getParseErrorMsg(TBDKey::Target)); |
| auto TargetStr = |
| getRequiredValue<StringRef>(TBDKey::Target, Obj, &Object::getString); |
| if (!TargetStr) |
| return make_error<JSONStubError>(getParseErrorMsg(TBDKey::Target)); |
| auto VersionStr = getRequiredValue<StringRef>(TBDKey::Deployment, Obj, |
| &Object::getString); |
| if (!VersionStr) |
| return make_error<JSONStubError>(getParseErrorMsg(TBDKey::Deployment)); |
| VersionTuple Version; |
| if (Version.tryParse(*VersionStr)) |
| return make_error<JSONStubError>(getParseErrorMsg(TBDKey::Deployment)); |
| auto TargetOrErr = Target::create(*TargetStr); |
| if (!TargetOrErr) |
| return make_error<JSONStubError>(getParseErrorMsg(TBDKey::Target)); |
| TargetOrErr->MinDeployment = Version; |
| // Convert to LLVM::Triple to accurately compute minOS + platform + arch |
| // pairing. |
| IFTargets.push_back( |
| MachO::Target(Triple(getTargetTripleName(*TargetOrErr)))); |
| } |
| 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::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<StringRef> getNameSection(const Object *File) { |
| const Array *Section = File->getArray(Keys[TBDKey::InstallName]); |
| if (!Section) |
| return make_error<JSONStubError>(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<JSONStubError>(getParseErrorMsg(TBDKey::InstallName)); |
| |
| return getRequiredValue<StringRef>(TBDKey::Name, Obj, &Object::getString); |
| } |
| |
| Expected<TargetsToSymbols> 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(std::move(MappedTargets), std::vector<JSONSymbol>())); |
| |
| 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<JSONStubError>(getParseErrorMsg(Key)); |
| |
| if (DataSection) { |
| auto Err = collectSymbolsFromSegment(DataSection, Result, |
| SectionFlag | SymbolFlags::Data); |
| if (Err) |
| return std::move(Err); |
| } |
| if (TextSection) { |
| auto Err = collectSymbolsFromSegment(TextSection, Result, |
| SectionFlag | SymbolFlags::Text); |
| if (Err) |
| return std::move(Err); |
| } |
| } |
| |
| return std::move(Result); |
| } |
| |
| Expected<AttrToTargets> 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<AttrToTargets> 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<JSONStubError>( |
| getParseErrorMsg(TBDKey::ParentUmbrella)); |
| |
| // Get Targets section. |
| auto TargetsOrErr = getTargets(Obj); |
| if (!TargetsOrErr) { |
| MappedTargets = Targets; |
| consumeError(TargetsOrErr.takeError()); |
| } else { |
| MappedTargets = *TargetsOrErr; |
| } |
| |
| auto UmbrellaOrErr = |
| getRequiredValue<StringRef>(TBDKey::Umbrella, Obj, &Object::getString); |
| if (!UmbrellaOrErr) |
| return UmbrellaOrErr.takeError(); |
| Result[UmbrellaOrErr->str()] = Targets; |
| } |
| return std::move(Result); |
| } |
| |
| Expected<uint8_t> 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<JSONStubError>(getParseErrorMsg(TBDKey::SwiftABI)); |
| |
| // TODO: Take first for now. |
| return getRequiredValue<int64_t, uint8_t>(TBDKey::ABI, Obj, |
| &Object::getInteger); |
| } |
| |
| return 0; |
| } |
| |
| Expected<PackedVersion> 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<JSONStubError>(getParseErrorMsg(Key)); |
| |
| auto ValidatePV = [](StringRef Version) -> std::optional<PackedVersion> { |
| PackedVersion PV; |
| auto [success, truncated] = PV.parse64(Version); |
| if (!success || truncated) |
| return std::nullopt; |
| return PV; |
| }; |
| // TODO: Take first for now. |
| return getRequiredValue<StringRef, PackedVersion>( |
| TBDKey::Version, Obj, &Object::getString, PackedVersion(1, 0, 0), |
| ValidatePV); |
| } |
| |
| return PackedVersion(1, 0, 0); |
| } |
| |
| Expected<TBDFlags> 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<JSONStubError>(getParseErrorMsg(TBDKey::Flags)); |
| |
| auto FlagsOrErr = |
| collectFromArray(TBDKey::Attributes, Obj, [&Flags](StringRef Flag) { |
| TBDFlags TBDFlag = |
| StringSwitch<TBDFlags>(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<InterfaceFile>; |
| Expected<IFPtr> 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 RPathsOrErr = getLibSection(File, TBDKey::RPath, TBDKey::Paths, Targets); |
| if (!RPathsOrErr) |
| return RPathsOrErr.takeError(); |
| AttrToTargets RPaths = std::move(*RPathsOrErr); |
| |
| 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 &[Path, Targets] : RPaths) |
| for (auto Target : Targets) |
| F->addRPath(Target, Path); |
| 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<std::vector<IFPtr>> getInlinedLibs(const Object *File) { |
| std::vector<IFPtr> 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<std::unique_ptr<InterfaceFile>> |
| 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<InterfaceFile> 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<InterfaceFile>(std::move(File))); |
| } |
| return std::move(IF); |
| } |
| |
| namespace { |
| |
| template <typename ContainerT = Array> |
| bool insertNonEmptyValues(Object &Obj, TBDKey Key, ContainerT &&Contents) { |
| if (Contents.empty()) |
| return false; |
| Obj[Keys[Key]] = std::move(Contents); |
| return true; |
| } |
| |
| std::string getFormattedStr(const MachO::Target &Targ) { |
| std::string PlatformStr = Targ.Platform == PLATFORM_MACCATALYST |
| ? "maccatalyst" |
| : getOSAndEnvironmentName(Targ.Platform); |
| return (getArchitectureName(Targ.Arch) + "-" + PlatformStr).str(); |
| } |
| |
| template <typename AggregateT> |
| std::vector<std::string> serializeTargets(const AggregateT Targets, |
| const TargetList &ActiveTargets) { |
| std::vector<std::string> TargetsStr; |
| if (Targets.size() == ActiveTargets.size()) |
| return TargetsStr; |
| |
| llvm::for_each(Targets, [&TargetsStr](const MachO::Target &Target) { |
| TargetsStr.emplace_back(getFormattedStr(Target)); |
| }); |
| return TargetsStr; |
| } |
| |
| Array serializeTargetInfo(const TargetList &ActiveTargets) { |
| Array Targets; |
| for (const auto Targ : ActiveTargets) { |
| Object TargetInfo; |
| TargetInfo[Keys[TBDKey::Deployment]] = Targ.MinDeployment.getAsString(); |
| TargetInfo[Keys[TBDKey::Target]] = getFormattedStr(Targ); |
| Targets.emplace_back(std::move(TargetInfo)); |
| } |
| return Targets; |
| } |
| |
| template <typename ValueT, typename EntryT = ValueT> |
| Array serializeScalar(TBDKey Key, ValueT Value, ValueT Default = ValueT()) { |
| if (Value == Default) |
| return {}; |
| Array Container; |
| Object ScalarObj({Object::KV({Keys[Key], EntryT(Value)})}); |
| |
| Container.emplace_back(std::move(ScalarObj)); |
| return Container; |
| } |
| |
| using TargetsToValuesMap = |
| std::map<std::vector<std::string>, std::vector<std::string>>; |
| |
| template <typename AggregateT = TargetsToValuesMap> |
| Array serializeAttrToTargets(AggregateT &Entries, TBDKey Key) { |
| Array Container; |
| for (const auto &[Targets, Values] : Entries) { |
| Object Obj; |
| insertNonEmptyValues(Obj, TBDKey::Targets, std::move(Targets)); |
| Obj[Keys[Key]] = Values; |
| Container.emplace_back(std::move(Obj)); |
| } |
| return Container; |
| } |
| |
| template <typename ValueT = std::string, |
| typename AggregateT = std::vector<std::pair<MachO::Target, ValueT>>> |
| Array serializeField(TBDKey Key, const AggregateT &Values, |
| const TargetList &ActiveTargets, bool IsArray = true) { |
| std::map<ValueT, std::set<MachO::Target>> Entries; |
| for (const auto &[Target, Val] : Values) |
| Entries[Val].insert(Target); |
| |
| if (!IsArray) { |
| std::map<std::vector<std::string>, std::string> FinalEntries; |
| for (const auto &[Val, Targets] : Entries) |
| FinalEntries[serializeTargets(Targets, ActiveTargets)] = Val; |
| return serializeAttrToTargets(FinalEntries, Key); |
| } |
| |
| TargetsToValuesMap FinalEntries; |
| for (const auto &[Val, Targets] : Entries) |
| FinalEntries[serializeTargets(Targets, ActiveTargets)].emplace_back(Val); |
| return serializeAttrToTargets(FinalEntries, Key); |
| } |
| |
| Array serializeField(TBDKey Key, const std::vector<InterfaceFileRef> &Values, |
| const TargetList &ActiveTargets) { |
| TargetsToValuesMap FinalEntries; |
| for (const auto &Ref : Values) { |
| TargetList Targets{Ref.targets().begin(), Ref.targets().end()}; |
| FinalEntries[serializeTargets(Targets, ActiveTargets)].emplace_back( |
| Ref.getInstallName()); |
| } |
| return serializeAttrToTargets(FinalEntries, Key); |
| } |
| |
| struct SymbolFields { |
| struct SymbolTypes { |
| std::vector<StringRef> Weaks; |
| std::vector<StringRef> Globals; |
| std::vector<StringRef> TLV; |
| std::vector<StringRef> ObjCClasses; |
| std::vector<StringRef> IVars; |
| std::vector<StringRef> EHTypes; |
| |
| bool empty() const { |
| return Weaks.empty() && Globals.empty() && TLV.empty() && |
| ObjCClasses.empty() && IVars.empty() && EHTypes.empty(); |
| } |
| }; |
| SymbolTypes Data; |
| SymbolTypes Text; |
| }; |
| |
| Array serializeSymbols(InterfaceFile::const_filtered_symbol_range Symbols, |
| const TargetList &ActiveTargets) { |
| auto AssignForSymbolType = [](SymbolFields::SymbolTypes &Assignment, |
| const Symbol *Sym) { |
| switch (Sym->getKind()) { |
| case SymbolKind::ObjectiveCClass: |
| Assignment.ObjCClasses.emplace_back(Sym->getName()); |
| return; |
| case SymbolKind::ObjectiveCClassEHType: |
| Assignment.EHTypes.emplace_back(Sym->getName()); |
| return; |
| case SymbolKind::ObjectiveCInstanceVariable: |
| Assignment.IVars.emplace_back(Sym->getName()); |
| return; |
| case SymbolKind::GlobalSymbol: { |
| if (Sym->isWeakReferenced() || Sym->isWeakDefined()) |
| Assignment.Weaks.emplace_back(Sym->getName()); |
| else if (Sym->isThreadLocalValue()) |
| Assignment.TLV.emplace_back(Sym->getName()); |
| else |
| Assignment.Globals.emplace_back(Sym->getName()); |
| return; |
| } |
| } |
| }; |
| |
| std::map<std::vector<std::string>, SymbolFields> Entries; |
| for (const auto *Sym : Symbols) { |
| std::set<MachO::Target> Targets{Sym->targets().begin(), |
| Sym->targets().end()}; |
| auto JSONTargets = serializeTargets(Targets, ActiveTargets); |
| if (Sym->isData()) |
| AssignForSymbolType(Entries[std::move(JSONTargets)].Data, Sym); |
| else if (Sym->isText()) |
| AssignForSymbolType(Entries[std::move(JSONTargets)].Text, Sym); |
| else |
| llvm_unreachable("unexpected symbol type"); |
| } |
| |
| auto InsertSymbolsToJSON = [](Object &SymSection, TBDKey SegmentKey, |
| SymbolFields::SymbolTypes &SymField) { |
| if (SymField.empty()) |
| return; |
| Object Segment; |
| insertNonEmptyValues(Segment, TBDKey::Globals, std::move(SymField.Globals)); |
| insertNonEmptyValues(Segment, TBDKey::ThreadLocal, std::move(SymField.TLV)); |
| insertNonEmptyValues(Segment, TBDKey::Weak, std::move(SymField.Weaks)); |
| insertNonEmptyValues(Segment, TBDKey::ObjCClass, |
| std::move(SymField.ObjCClasses)); |
| insertNonEmptyValues(Segment, TBDKey::ObjCEHType, |
| std::move(SymField.EHTypes)); |
| insertNonEmptyValues(Segment, TBDKey::ObjCIvar, std::move(SymField.IVars)); |
| insertNonEmptyValues(SymSection, SegmentKey, std::move(Segment)); |
| }; |
| |
| Array SymbolSection; |
| for (auto &[Targets, Fields] : Entries) { |
| Object AllSyms; |
| insertNonEmptyValues(AllSyms, TBDKey::Targets, std::move(Targets)); |
| InsertSymbolsToJSON(AllSyms, TBDKey::Data, Fields.Data); |
| InsertSymbolsToJSON(AllSyms, TBDKey::Text, Fields.Text); |
| SymbolSection.emplace_back(std::move(AllSyms)); |
| } |
| |
| return SymbolSection; |
| } |
| |
| Array serializeFlags(const InterfaceFile *File) { |
| // TODO: Give all Targets the same flags for now. |
| Array Flags; |
| if (!File->isTwoLevelNamespace()) |
| Flags.emplace_back("flat_namespace"); |
| if (!File->isApplicationExtensionSafe()) |
| Flags.emplace_back("not_app_extension_safe"); |
| return serializeScalar(TBDKey::Attributes, std::move(Flags)); |
| } |
| |
| Expected<Object> serializeIF(const InterfaceFile *File) { |
| Object Library; |
| |
| // Handle required keys. |
| TargetList ActiveTargets{File->targets().begin(), File->targets().end()}; |
| if (!insertNonEmptyValues(Library, TBDKey::TargetInfo, |
| serializeTargetInfo(ActiveTargets))) |
| return make_error<JSONStubError>(getSerializeErrorMsg(TBDKey::TargetInfo)); |
| |
| Array Name = serializeScalar<StringRef>(TBDKey::Name, File->getInstallName()); |
| if (!insertNonEmptyValues(Library, TBDKey::InstallName, std::move(Name))) |
| return make_error<JSONStubError>(getSerializeErrorMsg(TBDKey::InstallName)); |
| |
| // Handle optional keys. |
| Array Flags = serializeFlags(File); |
| insertNonEmptyValues(Library, TBDKey::Flags, std::move(Flags)); |
| |
| Array CurrentV = serializeScalar<PackedVersion, std::string>( |
| TBDKey::Version, File->getCurrentVersion(), PackedVersion(1, 0, 0)); |
| insertNonEmptyValues(Library, TBDKey::CurrentVersion, std::move(CurrentV)); |
| |
| Array CompatV = serializeScalar<PackedVersion, std::string>( |
| TBDKey::Version, File->getCompatibilityVersion(), PackedVersion(1, 0, 0)); |
| insertNonEmptyValues(Library, TBDKey::CompatibilityVersion, |
| std::move(CompatV)); |
| |
| Array SwiftABI = serializeScalar<uint8_t, int64_t>( |
| TBDKey::ABI, File->getSwiftABIVersion(), 0u); |
| insertNonEmptyValues(Library, TBDKey::SwiftABI, std::move(SwiftABI)); |
| |
| Array RPaths = serializeField(TBDKey::Paths, File->rpaths(), ActiveTargets); |
| insertNonEmptyValues(Library, TBDKey::RPath, std::move(RPaths)); |
| |
| Array Umbrellas = serializeField(TBDKey::Umbrella, File->umbrellas(), |
| ActiveTargets, /*IsArray=*/false); |
| insertNonEmptyValues(Library, TBDKey::ParentUmbrella, std::move(Umbrellas)); |
| |
| Array Clients = |
| serializeField(TBDKey::Clients, File->allowableClients(), ActiveTargets); |
| insertNonEmptyValues(Library, TBDKey::AllowableClients, std::move(Clients)); |
| |
| Array ReexportLibs = |
| serializeField(TBDKey::Names, File->reexportedLibraries(), ActiveTargets); |
| insertNonEmptyValues(Library, TBDKey::ReexportLibs, std::move(ReexportLibs)); |
| |
| // Handle symbols. |
| Array Exports = serializeSymbols(File->exports(), ActiveTargets); |
| insertNonEmptyValues(Library, TBDKey::Exports, std::move(Exports)); |
| |
| Array Reexports = serializeSymbols(File->reexports(), ActiveTargets); |
| insertNonEmptyValues(Library, TBDKey::Reexports, std::move(Reexports)); |
| |
| if (!File->isTwoLevelNamespace()) { |
| Array Undefineds = serializeSymbols(File->undefineds(), ActiveTargets); |
| insertNonEmptyValues(Library, TBDKey::Undefineds, std::move(Undefineds)); |
| } |
| |
| return std::move(Library); |
| } |
| |
| Expected<Object> getJSON(const InterfaceFile *File) { |
| assert(File->getFileType() == FileType::TBD_V5 && |
| "unexpected json file format version"); |
| Object Root; |
| |
| auto MainLibOrErr = serializeIF(File); |
| if (!MainLibOrErr) |
| return MainLibOrErr; |
| Root[Keys[TBDKey::MainLibrary]] = std::move(*MainLibOrErr); |
| Array Documents; |
| for (const auto &Doc : File->documents()) { |
| auto LibOrErr = serializeIF(Doc.get()); |
| if (!LibOrErr) |
| return LibOrErr; |
| Documents.emplace_back(std::move(*LibOrErr)); |
| } |
| |
| Root[Keys[TBDKey::TBDVersion]] = 5; |
| insertNonEmptyValues(Root, TBDKey::Documents, std::move(Documents)); |
| return std::move(Root); |
| } |
| |
| } // namespace |
| |
| Error MachO::serializeInterfaceFileToJSON(raw_ostream &OS, |
| const InterfaceFile &File, |
| bool Compact) { |
| auto TextFile = getJSON(&File); |
| if (!TextFile) |
| return TextFile.takeError(); |
| if (Compact) |
| OS << formatv("{0}", Value(std::move(*TextFile))) << "\n"; |
| else |
| OS << formatv("{0:2}", Value(std::move(*TextFile))) << "\n"; |
| return Error::success(); |
| } |