diff options
Diffstat (limited to 'llvm/lib/TextAPI/TextStubV5.cpp')
-rw-r--r-- | llvm/lib/TextAPI/TextStubV5.cpp | 1021 |
1 files changed, 1021 insertions, 0 deletions
diff --git a/llvm/lib/TextAPI/TextStubV5.cpp b/llvm/lib/TextAPI/TextStubV5.cpp new file mode 100644 index 000000000000..5b3d69b8d94a --- /dev/null +++ b/llvm/lib/TextAPI/TextStubV5.cpp @@ -0,0 +1,1021 @@ +//===- 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; + +namespace { +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 +} // 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; + + 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(); +} |