cb424d7448
verify-patch-sanity.py validates every active recipe .patch has internally- consistent hunk line counts — catching the 'malformed patch at line N' failure at commit/CI/preflight time instead of hours into a cook. This cycle hit that class three times (qtwaylandscanner, sddm, xwayland), each only discovered when cookbook tried to apply the patch. Running it across the repo found 29 latent malformed patches (validated against GNU patch: e.g. relibc/P3-sysv-ipc reproduces 'malformed patch at line 22'). They were harmless only because they sit in vendored recipes (baked, not re- applied) — but would fail on any version-bump re-derivation. --fix recounts the hunk headers (body untouched) and repaired all 29. Wired into build-preflight.sh (Phase 1.0D) and redbear-ci.yml, with a unit test (test-patch-sanity.sh). Skips archived/legacy trees and unvalidatable formats (empty placeholders, bare-@@ git hunks).
1084 lines
35 KiB
C++
1084 lines
35 KiB
C++
//===- 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" # 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<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 TargetOrErr = Target::create(*TargetStr);
|
|
if (!TargetOrErr)
|
|
return make_error<JSONStubError>(getParseErrorMsg(TBDKey::Target));
|
|
|
|
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();
|
|
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<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)
|
|
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;
|
|
|
|
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();
|
|
}
|