| //===- 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 <optional> | |
| #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" # Optional: minOS defaults to 0 | |
| }, | |
| { | |
| "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; | |
| namespace { | |
| struct JSONSymbol { | |
| EncodeKind Kind; | |
| std::string Name; | |
| SymbolFlags Flags; | |
| }; | |
| using AttrToTargets = std::map<std::string, TargetList>; | |
| using TargetsToSymbols = | |
| SmallVector<std::pair<TargetList, std::vector<JSONSymbol>>>; | |
| /// Wrapper over a vector for handling textstub attributes, mapped to target | |
| /// triples, that require insertion order to be intact in the resulting \c | |
| /// InterfaceFile. | |
| class InOrderAttrToTargets { | |
| using EntryT = std::pair<std::string, TargetList>; | |
| public: | |
| void insert(EntryT &&Entry) { | |
| auto &Element = get(Entry.first); | |
| Element.second = Entry.second; | |
| } | |
| const EntryT *begin() { return Container.begin(); } | |
| const EntryT *end() { return Container.end(); } | |
| private: | |
| EntryT &get(std::string &Key) { | |
| auto *It = find_if(Container, | |
| [&Key](EntryT &Input) { return Input.first == Key; }); | |
| if (It != Container.end()) | |
| return *It; | |
| Container.push_back(EntryT(Key, {})); | |
| return Container.back(); | |
| } | |
| llvm::SmallVector<EntryT> Container; | |
| }; | |
| 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)> const | |
| GetValue, | |
| StubT DefaultValue, function_ref<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, | |
| function_ref<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<std::optional<MachO::Target>> parseTargetStr(StringRef Str) { | |
| auto TargetOrErr = MachO::Target::create(Str); | |
| if (!TargetOrErr) | |
| return make_error<JSONStubError>(getParseErrorMsg(TBDKey::Target)); | |
| if (!TargetOrErr->isValid()) | |
| return std::nullopt; | |
| return *TargetOrErr; | |
| } | |
| 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 = parseTargetStr(TargetStr.value()); | |
| if (!TargetOrErr) | |
| return TargetOrErr.takeError(); | |
| if (!TargetOrErr->has_value()) | |
| continue; | |
| 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 TargetOrErr = parseTargetStr(*TargetStr); | |
| if (!TargetOrErr) | |
| return TargetOrErr.takeError(); | |
| if (!TargetOrErr->has_value()) | |
| continue; | |
| auto VersionStr = Obj->getString(Keys[TBDKey::Deployment]); | |
| VersionTuple Version; | |
| if (VersionStr && Version.tryParse(*VersionStr)) | |
| return make_error<JSONStubError>(getParseErrorMsg(TBDKey::Deployment)); | |
| (*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 = {EncodeKind::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 = {EncodeKind::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 = {EncodeKind::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 = {EncodeKind::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 = {EncodeKind::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 = {EncodeKind::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); | |
| } | |
| template <typename ReturnT = AttrToTargets> | |
| Expected<ReturnT> getLibSection(const Object *File, TBDKey Key, TBDKey SubKey, | |
| const TargetList &Targets) { | |
| auto *Section = File->getArray(Keys[Key]); | |
| if (!Section) | |
| return ReturnT(); | |
| ReturnT 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.insert({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 || Section->empty()) | |
| return Flags; | |
| for (auto &Val : *Section) { | |
| // FIXME: Flags currently apply to all target triples. | |
| 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) | |
| .Case("sim_support", TBDFlags::SimulatorSupport) | |
| .Case("not_for_dyld_shared_cache", | |
| TBDFlags::OSLibNotForSharedCache) | |
| .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(); | |
| const AttrToTargets &Umbrellas = *UmbrellasOrErr; | |
| auto ClientsOrErr = | |
| getLibSection(File, TBDKey::AllowableClients, TBDKey::Clients, Targets); | |
| if (!ClientsOrErr) | |
| return ClientsOrErr.takeError(); | |
| const 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<InOrderAttrToTargets>( | |
| File, TBDKey::RPath, TBDKey::Paths, Targets); | |
| if (!RPathsOrErr) | |
| return RPathsOrErr.takeError(); | |
| InOrderAttrToTargets 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)); | |
| F->setSimulatorSupport((Flags & TBDFlags::SimulatorSupport)); | |
| F->setOSLibNotForSharedCache((Flags & TBDFlags::OSLibNotForSharedCache)); | |
| 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(Path, Target); | |
| for (auto &[Targets, Symbols] : Exports) { | |
| if (Targets.empty()) | |
| continue; | |
| for (auto &Sym : Symbols) | |
| F->addSymbol(Sym.Kind, Sym.Name, Targets, Sym.Flags); | |
| } | |
| for (auto &[Targets, Symbols] : Reexports) { | |
| if (Targets.empty()) | |
| continue; | |
| for (auto &Sym : Symbols) | |
| F->addSymbol(Sym.Kind, Sym.Name, Targets, Sym.Flags); | |
| } | |
| for (auto &[Targets, Symbols] : Undefineds) { | |
| if (Targets.empty()) | |
| continue; | |
| 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 | |
| } // namespace | |
| 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; | |
| for (const MachO::Target &Target : Targets) | |
| TargetsStr.emplace_back(getFormattedStr(Target)); | |
| return TargetsStr; | |
| } | |
| Array serializeTargetInfo(const TargetList &ActiveTargets) { | |
| Array Targets; | |
| for (const auto Targ : ActiveTargets) { | |
| Object TargetInfo; | |
| if (!Targ.MinDeployment.empty()) | |
| 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; | |
| } | |
| /// When there is no significance in order, the common case, serialize all | |
| /// attributes in a stable order. | |
| 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); | |
| } | |
| template < | |
| typename AggregateT = std::vector<std::pair<MachO::Target, std::string>>> | |
| Array serializeFieldInInsertionOrder(TBDKey Key, const AggregateT &Values, | |
| const TargetList &ActiveTargets) { | |
| MapVector<StringRef, std::set<MachO::Target>> Entries; | |
| for (const auto &[Target, Val] : Values) | |
| Entries[Val].insert(Target); | |
| TargetsToValuesMap FinalEntries; | |
| for (const auto &[Val, Targets] : Entries) | |
| FinalEntries[serializeTargets(Targets, ActiveTargets)].emplace_back( | |
| Val.str()); | |
| 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 EncodeKind::ObjectiveCClass: | |
| Assignment.ObjCClasses.emplace_back(Sym->getName()); | |
| return; | |
| case EncodeKind::ObjectiveCClassEHType: | |
| Assignment.EHTypes.emplace_back(Sym->getName()); | |
| return; | |
| case EncodeKind::ObjectiveCInstanceVariable: | |
| Assignment.IVars.emplace_back(Sym->getName()); | |
| return; | |
| case EncodeKind::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; | |
| llvm::sort(SymField.Globals); | |
| llvm::sort(SymField.TLV); | |
| llvm::sort(SymField.Weaks); | |
| llvm::sort(SymField.ObjCClasses); | |
| llvm::sort(SymField.EHTypes); | |
| llvm::sort(SymField.IVars); | |
| 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"); | |
| if (File->hasSimulatorSupport()) | |
| Flags.emplace_back("sim_support"); | |
| if (File->isOSLibNotForSharedCache()) | |
| Flags.emplace_back("not_for_dyld_shared_cache"); | |
| 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 = serializeFieldInInsertionOrder(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, const FileType FileKind) { | |
| assert(FileKind == 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, | |
| const FileType FileKind, | |
| bool Compact) { | |
| auto TextFile = getJSON(&File, FileKind); | |
| 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(); | |
| } |