Skip to content

Commit c24efff

Browse files
authored
[InstallAPI] Handle zippered frameworks (#88205)
A zippered framework is a single framework that can be loaded in both macOS and macatalyst processes. Broadly to InstallAPI, it means the same interface can represent two separate platforms. A dylib's symbol table does not distinguish between macOS/macCatalyst. `InstallAPI` provides the ability for the tbd file to distinct symbols between them. The verifier handles this special logic by tracking all unavailable and obsoleted APIs in this context and checking against those when determining dylib symbols with no matching declaration. * If there exists an available decl for either platform, do not warn. * If there is no available decl, emit a diagnostic and print the source location for both decls.
1 parent 334e07f commit c24efff

File tree

8 files changed

+964
-16
lines changed

8 files changed

+964
-16
lines changed

clang/include/clang/Basic/DiagnosticInstallAPIKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ def warn_no_such_excluded_header_file : Warning<"no such excluded %select{public
2020
def warn_glob_did_not_match: Warning<"glob '%0' did not match any header file">, InGroup<InstallAPIViolation>;
2121
def err_no_such_umbrella_header_file : Error<"%select{public|private|project}1 umbrella header file not found in input: '%0'">;
2222
def err_cannot_find_reexport : Error<"cannot find re-exported %select{framework|library}0: '%1'">;
23+
def err_no_matching_target : Error<"no matching target found for target variant '%0'">;
24+
def err_unsupported_vendor : Error<"vendor '%0' is not supported: '%1'">;
25+
def err_unsupported_environment : Error<"environment '%0' is not supported: '%1'">;
26+
def err_unsupported_os : Error<"os '%0' is not supported: '%1'">;
2327
} // end of command line category.
2428

