Skip to content

[InstallAPI] Handle zippered frameworks #88205

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/DiagnosticInstallAPIKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ def warn_no_such_excluded_header_file : Warning<"no such excluded %select{public
def warn_glob_did_not_match: Warning<"glob '%0' did not match any header file">, InGroup<InstallAPIViolation>;
def err_no_such_umbrella_header_file : Error<"%select{public|private|project}1 umbrella header file not found in input: '%0'">;
def err_cannot_find_reexport : Error<"cannot find re-exported %select{framework|library}0: '%1'">;
def err_no_matching_target : Error<"no matching target found for target variant '%0'">;
def err_unsupported_vendor : Error<"vendor '%0' is not supported: '%1'">;
def err_unsupported_environment : Error<"environment '%0' is not supported: '%1'">;
def err_unsupported_os : Error<"os '%0' is not supported: '%1'">;
} // end of command line category.

let CategoryName = "Verification" in {
Expand Down
32 changes: 29 additions & 3 deletions clang/include/clang/InstallAPI/DylibVerifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ enum class VerificationMode {
using LibAttrs = llvm::StringMap<ArchitectureSet>;
using ReexportedInterfaces = llvm::SmallVector<llvm::MachO::InterfaceFile, 8>;

// Pointers to information about a zippered declaration used for
// querying and reporting violations against different
// declarations that all map to the same symbol.
struct ZipperedDeclSource {
const FrontendAttrs *FA;
clang::SourceManager *SrcMgr;
Target T;
};
using ZipperedDeclSources = std::vector<ZipperedDeclSource>;

/// Service responsible to tracking state of verification across the
/// lifetime of InstallAPI.
/// As declarations are collected during AST traversal, they are
Expand Down Expand Up @@ -68,10 +78,10 @@ class DylibVerifier : llvm::MachO::RecordVisitor {
DylibVerifier() = default;

DylibVerifier(llvm::MachO::Records &&Dylib, ReexportedInterfaces &&Reexports,
DiagnosticsEngine *Diag, VerificationMode Mode, bool Demangle,
StringRef DSYMPath)
DiagnosticsEngine *Diag, VerificationMode Mode, bool Zippered,
bool Demangle, StringRef DSYMPath)
: Dylib(std::move(Dylib)), Reexports(std::move(Reexports)), Mode(Mode),
Demangle(Demangle), DSYMPath(DSYMPath),
Zippered(Zippered), Demangle(Demangle), DSYMPath(DSYMPath),
Exports(std::make_unique<SymbolSet>()), Ctx(VerifierContext{Diag}) {}

Result verify(GlobalRecord *R, const FrontendAttrs *FA);
Expand Down Expand Up @@ -118,6 +128,15 @@ class DylibVerifier : llvm::MachO::RecordVisitor {
/// symbols should be omitted from the text-api file.
bool shouldIgnoreReexport(const Record *R, SymbolContext &SymCtx) const;

// Ignore and omit unavailable symbols in zippered libraries.
bool shouldIgnoreZipperedAvailability(const Record *R, SymbolContext &SymCtx);

// Check if an internal declaration in zippered library has an
// external declaration for a different platform. This results
// in the symbol being in a "seperate" platform slice.
bool shouldIgnoreInternalZipperedSymbol(const Record *R,
const SymbolContext &SymCtx) const;

/// Compare the visibility declarations to the linkage of symbol found in
/// dylib.
Result compareVisibility(const Record *R, SymbolContext &SymCtx,
Expand Down Expand Up @@ -173,6 +192,9 @@ class DylibVerifier : llvm::MachO::RecordVisitor {
// Controls what class of violations to report.
VerificationMode Mode = VerificationMode::Invalid;

// Library is zippered.
bool Zippered = false;

// Attempt to demangle when reporting violations.
bool Demangle = false;

Expand All @@ -182,6 +204,10 @@ class DylibVerifier : llvm::MachO::RecordVisitor {
// Valid symbols in final text file.
std::unique_ptr<SymbolSet> Exports = std::make_unique<SymbolSet>();

// Unavailable or obsoleted declarations for a zippered library.
// These are cross referenced against symbols in the dylib.
llvm::StringMap<ZipperedDeclSources> DeferredZipperedSymbols;

// Track current state of verification while traversing AST.
VerifierContext Ctx;

Expand Down
98 changes: 87 additions & 11 deletions clang/lib/InstallAPI/DylibVerifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,13 @@ void DylibVerifier::addSymbol(const Record *R, SymbolContext &SymCtx,

bool DylibVerifier::shouldIgnoreObsolete(const Record *R, SymbolContext &SymCtx,
const Record *DR) {
return SymCtx.FA->Avail.isObsoleted();
if (!SymCtx.FA->Avail.isObsoleted())
return false;

if (Zippered)
DeferredZipperedSymbols[SymCtx.SymbolName].emplace_back(ZipperedDeclSource{
SymCtx.FA, &Ctx.Diag->getSourceManager(), Ctx.Target});
return true;
}

bool DylibVerifier::shouldIgnoreReexport(const Record *R,
Expand All @@ -195,6 +201,28 @@ bool DylibVerifier::shouldIgnoreReexport(const Record *R,
return false;
}

bool DylibVerifier::shouldIgnoreInternalZipperedSymbol(
const Record *R, const SymbolContext &SymCtx) const {
if (!Zippered)
return false;

return Exports->findSymbol(SymCtx.Kind, SymCtx.SymbolName,
SymCtx.ObjCIFKind) != nullptr;
}

bool DylibVerifier::shouldIgnoreZipperedAvailability(const Record *R,
SymbolContext &SymCtx) {
if (!(Zippered && SymCtx.FA->Avail.isUnavailable()))
return false;

// Collect source location incase there is an exported symbol to diagnose
// during `verifyRemainingSymbols`.
DeferredZipperedSymbols[SymCtx.SymbolName].emplace_back(
ZipperedDeclSource{SymCtx.FA, SourceManagers.back().get(), Ctx.Target});

return true;
}

bool DylibVerifier::compareObjCInterfaceSymbols(const Record *R,
SymbolContext &SymCtx,
const ObjCInterfaceRecord *DR) {
Expand Down Expand Up @@ -294,6 +322,9 @@ DylibVerifier::Result DylibVerifier::compareVisibility(const Record *R,
if (shouldIgnorePrivateExternAttr(SymCtx.FA->D))
return Result::Ignore;

if (shouldIgnoreInternalZipperedSymbol(R, SymCtx))
return Result::Ignore;

unsigned ID;
Result Outcome;
if (Mode == VerificationMode::ErrorsAndWarnings) {
Expand Down Expand Up @@ -321,6 +352,9 @@ DylibVerifier::Result DylibVerifier::compareAvailability(const Record *R,
if (!SymCtx.FA->Avail.isUnavailable())
return Result::Valid;

if (shouldIgnoreZipperedAvailability(R, SymCtx))
return Result::Ignore;

const bool IsDeclAvailable = SymCtx.FA->Avail.isUnavailable();

switch (Mode) {
Expand Down Expand Up @@ -588,13 +622,58 @@ void DylibVerifier::visitSymbolInDylib(const Record &R, SymbolContext &SymCtx) {
}
}

const bool IsLinkerSymbol = SymbolName.starts_with("$ld$");

if (R.isVerified()) {
// Check for unavailable symbols.
// This should only occur in the zippered case where we ignored
// availability until all headers have been parsed.
auto It = DeferredZipperedSymbols.find(SymCtx.SymbolName);
if (It == DeferredZipperedSymbols.end()) {
updateState(Result::Valid);
return;
}

ZipperedDeclSources Locs;
for (const ZipperedDeclSource &ZSource : It->second) {
if (ZSource.FA->Avail.isObsoleted()) {
updateState(Result::Ignore);
return;
}
if (ZSource.T.Arch != Ctx.Target.Arch)
continue;
Locs.emplace_back(ZSource);
}
assert(Locs.size() == 2 && "Expected two decls for zippered symbol");

// Print violating declarations per platform.
for (const ZipperedDeclSource &ZSource : Locs) {
unsigned DiagID = 0;
if (Mode == VerificationMode::Pedantic || IsLinkerSymbol) {
updateState(Result::Invalid);
DiagID = diag::err_header_availability_mismatch;
} else if (Mode == VerificationMode::ErrorsAndWarnings) {
updateState(Result::Ignore);
DiagID = diag::warn_header_availability_mismatch;
} else {
updateState(Result::Ignore);
return;
}
// Bypass emitDiag banner and print the target everytime.
Ctx.Diag->setSourceManager(ZSource.SrcMgr);
Ctx.Diag->Report(diag::warn_target) << getTargetTripleName(ZSource.T);
Ctx.Diag->Report(ZSource.FA->Loc, DiagID)
<< getAnnotatedName(&R, SymCtx) << ZSource.FA->Avail.isUnavailable()
<< ZSource.FA->Avail.isUnavailable();
}
return;
}

if (shouldIgnoreCpp(SymbolName, R.isWeakDefined())) {
updateState(Result::Valid);
return;
}

const bool IsLinkerSymbol = SymbolName.starts_with("$ld$");

// All checks at this point classify as some kind of violation.
// The different verification modes dictate whether they are reported to the
// user.
Expand Down Expand Up @@ -647,8 +726,6 @@ void DylibVerifier::visitSymbolInDylib(const Record &R, SymbolContext &SymCtx) {
}

void DylibVerifier::visitGlobal(const GlobalRecord &R) {
if (R.isVerified())
return;
SymbolContext SymCtx;
SimpleSymbol Sym = parseSymbol(R.getName());
SymCtx.SymbolName = Sym.Name;
Expand All @@ -658,8 +735,6 @@ void DylibVerifier::visitGlobal(const GlobalRecord &R) {

void DylibVerifier::visitObjCIVar(const ObjCIVarRecord &R,
const StringRef Super) {
if (R.isVerified())
return;
SymbolContext SymCtx;
SymCtx.SymbolName = ObjCIVarRecord::createScopedName(Super, R.getName());
SymCtx.Kind = EncodeKind::ObjectiveCInstanceVariable;
Expand All @@ -679,8 +754,6 @@ void DylibVerifier::accumulateSrcLocForDylibSymbols() {
}

void DylibVerifier::visitObjCInterface(const ObjCInterfaceRecord &R) {
if (R.isVerified())
return;
SymbolContext SymCtx;
SymCtx.SymbolName = R.getName();
SymCtx.ObjCIFKind = assignObjCIFSymbolKind(&R);
Expand Down Expand Up @@ -713,9 +786,12 @@ DylibVerifier::Result DylibVerifier::verifyRemainingSymbols() {

DWARFContext DWARFInfo;
DWARFCtx = &DWARFInfo;
Ctx.DiscoveredFirstError = false;
Ctx.PrintArch = true;
Ctx.Target = Target(Architecture::AK_unknown, PlatformType::PLATFORM_UNKNOWN);
for (std::shared_ptr<RecordsSlice> Slice : Dylib) {
if (Ctx.Target.Arch == Slice->getTarget().Arch)
continue;
Ctx.DiscoveredFirstError = false;
Ctx.PrintArch = true;
Ctx.Target = Slice->getTarget();
Ctx.DylibSlice = Slice.get();
Slice->visit(*this);
Expand Down
19 changes: 19 additions & 0 deletions clang/test/InstallAPI/Inputs/MacOSX13.0.sdk/SDKSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"DefaultVariant": "macos", "DisplayName": "macOS 13",
"Version": "13.0",
"MaximumDeploymentTarget": "13.0.99",
"PropertyConditionFallbackNames": [], "VersionMap": {
"iOSMac_macOS": {
"16.1": "13.0",
"15.0": "12.0",
"13.1": "10.15",
"14.0": "11.0"
},
"macOS_iOSMac": {
"13.0": "16.1",
"12.0": "15.0",
"11.0": "14.0",
"10.15": "13.1"
}
}
}
Loading