//===- 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 // 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; using TargetsToSymbols = SmallVector>>; enum TBDKey : size_t { TBDVersion = 0U, MainLibrary, Documents, TargetInfo, Targets, Target, Deployment, Flags, Attributes, InstallName, CurrentVersion, CompatibilityVersion, Version, SwiftABI, ABI, ParentUmbrella, Umbrella, AllowableClients, Clients, ReexportLibs, Names, Name, Exports, Reexports, Undefineds, Data, Text, Weak, ThreadLocal, Globals, ObjCClass, ObjCEHType, ObjCIvar, RPath, Paths, }; std::array Keys = { "tapi_tbd_version", "main_library", "libraries", "target_info", "targets", "target", "min_deployment", "flags", "attributes", "install_names", "current_versions", "compatibility_versions", "version", "swift_abi", "abi", "parent_umbrellas", "umbrella", "allowable_clients", "clients", "reexported_libraries", "names", "name", "exported_symbols", "reexported_symbols", "undefined_symbols", "data", "text", "weak", "thread_local", "global", "objc_class", "objc_eh_type", "objc_ivar", "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 { public: JSONStubError(Twine ErrMsg) : Message(ErrMsg.str()) {} void log(llvm::raw_ostream &OS) const override { OS << Message << "\n"; } std::error_code convertToErrorCode() const override { return llvm::inconvertibleErrorCode(); } private: std::string Message; }; template Expected getRequiredValue( TBDKey Key, const Object *Obj, std::function(const Object *, StringRef)> GetValue, std::function(JsonT)> Validate = nullptr) { std::optional Val = GetValue(Obj, Keys[Key]); if (!Val) return make_error(getParseErrorMsg(Key)); if (Validate == nullptr) return static_cast(*Val); std::optional Result = Validate(*Val); if (!Result.has_value()) return make_error(getParseErrorMsg(Key)); return Result.value(); } template Expected getRequiredValue( TBDKey Key, const Object *Obj, std::function(const Object *, StringRef)> const GetValue, StubT DefaultValue, function_ref(JsonT)> Validate) { std::optional Val = GetValue(Obj, Keys[Key]); if (!Val) return DefaultValue; std::optional Result; Result = Validate(*Val); if (!Result.has_value()) return make_error(getParseErrorMsg(Key)); return Result.value(); } Error collectFromArray(TBDKey Key, const Object *Obj, function_ref Append, bool IsRequired = false) { const auto *Values = Obj->getArray(Keys[Key]); if (!Values) { if (IsRequired) return make_error(getParseErrorMsg(Key)); return Error::success(); } for (const Value &Val : *Values) { auto ValStr = Val.getAsString(); if (!ValStr.has_value()) return make_error(getParseErrorMsg(Key)); Append(ValStr.value()); } return Error::success(); } namespace StubParser { Expected getVersion(const Object *File) { auto VersionOrErr = getRequiredValue( TBDKey::TBDVersion, File, &Object::getInteger, [](int64_t Val) -> std::optional { unsigned Result = Val; if (Result != 5) return std::nullopt; return FileType::TBD_V5; }); if (!VersionOrErr) return VersionOrErr.takeError(); return *VersionOrErr; } Expected getTargets(const Object *Section) { const auto *Targets = Section->getArray(Keys[TBDKey::Targets]); if (!Targets) return make_error(getParseErrorMsg(TBDKey::Targets)); TargetList IFTargets; for (const Value &JSONTarget : *Targets) { auto TargetStr = JSONTarget.getAsString(); if (!TargetStr.has_value()) return make_error(getParseErrorMsg(TBDKey::Target)); auto TargetOrErr = Target::create(TargetStr.value()); if (!TargetOrErr) return make_error(getParseErrorMsg(TBDKey::Target)); IFTargets.push_back(*TargetOrErr); } return std::move(IFTargets); } Expected getTargetsSection(const Object *Section) { const Array *Targets = Section->getArray(Keys[TBDKey::TargetInfo]); if (!Targets) return make_error(getParseErrorMsg(TBDKey::Targets)); TargetList IFTargets; for (const Value &JSONTarget : *Targets) { const auto *Obj = JSONTarget.getAsObject(); if (!Obj) return make_error(getParseErrorMsg(TBDKey::Target)); auto TargetStr = getRequiredValue(TBDKey::Target, Obj, &Object::getString); if (!TargetStr) return make_error(getParseErrorMsg(TBDKey::Target)); auto TargetOrErr = Target::create(*TargetStr); if (!TargetOrErr) return make_error(getParseErrorMsg(TBDKey::Target)); auto VersionStr = Obj->getString(Keys[TBDKey::Deployment]); VersionTuple Version; if (VersionStr && Version.tryParse(*VersionStr)) return make_error(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 getNameSection(const Object *File) { const Array *Section = File->getArray(Keys[TBDKey::InstallName]); if (!Section) return make_error(getParseErrorMsg(TBDKey::InstallName)); assert(!Section->empty() && "unexpected missing install name"); // TODO: Just take first for now. const auto *Obj = Section->front().getAsObject(); if (!Obj) return make_error(getParseErrorMsg(TBDKey::InstallName)); return getRequiredValue(TBDKey::Name, Obj, &Object::getString); } Expected getSymbolSection(const Object *File, TBDKey Key, TargetList &Targets) { const Array *Section = File->getArray(Keys[Key]); if (!Section) return TargetsToSymbols(); SymbolFlags SectionFlag; switch (Key) { case TBDKey::Reexports: SectionFlag = SymbolFlags::Rexported; break; case TBDKey::Undefineds: SectionFlag = SymbolFlags::Undefined; break; default: SectionFlag = SymbolFlags::None; break; }; TargetsToSymbols Result; TargetList MappedTargets; for (auto Val : *Section) { auto *Obj = Val.getAsObject(); if (!Obj) continue; auto TargetsOrErr = getTargets(Obj); if (!TargetsOrErr) { MappedTargets = Targets; consumeError(TargetsOrErr.takeError()); } else { MappedTargets = *TargetsOrErr; } Result.emplace_back( std::make_pair(std::move(MappedTargets), std::vector())); auto *DataSection = Obj->getObject(Keys[TBDKey::Data]); auto *TextSection = Obj->getObject(Keys[TBDKey::Text]); // There should be at least one valid section. if (!DataSection && !TextSection) return make_error(getParseErrorMsg(Key)); if (DataSection) { auto Err = collectSymbolsFromSegment(DataSection, Result, SectionFlag | 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 getLibSection(const Object *File, TBDKey Key, TBDKey SubKey, const TargetList &Targets) { auto *Section = File->getArray(Keys[Key]); if (!Section) return AttrToTargets(); AttrToTargets Result; TargetList MappedTargets; for (auto Val : *Section) { auto *Obj = Val.getAsObject(); if (!Obj) continue; auto TargetsOrErr = getTargets(Obj); if (!TargetsOrErr) { MappedTargets = Targets; consumeError(TargetsOrErr.takeError()); } else { MappedTargets = *TargetsOrErr; } auto Err = collectFromArray(SubKey, Obj, [&Result, &MappedTargets](StringRef Key) { Result[Key.str()] = MappedTargets; }); if (Err) return std::move(Err); } return std::move(Result); } Expected getUmbrellaSection(const Object *File, const TargetList &Targets) { const auto *Umbrella = File->getArray(Keys[TBDKey::ParentUmbrella]); if (!Umbrella) return AttrToTargets(); AttrToTargets Result; TargetList MappedTargets; for (auto Val : *Umbrella) { auto *Obj = Val.getAsObject(); if (!Obj) return make_error( getParseErrorMsg(TBDKey::ParentUmbrella)); // Get Targets section. auto TargetsOrErr = getTargets(Obj); if (!TargetsOrErr) { MappedTargets = Targets; consumeError(TargetsOrErr.takeError()); } else { MappedTargets = *TargetsOrErr; } auto UmbrellaOrErr = getRequiredValue(TBDKey::Umbrella, Obj, &Object::getString); if (!UmbrellaOrErr) return UmbrellaOrErr.takeError(); Result[UmbrellaOrErr->str()] = Targets; } return std::move(Result); } Expected getSwiftVersion(const Object *File) { const Array *Versions = File->getArray(Keys[TBDKey::SwiftABI]); if (!Versions) return 0; for (const auto &Val : *Versions) { const auto *Obj = Val.getAsObject(); if (!Obj) return make_error(getParseErrorMsg(TBDKey::SwiftABI)); // TODO: Take first for now. return getRequiredValue(TBDKey::ABI, Obj, &Object::getInteger); } return 0; } Expected getPackedVersion(const Object *File, TBDKey Key) { const Array *Versions = File->getArray(Keys[Key]); if (!Versions) return PackedVersion(1, 0, 0); for (const auto &Val : *Versions) { const auto *Obj = Val.getAsObject(); if (!Obj) return make_error(getParseErrorMsg(Key)); auto ValidatePV = [](StringRef Version) -> std::optional { PackedVersion PV; auto [success, truncated] = PV.parse64(Version); if (!success || truncated) return std::nullopt; return PV; }; // TODO: Take first for now. return getRequiredValue( TBDKey::Version, Obj, &Object::getString, PackedVersion(1, 0, 0), ValidatePV); } return PackedVersion(1, 0, 0); } Expected getFlags(const Object *File) { TBDFlags Flags = TBDFlags::None; const Array *Section = File->getArray(Keys[TBDKey::Flags]); if (!Section || 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(getParseErrorMsg(TBDKey::Flags)); auto FlagsOrErr = collectFromArray(TBDKey::Attributes, Obj, [&Flags](StringRef Flag) { TBDFlags TBDFlag = StringSwitch(Flag) .Case("flat_namespace", TBDFlags::FlatNamespace) .Case("not_app_extension_safe", TBDFlags::NotApplicationExtensionSafe) .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; Expected parseToInterfaceFile(const Object *File) { auto TargetsOrErr = getTargetsSection(File); if (!TargetsOrErr) return TargetsOrErr.takeError(); TargetList Targets = *TargetsOrErr; auto NameOrErr = getNameSection(File); if (!NameOrErr) return NameOrErr.takeError(); StringRef Name = *NameOrErr; auto CurrVersionOrErr = getPackedVersion(File, TBDKey::CurrentVersion); if (!CurrVersionOrErr) return CurrVersionOrErr.takeError(); PackedVersion CurrVersion = *CurrVersionOrErr; auto CompVersionOrErr = getPackedVersion(File, TBDKey::CompatibilityVersion); if (!CompVersionOrErr) return CompVersionOrErr.takeError(); PackedVersion CompVersion = *CompVersionOrErr; auto SwiftABIOrErr = getSwiftVersion(File); if (!SwiftABIOrErr) return SwiftABIOrErr.takeError(); uint8_t SwiftABI = *SwiftABIOrErr; auto FlagsOrErr = getFlags(File); if (!FlagsOrErr) return FlagsOrErr.takeError(); TBDFlags Flags = *FlagsOrErr; auto UmbrellasOrErr = getUmbrellaSection(File, Targets); if (!UmbrellasOrErr) return UmbrellasOrErr.takeError(); AttrToTargets Umbrellas = *UmbrellasOrErr; auto ClientsOrErr = getLibSection(File, TBDKey::AllowableClients, TBDKey::Clients, Targets); if (!ClientsOrErr) return ClientsOrErr.takeError(); AttrToTargets Clients = *ClientsOrErr; auto RLOrErr = getLibSection(File, TBDKey::ReexportLibs, TBDKey::Names, Targets); if (!RLOrErr) return RLOrErr.takeError(); AttrToTargets ReexportLibs = std::move(*RLOrErr); auto 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)); 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) for (auto &Sym : Symbols) F->addSymbol(Sym.Kind, Sym.Name, Targets, Sym.Flags); for (auto &[Targets, Symbols] : Reexports) for (auto &Sym : Symbols) F->addSymbol(Sym.Kind, Sym.Name, Targets, Sym.Flags); for (auto &[Targets, Symbols] : Undefineds) for (auto &Sym : Symbols) F->addSymbol(Sym.Kind, Sym.Name, Targets, Sym.Flags); return std::move(F); } Expected> getInlinedLibs(const Object *File) { std::vector IFs; const Array *Files = File->getArray(Keys[TBDKey::Documents]); if (!Files) return std::move(IFs); for (auto Lib : *Files) { auto IFOrErr = parseToInterfaceFile(Lib.getAsObject()); if (!IFOrErr) return IFOrErr.takeError(); auto IF = std::move(*IFOrErr); IFs.emplace_back(std::move(IF)); } return std::move(IFs); } } // namespace StubParser } // namespace Expected> MachO::getInterfaceFileFromJSON(StringRef JSON) { auto ValOrErr = parse(JSON); if (!ValOrErr) return ValOrErr.takeError(); auto *Root = ValOrErr->getAsObject(); auto VersionOrErr = StubParser::getVersion(Root); if (!VersionOrErr) return VersionOrErr.takeError(); FileType Version = *VersionOrErr; Object *MainLib = Root->getObject(Keys[TBDKey::MainLibrary]); auto IFOrErr = StubParser::parseToInterfaceFile(MainLib); if (!IFOrErr) return IFOrErr.takeError(); (*IFOrErr)->setFileType(Version); std::unique_ptr IF(std::move(*IFOrErr)); auto IFsOrErr = StubParser::getInlinedLibs(Root); if (!IFsOrErr) return IFsOrErr.takeError(); for (auto &File : *IFsOrErr) { File->setFileType(Version); IF->addDocument(std::shared_ptr(std::move(File))); } return std::move(IF); } namespace { template 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 std::vector serializeTargets(const AggregateT Targets, const TargetList &ActiveTargets) { std::vector 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 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>; template 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 >> Array serializeField(TBDKey Key, const AggregateT &Values, const TargetList &ActiveTargets, bool IsArray = true) { std::map> Entries; for (const auto &[Target, Val] : Values) Entries[Val].insert(Target); if (!IsArray) { std::map, 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 &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 Weaks; std::vector Globals; std::vector TLV; std::vector ObjCClasses; std::vector IVars; std::vector 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, SymbolFields> Entries; for (const auto *Sym : Symbols) { std::set 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"); 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 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(getSerializeErrorMsg(TBDKey::TargetInfo)); Array Name = serializeScalar(TBDKey::Name, File->getInstallName()); if (!insertNonEmptyValues(Library, TBDKey::InstallName, std::move(Name))) return make_error(getSerializeErrorMsg(TBDKey::InstallName)); // Handle optional keys. Array Flags = serializeFlags(File); insertNonEmptyValues(Library, TBDKey::Flags, std::move(Flags)); Array CurrentV = serializeScalar( TBDKey::Version, File->getCurrentVersion(), PackedVersion(1, 0, 0)); insertNonEmptyValues(Library, TBDKey::CurrentVersion, std::move(CurrentV)); Array CompatV = serializeScalar( TBDKey::Version, File->getCompatibilityVersion(), PackedVersion(1, 0, 0)); insertNonEmptyValues(Library, TBDKey::CompatibilityVersion, std::move(CompatV)); Array SwiftABI = serializeScalar( 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 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(); }