2529
let CategoryName = "Verification" in {

clang/include/clang/InstallAPI/DylibVerifier.h

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ enum class VerificationMode {
2828
using LibAttrs = llvm::StringMap<ArchitectureSet>;
2929
using ReexportedInterfaces = llvm::SmallVector<llvm::MachO::InterfaceFile, 8>;
3030

31+
// Pointers to information about a zippered declaration used for
32+
// querying and reporting violations against different
33+
// declarations that all map to the same symbol.
34+
struct ZipperedDeclSource {
35+
const FrontendAttrs *FA;
36+
clang::SourceManager *SrcMgr;
37+
Target T;
38+
};
39+
using ZipperedDeclSources = std::vector<ZipperedDeclSource>;
40+
3141
/// Service responsible to tracking state of verification across the
3242
/// lifetime of InstallAPI.
3343
/// As declarations are collected during AST traversal, they are
@@ -68,10 +78,10 @@ class DylibVerifier : llvm::MachO::RecordVisitor {
6878
DylibVerifier() = default;
6979

7080
DylibVerifier(llvm::MachO::Records &&Dylib, ReexportedInterfaces &&Reexports,
71-
DiagnosticsEngine *Diag, VerificationMode Mode, bool Demangle,
72-
StringRef DSYMPath)
81+
DiagnosticsEngine *Diag, VerificationMode Mode, bool Zippered,
82+
bool Demangle, StringRef DSYMPath)
7383
: Dylib(std::move(Dylib)), Reexports(std::move(Reexports)), Mode(Mode),
74-
Demangle(Demangle), DSYMPath(DSYMPath),
84+
Zippered(Zippered), Demangle(Demangle), DSYMPath(DSYMPath),
7585
Exports(std::make_unique<SymbolSet>()), Ctx(VerifierContext{Diag}) {}
7686

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

131+
// Ignore and omit unavailable symbols in zippered libraries.
132+
bool shouldIgnoreZipperedAvailability(const Record *R, SymbolContext &SymCtx);
133+
134+
// Check if an internal declaration in zippered library has an
135+
// external declaration for a different platform. This results
136+
// in the symbol being in a "seperate" platform slice.
137+
bool shouldIgnoreInternalZipperedSymbol(const Record *R,
138+
const SymbolContext &SymCtx) const;
139+
121140
/// Compare the visibility declarations to the linkage of symbol found in
122141
/// dylib.
123142
Result compareVisibility(const Record *R, SymbolContext &SymCtx,
@@ -173,6 +192,9 @@ class DylibVerifier : llvm::MachO::RecordVisitor {
173192
// Controls what class of violations to report.
174193
VerificationMode Mode = VerificationMode::Invalid;
175194

195+
// Library is zippered.
196+
bool Zippered = false;
197+
176198
// Attempt to demangle when reporting violations.
177199
bool Demangle = false;
178200

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

207+
// Unavailable or obsoleted declarations for a zippered library.
208+
// These are cross referenced against symbols in the dylib.
209+
llvm::StringMap<ZipperedDeclSources> DeferredZipperedSymbols;
210+
185211
// Track current state of verification while traversing AST.
186212
VerifierContext Ctx;
187213

clang/lib/InstallAPI/DylibVerifier.cpp

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,13 @@ void DylibVerifier::addSymbol(const Record *R, SymbolContext &SymCtx,
176176

177177
bool DylibVerifier::shouldIgnoreObsolete(const Record *R, SymbolContext &SymCtx,
178178
const Record *DR) {
179-
return SymCtx.FA->Avail.isObsoleted();
179+
if (!SymCtx.FA->Avail.isObsoleted())
180+
return false;
181+
182+
if (Zippered)
183+
DeferredZipperedSymbols[SymCtx.SymbolName].emplace_back(ZipperedDeclSource{
184+
SymCtx.FA, &Ctx.Diag->getSourceManager(), Ctx.Target});
185+
return true;
180186
}
181187

182188
bool DylibVerifier::shouldIgnoreReexport(const Record *R,
@@ -195,6 +201,28 @@ bool DylibVerifier::shouldIgnoreReexport(const Record *R,
195201
return false;
196202
}
197203

204+
bool DylibVerifier::shouldIgnoreInternalZipperedSymbol(
205+
const Record *R, const SymbolContext &SymCtx) const {
206+
if (!Zippered)
207+
return false;
208+
209+
return Exports->findSymbol(SymCtx.Kind, SymCtx.SymbolName,
210+
SymCtx.ObjCIFKind) != nullptr;
211+
}
212+
213+
bool DylibVerifier::shouldIgnoreZipperedAvailability(const Record *R,
214+
SymbolContext &SymCtx) {
215+
if (!(Zippered && SymCtx.FA->Avail.isUnavailable()))
216+
return false;
217+
218+
// Collect source location incase there is an exported symbol to diagnose
219+
// during `verifyRemainingSymbols`.
220+
DeferredZipperedSymbols[SymCtx.SymbolName].emplace_back(
221+
ZipperedDeclSource{SymCtx.FA, SourceManagers.back().get(), Ctx.Target});
222+
223+
return true;
224+
}
225+
198226
bool DylibVerifier::compareObjCInterfaceSymbols(const Record *R,
199227
SymbolContext &SymCtx,
200228
const ObjCInterfaceRecord *DR) {
@@ -294,6 +322,9 @@ DylibVerifier::Result DylibVerifier::compareVisibility(const Record *R,
294322
if (shouldIgnorePrivateExternAttr(SymCtx.FA->D))
295323
return Result::Ignore;
296324

325+
if (shouldIgnoreInternalZipperedSymbol(R, SymCtx))
326+
return Result::Ignore;
327+
297328
unsigned ID;
298329
Result Outcome;
299330
if (Mode == VerificationMode::ErrorsAndWarnings) {
@@ -321,6 +352,9 @@ DylibVerifier::Result DylibVerifier::compareAvailability(const Record *R,
321352
if (!SymCtx.FA->Avail.isUnavailable())
322353
return Result::Valid;
323354

355+
if (shouldIgnoreZipperedAvailability(R, SymCtx))
356+
return Result::Ignore;
357+
324358
const bool IsDeclAvailable = SymCtx.FA->Avail.isUnavailable();
325359

326360
switch (Mode) {
@@ -588,13 +622,58 @@ void DylibVerifier::visitSymbolInDylib(const Record &R, SymbolContext &SymCtx) {
588622
}
589623
}
590624

625+
const bool IsLinkerSymbol = SymbolName.starts_with("$ld$");
626+
627+
if (R.isVerified()) {
628+
// Check for unavailable symbols.
629+
// This should only occur in the zippered case where we ignored
630+
// availability until all headers have been parsed.
631+
auto It = DeferredZipperedSymbols.find(SymCtx.SymbolName);
632+
if (It == DeferredZipperedSymbols.end()) {
633+
updateState(Result::Valid);
634+
return;
635+
}
636+
637+
ZipperedDeclSources Locs;
638+
for (const ZipperedDeclSource &ZSource : It->second) {
639+
if (ZSource.FA->Avail.isObsoleted()) {
640+
updateState(Result::Ignore);
641+
return;
642+
}
643+
if (ZSource.T.Arch != Ctx.Target.Arch)
644+
continue;
645+
Locs.emplace_back(ZSource);
646+
}
647+
assert(Locs.size() == 2 && "Expected two decls for zippered symbol");
648+
649+
// Print violating declarations per platform.
650+
for (const ZipperedDeclSource &ZSource : Locs) {
651+
unsigned DiagID = 0;
652+
if (Mode == VerificationMode::Pedantic || IsLinkerSymbol) {
653+
updateState(Result::Invalid);
654+
DiagID = diag::err_header_availability_mismatch;
655+
} else if (Mode == VerificationMode::ErrorsAndWarnings) {
656+
updateState(Result::Ignore);
657+
DiagID = diag::warn_header_availability_mismatch;
658+
} else {
659+
updateState(Result::Ignore);
660+
return;
661+
}
662+
// Bypass emitDiag banner and print the target everytime.
663+
Ctx.Diag->setSourceManager(ZSource.SrcMgr);
664+
Ctx.Diag->Report(diag::warn_target) << getTargetTripleName(ZSource.T);
665+
Ctx.Diag->Report(ZSource.FA->Loc, DiagID)
666+
<< getAnnotatedName(&R, SymCtx) << ZSource.FA->Avail.isUnavailable()
667+
<< ZSource.FA->Avail.isUnavailable();
668+
}
669+
return;
670+
}
671+
591672
if (shouldIgnoreCpp(SymbolName, R.isWeakDefined())) {
592673
updateState(Result::Valid);
593674
return;
594675
}
595676

596-
const bool IsLinkerSymbol = SymbolName.starts_with("$ld$");
597-
598677
// All checks at this point classify as some kind of violation.
599678
// The different verification modes dictate whether they are reported to the
600679
// user.
@@ -647,8 +726,6 @@ void DylibVerifier::visitSymbolInDylib(const Record &R, SymbolContext &SymCtx) {
647726
}
648727

649728
void DylibVerifier::visitGlobal(const GlobalRecord &R) {
650-
if (R.isVerified())
651-
return;
652729
SymbolContext SymCtx;
653730
SimpleSymbol Sym = parseSymbol(R.getName());
654731
SymCtx.SymbolName = Sym.Name;
@@ -658,8 +735,6 @@ void DylibVerifier::visitGlobal(const GlobalRecord &R) {
658735

659736
void DylibVerifier::visitObjCIVar(const ObjCIVarRecord &R,
660737
const StringRef Super) {
661-
if (R.isVerified())
662-
return;
663738
SymbolContext SymCtx;
664739
SymCtx.SymbolName = ObjCIVarRecord::createScopedName(Super, R.getName());
665740
SymCtx.Kind = EncodeKind::ObjectiveCInstanceVariable;
@@ -679,8 +754,6 @@ void DylibVerifier::accumulateSrcLocForDylibSymbols() {
679754
}
680755

681756
void DylibVerifier::visitObjCInterface(const ObjCInterfaceRecord &R) {
682-
if (R.isVerified())
683-
return;
684757
SymbolContext SymCtx;
685758
SymCtx.SymbolName = R.getName();
686759
SymCtx.ObjCIFKind = assignObjCIFSymbolKind(&R);
@@ -713,9 +786,12 @@ DylibVerifier::Result DylibVerifier::verifyRemainingSymbols() {
713786

714787
DWARFContext DWARFInfo;
715788
DWARFCtx = &DWARFInfo;
716-
Ctx.DiscoveredFirstError = false;
717-
Ctx.PrintArch = true;
789+
Ctx.Target = Target(Architecture::AK_unknown, PlatformType::PLATFORM_UNKNOWN);
718790
for (std::shared_ptr<RecordsSlice> Slice : Dylib) {
791+
if (Ctx.Target.Arch == Slice->getTarget().Arch)
792+
continue;
793+
Ctx.DiscoveredFirstError = false;
794+
Ctx.PrintArch = true;
719795
Ctx.Target = Slice->getTarget();
720796
Ctx.DylibSlice = Slice.get();
721797
Slice->visit(*this);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"DefaultVariant": "macos", "DisplayName": "macOS 13",
3+
"Version": "13.0",
4+
"MaximumDeploymentTarget": "13.0.99",
5+
"PropertyConditionFallbackNames": [], "VersionMap": {
6+
"iOSMac_macOS": {
7+
"16.1": "13.0",
8+
"15.0": "12.0",
9+
"13.1": "10.15",
10+
"14.0": "11.0"
11+
},
12+
"macOS_iOSMac": {
13+
"13.0": "16.1",
14+
"12.0": "15.0",
15+
"11.0": "14.0",
16+
"10.15": "13.1"
17+
}
18+
}
19+
}

0 commit comments

Comments
 (0)