1026 lines
34 KiB
C++
1026 lines
34 KiB
C++
//===- DylibVerifier.cpp ----------------------------------------*- C++--*-===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "clang/InstallAPI/DylibVerifier.h"
|
|
#include "DiagnosticBuilderWrappers.h"
|
|
#include "clang/InstallAPI/FrontendRecords.h"
|
|
#include "clang/InstallAPI/InstallAPIDiagnostic.h"
|
|
#include "llvm/Demangle/Demangle.h"
|
|
#include "llvm/TextAPI/DylibReader.h"
|
|
|
|
using namespace llvm::MachO;
|
|
|
|
namespace clang {
|
|
namespace installapi {
|
|
|
|
ArchitectureSet &LibAttrs::getArchSet(StringRef Attr) {
|
|
auto *It = llvm::find_if(LibraryAttributes, [&Attr](const auto &Input) {
|
|
return Attr == Input.first;
|
|
});
|
|
if (It != LibraryAttributes.end())
|
|
return It->second;
|
|
LibraryAttributes.push_back({Attr.str(), ArchitectureSet()});
|
|
return LibraryAttributes.back().second;
|
|
}
|
|
|
|
std::optional<LibAttrs::Entry> LibAttrs::find(StringRef Attr) const {
|
|
auto *It = llvm::find_if(LibraryAttributes, [&Attr](const auto &Input) {
|
|
return Attr == Input.first;
|
|
});
|
|
if (It == LibraryAttributes.end())
|
|
return std::nullopt;
|
|
return *It;
|
|
}
|
|
|
|
/// Metadata stored about a mapping of a declaration to a symbol.
|
|
struct DylibVerifier::SymbolContext {
|
|
// Name to use for all querying and verification
|
|
// purposes.
|
|
std::string SymbolName{""};
|
|
|
|
// Kind to map symbol type against record.
|
|
EncodeKind Kind = EncodeKind::GlobalSymbol;
|
|
|
|
// Frontend Attributes tied to the AST.
|
|
const FrontendAttrs *FA = nullptr;
|
|
|
|
// The ObjCInterface symbol type, if applicable.
|
|
ObjCIFSymbolKind ObjCIFKind = ObjCIFSymbolKind::None;
|
|
|
|
// Whether Decl is inlined.
|
|
bool Inlined = false;
|
|
};
|
|
|
|
struct DylibVerifier::DWARFContext {
|
|
// Track whether DSYM parsing has already been attempted to avoid re-parsing.
|
|
bool ParsedDSYM{false};
|
|
|
|
// Lookup table for source locations by symbol name.
|
|
DylibReader::SymbolToSourceLocMap SourceLocs{};
|
|
};
|
|
|
|
static bool isCppMangled(StringRef Name) {
|
|
// InstallAPI currently only supports itanium manglings.
|
|
return (Name.starts_with("_Z") || Name.starts_with("__Z") ||
|
|
Name.starts_with("___Z"));
|
|
}
|
|
|
|
static std::string demangle(StringRef Name) {
|
|
// InstallAPI currently only supports itanium manglings.
|
|
if (!isCppMangled(Name))
|
|
return Name.str();
|
|
char *Result = llvm::itaniumDemangle(Name);
|
|
if (!Result)
|
|
return Name.str();
|
|
|
|
std::string Demangled(Result);
|
|
free(Result);
|
|
return Demangled;
|
|
}
|
|
|
|
std::string DylibVerifier::getAnnotatedName(const Record *R,
|
|
SymbolContext &SymCtx,
|
|
bool ValidSourceLoc) {
|
|
assert(!SymCtx.SymbolName.empty() && "Expected symbol name");
|
|
|
|
const StringRef SymbolName = SymCtx.SymbolName;
|
|
std::string PrettyName =
|
|
(Demangle && (SymCtx.Kind == EncodeKind::GlobalSymbol))
|
|
? demangle(SymbolName)
|
|
: SymbolName.str();
|
|
|
|
std::string Annotation;
|
|
if (R->isWeakDefined())
|
|
Annotation += "(weak-def) ";
|
|
if (R->isWeakReferenced())
|
|
Annotation += "(weak-ref) ";
|
|
if (R->isThreadLocalValue())
|
|
Annotation += "(tlv) ";
|
|
|
|
// Check if symbol represents only part of a @interface declaration.
|
|
switch (SymCtx.ObjCIFKind) {
|
|
default:
|
|
break;
|
|
case ObjCIFSymbolKind::EHType:
|
|
return Annotation + "Exception Type of " + PrettyName;
|
|
case ObjCIFSymbolKind::MetaClass:
|
|
return Annotation + "Metaclass of " + PrettyName;
|
|
case ObjCIFSymbolKind::Class:
|
|
return Annotation + "Class of " + PrettyName;
|
|
}
|
|
|
|
// Only print symbol type prefix or leading "_" if there is no source location
|
|
// tied to it. This can only ever happen when the location has to come from
|
|
// debug info.
|
|
if (ValidSourceLoc) {
|
|
StringRef PrettyNameRef(PrettyName);
|
|
if ((SymCtx.Kind == EncodeKind::GlobalSymbol) &&
|
|
!isCppMangled(SymbolName) && PrettyNameRef.starts_with("_"))
|
|
return Annotation + PrettyNameRef.drop_front(1).str();
|
|
return Annotation + PrettyName;
|
|
}
|
|
|
|
switch (SymCtx.Kind) {
|
|
case EncodeKind::GlobalSymbol:
|
|
return Annotation + PrettyName;
|
|
case EncodeKind::ObjectiveCInstanceVariable:
|
|
return Annotation + "(ObjC IVar) " + PrettyName;
|
|
case EncodeKind::ObjectiveCClass:
|
|
return Annotation + "(ObjC Class) " + PrettyName;
|
|
case EncodeKind::ObjectiveCClassEHType:
|
|
return Annotation + "(ObjC Class EH) " + PrettyName;
|
|
}
|
|
|
|
llvm_unreachable("unexpected case for EncodeKind");
|
|
}
|
|
|
|
static DylibVerifier::Result updateResult(const DylibVerifier::Result Prev,
|
|
const DylibVerifier::Result Curr) {
|
|
if (Prev == Curr)
|
|
return Prev;
|
|
|
|
// Never update from invalid or noverify state.
|
|
if ((Prev == DylibVerifier::Result::Invalid) ||
|
|
(Prev == DylibVerifier::Result::NoVerify))
|
|
return Prev;
|
|
|
|
// Don't let an ignored verification remove a valid one.
|
|
if (Prev == DylibVerifier::Result::Valid &&
|
|
Curr == DylibVerifier::Result::Ignore)
|
|
return Prev;
|
|
|
|
return Curr;
|
|
}
|
|
// __private_extern__ is a deprecated specifier that clang does not
|
|
// respect in all contexts, it should just be considered hidden for InstallAPI.
|
|
static bool shouldIgnorePrivateExternAttr(const Decl *D) {
|
|
if (const FunctionDecl *FD = cast<FunctionDecl>(D))
|
|
return FD->getStorageClass() == StorageClass::SC_PrivateExtern;
|
|
if (const VarDecl *VD = cast<VarDecl>(D))
|
|
return VD->getStorageClass() == StorageClass::SC_PrivateExtern;
|
|
|
|
return false;
|
|
}
|
|
|
|
Record *findRecordFromSlice(const RecordsSlice *Slice, StringRef Name,
|
|
EncodeKind Kind) {
|
|
switch (Kind) {
|
|
case EncodeKind::GlobalSymbol:
|
|
return Slice->findGlobal(Name);
|
|
case EncodeKind::ObjectiveCInstanceVariable:
|
|
return Slice->findObjCIVar(Name.contains('.'), Name);
|
|
case EncodeKind::ObjectiveCClass:
|
|
case EncodeKind::ObjectiveCClassEHType:
|
|
return Slice->findObjCInterface(Name);
|
|
}
|
|
llvm_unreachable("unexpected end when finding record");
|
|
}
|
|
|
|
void DylibVerifier::updateState(Result State) {
|
|
Ctx.FrontendState = updateResult(Ctx.FrontendState, State);
|
|
}
|
|
|
|
void DylibVerifier::addSymbol(const Record *R, SymbolContext &SymCtx,
|
|
TargetList &&Targets) {
|
|
if (Targets.empty())
|
|
Targets = {Ctx.Target};
|
|
|
|
Exports->addGlobal(SymCtx.Kind, SymCtx.SymbolName, R->getFlags(), Targets);
|
|
}
|
|
|
|
bool DylibVerifier::shouldIgnoreObsolete(const Record *R, SymbolContext &SymCtx,
|
|
const Record *DR) {
|
|
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,
|
|
SymbolContext &SymCtx) const {
|
|
StringRef SymName = SymCtx.SymbolName;
|
|
// Linker directive symbols can never be ignored.
|
|
if (SymName.starts_with("$ld$"))
|
|
return false;
|
|
|
|
if (Reexports.empty())
|
|
return false;
|
|
|
|
for (const InterfaceFile &Lib : Reexports) {
|
|
if (!Lib.hasTarget(Ctx.Target))
|
|
continue;
|
|
if (auto Sym = Lib.getSymbol(SymCtx.Kind, SymName, SymCtx.ObjCIFKind))
|
|
if ((*Sym)->hasTarget(Ctx.Target))
|
|
return true;
|
|
}
|
|
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) {
|
|
const bool IsDeclVersionComplete =
|
|
((SymCtx.ObjCIFKind & ObjCIFSymbolKind::Class) ==
|
|
ObjCIFSymbolKind::Class) &&
|
|
((SymCtx.ObjCIFKind & ObjCIFSymbolKind::MetaClass) ==
|
|
ObjCIFSymbolKind::MetaClass);
|
|
|
|
const bool IsDylibVersionComplete = DR->isCompleteInterface();
|
|
|
|
// The common case, a complete ObjCInterface.
|
|
if (IsDeclVersionComplete && IsDylibVersionComplete)
|
|
return true;
|
|
|
|
auto PrintDiagnostic = [&](auto SymLinkage, const Record *Record,
|
|
StringRef SymName, bool PrintAsWarning = false) {
|
|
if (SymLinkage == RecordLinkage::Unknown)
|
|
Ctx.emitDiag([&]() {
|
|
Ctx.Diag->Report(SymCtx.FA->Loc, PrintAsWarning
|
|
? diag::warn_library_missing_symbol
|
|
: diag::err_library_missing_symbol)
|
|
<< SymName;
|
|
});
|
|
else
|
|
Ctx.emitDiag([&]() {
|
|
Ctx.Diag->Report(SymCtx.FA->Loc, PrintAsWarning
|
|
? diag::warn_library_hidden_symbol
|
|
: diag::err_library_hidden_symbol)
|
|
<< SymName;
|
|
});
|
|
};
|
|
|
|
if (IsDeclVersionComplete) {
|
|
// The decl represents a complete ObjCInterface, but the symbols in the
|
|
// dylib do not. Determine which symbol is missing. To keep older projects
|
|
// building, treat this as a warning.
|
|
if (!DR->isExportedSymbol(ObjCIFSymbolKind::Class)) {
|
|
SymCtx.ObjCIFKind = ObjCIFSymbolKind::Class;
|
|
PrintDiagnostic(DR->getLinkageForSymbol(ObjCIFSymbolKind::Class), R,
|
|
getAnnotatedName(R, SymCtx),
|
|
/*PrintAsWarning=*/true);
|
|
}
|
|
if (!DR->isExportedSymbol(ObjCIFSymbolKind::MetaClass)) {
|
|
SymCtx.ObjCIFKind = ObjCIFSymbolKind::MetaClass;
|
|
PrintDiagnostic(DR->getLinkageForSymbol(ObjCIFSymbolKind::MetaClass), R,
|
|
getAnnotatedName(R, SymCtx),
|
|
/*PrintAsWarning=*/true);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (DR->isExportedSymbol(SymCtx.ObjCIFKind)) {
|
|
if (!IsDylibVersionComplete) {
|
|
// Both the declaration and dylib have a non-complete interface.
|
|
SymCtx.Kind = EncodeKind::GlobalSymbol;
|
|
SymCtx.SymbolName = R->getName();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// At this point that means there was not a matching class symbol
|
|
// to represent the one discovered as a declaration.
|
|
PrintDiagnostic(DR->getLinkageForSymbol(SymCtx.ObjCIFKind), R,
|
|
SymCtx.SymbolName);
|
|
return false;
|
|
}
|
|
|
|
DylibVerifier::Result DylibVerifier::compareVisibility(const Record *R,
|
|
SymbolContext &SymCtx,
|
|
const Record *DR) {
|
|
|
|
if (R->isExported()) {
|
|
if (!DR) {
|
|
Ctx.emitDiag([&]() {
|
|
Ctx.Diag->Report(SymCtx.FA->Loc, diag::err_library_missing_symbol)
|
|
<< getAnnotatedName(R, SymCtx);
|
|
});
|
|
return Result::Invalid;
|
|
}
|
|
if (DR->isInternal()) {
|
|
Ctx.emitDiag([&]() {
|
|
Ctx.Diag->Report(SymCtx.FA->Loc, diag::err_library_hidden_symbol)
|
|
<< getAnnotatedName(R, SymCtx);
|
|
});
|
|
return Result::Invalid;
|
|
}
|
|
}
|
|
|
|
// Emit a diagnostic for hidden declarations with external symbols, except
|
|
// when theres an inlined attribute.
|
|
if ((R->isInternal() && !SymCtx.Inlined) && DR && DR->isExported()) {
|
|
|
|
if (Mode == VerificationMode::ErrorsOnly)
|
|
return Result::Ignore;
|
|
|
|
if (shouldIgnorePrivateExternAttr(SymCtx.FA->D))
|
|
return Result::Ignore;
|
|
|
|
if (shouldIgnoreInternalZipperedSymbol(R, SymCtx))
|
|
return Result::Ignore;
|
|
|
|
unsigned ID;
|
|
Result Outcome;
|
|
if (Mode == VerificationMode::ErrorsAndWarnings) {
|
|
ID = diag::warn_header_hidden_symbol;
|
|
Outcome = Result::Ignore;
|
|
} else {
|
|
ID = diag::err_header_hidden_symbol;
|
|
Outcome = Result::Invalid;
|
|
}
|
|
Ctx.emitDiag([&]() {
|
|
Ctx.Diag->Report(SymCtx.FA->Loc, ID) << getAnnotatedName(R, SymCtx);
|
|
});
|
|
return Outcome;
|
|
}
|
|
|
|
if (R->isInternal())
|
|
return Result::Ignore;
|
|
|
|
return Result::Valid;
|
|
}
|
|
|
|
DylibVerifier::Result DylibVerifier::compareAvailability(const Record *R,
|
|
SymbolContext &SymCtx,
|
|
const Record *DR) {
|
|
if (!SymCtx.FA->Avail.isUnavailable())
|
|
return Result::Valid;
|
|
|
|
if (shouldIgnoreZipperedAvailability(R, SymCtx))
|
|
return Result::Ignore;
|
|
|
|
const bool IsDeclAvailable = SymCtx.FA->Avail.isUnavailable();
|
|
|
|
switch (Mode) {
|
|
case VerificationMode::ErrorsAndWarnings:
|
|
Ctx.emitDiag([&]() {
|
|
Ctx.Diag->Report(SymCtx.FA->Loc, diag::warn_header_availability_mismatch)
|
|
<< getAnnotatedName(R, SymCtx) << IsDeclAvailable << IsDeclAvailable;
|
|
});
|
|
return Result::Ignore;
|
|
case VerificationMode::Pedantic:
|
|
Ctx.emitDiag([&]() {
|
|
Ctx.Diag->Report(SymCtx.FA->Loc, diag::err_header_availability_mismatch)
|
|
<< getAnnotatedName(R, SymCtx) << IsDeclAvailable << IsDeclAvailable;
|
|
});
|
|
return Result::Invalid;
|
|
case VerificationMode::ErrorsOnly:
|
|
return Result::Ignore;
|
|
case VerificationMode::Invalid:
|
|
llvm_unreachable("Unexpected verification mode symbol verification");
|
|
}
|
|
llvm_unreachable("Unexpected verification mode symbol verification");
|
|
}
|
|
|
|
bool DylibVerifier::compareSymbolFlags(const Record *R, SymbolContext &SymCtx,
|
|
const Record *DR) {
|
|
if (DR->isThreadLocalValue() && !R->isThreadLocalValue()) {
|
|
Ctx.emitDiag([&]() {
|
|
Ctx.Diag->Report(SymCtx.FA->Loc, diag::err_dylib_symbol_flags_mismatch)
|
|
<< getAnnotatedName(DR, SymCtx) << DR->isThreadLocalValue();
|
|
});
|
|
return false;
|
|
}
|
|
if (!DR->isThreadLocalValue() && R->isThreadLocalValue()) {
|
|
Ctx.emitDiag([&]() {
|
|
Ctx.Diag->Report(SymCtx.FA->Loc, diag::err_header_symbol_flags_mismatch)
|
|
<< getAnnotatedName(R, SymCtx) << R->isThreadLocalValue();
|
|
});
|
|
return false;
|
|
}
|
|
|
|
if (DR->isWeakDefined() && !R->isWeakDefined()) {
|
|
Ctx.emitDiag([&]() {
|
|
Ctx.Diag->Report(SymCtx.FA->Loc, diag::err_dylib_symbol_flags_mismatch)
|
|
<< getAnnotatedName(DR, SymCtx) << R->isWeakDefined();
|
|
});
|
|
return false;
|
|
}
|
|
if (!DR->isWeakDefined() && R->isWeakDefined()) {
|
|
Ctx.emitDiag([&]() {
|
|
Ctx.Diag->Report(SymCtx.FA->Loc, diag::err_header_symbol_flags_mismatch)
|
|
<< getAnnotatedName(R, SymCtx) << R->isWeakDefined();
|
|
});
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
DylibVerifier::Result DylibVerifier::verifyImpl(Record *R,
|
|
SymbolContext &SymCtx) {
|
|
R->setVerify();
|
|
if (!canVerify()) {
|
|
// Accumulate symbols when not in verifying against dylib.
|
|
if (R->isExported() && !SymCtx.FA->Avail.isUnavailable() &&
|
|
!SymCtx.FA->Avail.isObsoleted()) {
|
|
addSymbol(R, SymCtx);
|
|
}
|
|
return Ctx.FrontendState;
|
|
}
|
|
|
|
if (shouldIgnoreReexport(R, SymCtx)) {
|
|
updateState(Result::Ignore);
|
|
return Ctx.FrontendState;
|
|
}
|
|
|
|
Record *DR =
|
|
findRecordFromSlice(Ctx.DylibSlice, SymCtx.SymbolName, SymCtx.Kind);
|
|
if (DR)
|
|
DR->setVerify();
|
|
|
|
if (shouldIgnoreObsolete(R, SymCtx, DR)) {
|
|
updateState(Result::Ignore);
|
|
return Ctx.FrontendState;
|
|
}
|
|
|
|
// Unavailable declarations don't need matching symbols.
|
|
if (SymCtx.FA->Avail.isUnavailable() && (!DR || DR->isInternal())) {
|
|
updateState(Result::Valid);
|
|
return Ctx.FrontendState;
|
|
}
|
|
|
|
Result VisibilityCheck = compareVisibility(R, SymCtx, DR);
|
|
if (VisibilityCheck != Result::Valid) {
|
|
updateState(VisibilityCheck);
|
|
return Ctx.FrontendState;
|
|
}
|
|
|
|
// All missing symbol cases to diagnose have been handled now.
|
|
if (!DR) {
|
|
updateState(Result::Ignore);
|
|
return Ctx.FrontendState;
|
|
}
|
|
|
|
// Check for mismatching ObjC interfaces.
|
|
if (SymCtx.ObjCIFKind != ObjCIFSymbolKind::None) {
|
|
if (!compareObjCInterfaceSymbols(
|
|
R, SymCtx, Ctx.DylibSlice->findObjCInterface(DR->getName()))) {
|
|
updateState(Result::Invalid);
|
|
return Ctx.FrontendState;
|
|
}
|
|
}
|
|
|
|
Result AvailabilityCheck = compareAvailability(R, SymCtx, DR);
|
|
if (AvailabilityCheck != Result::Valid) {
|
|
updateState(AvailabilityCheck);
|
|
return Ctx.FrontendState;
|
|
}
|
|
|
|
if (!compareSymbolFlags(R, SymCtx, DR)) {
|
|
updateState(Result::Invalid);
|
|
return Ctx.FrontendState;
|
|
}
|
|
|
|
addSymbol(R, SymCtx);
|
|
updateState(Result::Valid);
|
|
return Ctx.FrontendState;
|
|
}
|
|
|
|
bool DylibVerifier::canVerify() {
|
|
return Ctx.FrontendState != Result::NoVerify;
|
|
}
|
|
|
|
void DylibVerifier::assignSlice(const Target &T) {
|
|
assert(T == Ctx.Target && "Active targets should match.");
|
|
if (Dylib.empty())
|
|
return;
|
|
|
|
// Note: there are no reexport slices with binaries, as opposed to TBD files,
|
|
// so it can be assumed that the target match is the active top-level library.
|
|
auto It = find_if(
|
|
Dylib, [&T](const auto &Slice) { return T == Slice->getTarget(); });
|
|
|
|
assert(It != Dylib.end() && "Target slice should always exist.");
|
|
Ctx.DylibSlice = It->get();
|
|
}
|
|
|
|
void DylibVerifier::setTarget(const Target &T) {
|
|
Ctx.Target = T;
|
|
Ctx.DiscoveredFirstError = false;
|
|
if (Dylib.empty()) {
|
|
updateState(Result::NoVerify);
|
|
return;
|
|
}
|
|
updateState(Result::Ignore);
|
|
assignSlice(T);
|
|
}
|
|
|
|
void DylibVerifier::setSourceManager(
|
|
IntrusiveRefCntPtr<SourceManager> SourceMgr) {
|
|
if (!Ctx.Diag)
|
|
return;
|
|
SourceManagers.push_back(std::move(SourceMgr));
|
|
Ctx.Diag->setSourceManager(SourceManagers.back().get());
|
|
}
|
|
|
|
DylibVerifier::Result DylibVerifier::verify(ObjCIVarRecord *R,
|
|
const FrontendAttrs *FA,
|
|
const StringRef SuperClass) {
|
|
if (R->isVerified())
|
|
return getState();
|
|
|
|
std::string FullName =
|
|
ObjCIVarRecord::createScopedName(SuperClass, R->getName());
|
|
SymbolContext SymCtx{FullName, EncodeKind::ObjectiveCInstanceVariable, FA};
|
|
return verifyImpl(R, SymCtx);
|
|
}
|
|
|
|
static ObjCIFSymbolKind assignObjCIFSymbolKind(const ObjCInterfaceRecord *R) {
|
|
ObjCIFSymbolKind Result = ObjCIFSymbolKind::None;
|
|
if (R->getLinkageForSymbol(ObjCIFSymbolKind::Class) != RecordLinkage::Unknown)
|
|
Result |= ObjCIFSymbolKind::Class;
|
|
if (R->getLinkageForSymbol(ObjCIFSymbolKind::MetaClass) !=
|
|
RecordLinkage::Unknown)
|
|
Result |= ObjCIFSymbolKind::MetaClass;
|
|
if (R->getLinkageForSymbol(ObjCIFSymbolKind::EHType) !=
|
|
RecordLinkage::Unknown)
|
|
Result |= ObjCIFSymbolKind::EHType;
|
|
return Result;
|
|
}
|
|
|
|
DylibVerifier::Result DylibVerifier::verify(ObjCInterfaceRecord *R,
|
|
const FrontendAttrs *FA) {
|
|
if (R->isVerified())
|
|
return getState();
|
|
SymbolContext SymCtx;
|
|
SymCtx.SymbolName = R->getName();
|
|
SymCtx.ObjCIFKind = assignObjCIFSymbolKind(R);
|
|
|
|
SymCtx.Kind = R->hasExceptionAttribute() ? EncodeKind::ObjectiveCClassEHType
|
|
: EncodeKind::ObjectiveCClass;
|
|
SymCtx.FA = FA;
|
|
|
|
return verifyImpl(R, SymCtx);
|
|
}
|
|
|
|
DylibVerifier::Result DylibVerifier::verify(GlobalRecord *R,
|
|
const FrontendAttrs *FA) {
|
|
if (R->isVerified())
|
|
return getState();
|
|
|
|
// Global classifications could be obfusciated with `asm`.
|
|
SimpleSymbol Sym = parseSymbol(R->getName());
|
|
SymbolContext SymCtx;
|
|
SymCtx.SymbolName = Sym.Name;
|
|
SymCtx.Kind = Sym.Kind;
|
|
SymCtx.FA = FA;
|
|
SymCtx.Inlined = R->isInlined();
|
|
return verifyImpl(R, SymCtx);
|
|
}
|
|
|
|
void DylibVerifier::VerifierContext::emitDiag(llvm::function_ref<void()> Report,
|
|
RecordLoc *Loc) {
|
|
if (!DiscoveredFirstError) {
|
|
Diag->Report(diag::warn_target)
|
|
<< (PrintArch ? getArchitectureName(Target.Arch)
|
|
: getTargetTripleName(Target));
|
|
DiscoveredFirstError = true;
|
|
}
|
|
if (Loc && Loc->isValid())
|
|
llvm::errs() << Loc->File << ":" << Loc->Line << ":" << 0 << ": ";
|
|
|
|
Report();
|
|
}
|
|
|
|
// The existence of weak-defined RTTI can not always be inferred from the
|
|
// header files because they can be generated as part of an implementation
|
|
// file.
|
|
// InstallAPI doesn't warn about weak-defined RTTI, because this doesn't affect
|
|
// static linking and so can be ignored for text-api files.
|
|
static bool shouldIgnoreCpp(StringRef Name, bool IsWeakDef) {
|
|
return (IsWeakDef &&
|
|
(Name.starts_with("__ZTI") || Name.starts_with("__ZTS")));
|
|
}
|
|
void DylibVerifier::visitSymbolInDylib(const Record &R, SymbolContext &SymCtx) {
|
|
// Undefined symbols should not be in InstallAPI generated text-api files.
|
|
if (R.isUndefined()) {
|
|
updateState(Result::Valid);
|
|
return;
|
|
}
|
|
|
|
// Internal symbols should not be in InstallAPI generated text-api files.
|
|
if (R.isInternal()) {
|
|
updateState(Result::Valid);
|
|
return;
|
|
}
|
|
|
|
// Allow zippered symbols with potentially mismatching availability
|
|
// between macOS and macCatalyst in the final text-api file.
|
|
const StringRef SymbolName(SymCtx.SymbolName);
|
|
if (const Symbol *Sym = Exports->findSymbol(SymCtx.Kind, SymCtx.SymbolName,
|
|
SymCtx.ObjCIFKind)) {
|
|
if (Sym->hasArchitecture(Ctx.Target.Arch)) {
|
|
updateState(Result::Ignore);
|
|
return;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (Aliases.count({SymbolName.str(), SymCtx.Kind})) {
|
|
updateState(Result::Valid);
|
|
return;
|
|
}
|
|
|
|
// All checks at this point classify as some kind of violation.
|
|
// The different verification modes dictate whether they are reported to the
|
|
// user.
|
|
if (IsLinkerSymbol || (Mode > VerificationMode::ErrorsOnly))
|
|
accumulateSrcLocForDylibSymbols();
|
|
RecordLoc Loc = DWARFCtx->SourceLocs.lookup(SymCtx.SymbolName);
|
|
|
|
// Regardless of verification mode, error out on mismatched special linker
|
|
// symbols.
|
|
if (IsLinkerSymbol) {
|
|
Ctx.emitDiag(
|
|
[&]() {
|
|
Ctx.Diag->Report(diag::err_header_symbol_missing)
|
|
<< getAnnotatedName(&R, SymCtx, Loc.isValid());
|
|
},
|
|
&Loc);
|
|
updateState(Result::Invalid);
|
|
return;
|
|
}
|
|
|
|
// Missing declarations for exported symbols are hard errors on Pedantic mode.
|
|
if (Mode == VerificationMode::Pedantic) {
|
|
Ctx.emitDiag(
|
|
[&]() {
|
|
Ctx.Diag->Report(diag::err_header_symbol_missing)
|
|
<< getAnnotatedName(&R, SymCtx, Loc.isValid());
|
|
},
|
|
&Loc);
|
|
updateState(Result::Invalid);
|
|
return;
|
|
}
|
|
|
|
// Missing declarations for exported symbols are warnings on ErrorsAndWarnings
|
|
// mode.
|
|
if (Mode == VerificationMode::ErrorsAndWarnings) {
|
|
Ctx.emitDiag(
|
|
[&]() {
|
|
Ctx.Diag->Report(diag::warn_header_symbol_missing)
|
|
<< getAnnotatedName(&R, SymCtx, Loc.isValid());
|
|
},
|
|
&Loc);
|
|
updateState(Result::Ignore);
|
|
return;
|
|
}
|
|
|
|
// Missing declarations are dropped for ErrorsOnly mode. It is the last
|
|
// remaining mode.
|
|
updateState(Result::Ignore);
|
|
}
|
|
|
|
void DylibVerifier::visitGlobal(const GlobalRecord &R) {
|
|
SymbolContext SymCtx;
|
|
SimpleSymbol Sym = parseSymbol(R.getName());
|
|
SymCtx.SymbolName = Sym.Name;
|
|
SymCtx.Kind = Sym.Kind;
|
|
visitSymbolInDylib(R, SymCtx);
|
|
}
|
|
|
|
void DylibVerifier::visitObjCIVar(const ObjCIVarRecord &R,
|
|
const StringRef Super) {
|
|
SymbolContext SymCtx;
|
|
SymCtx.SymbolName = ObjCIVarRecord::createScopedName(Super, R.getName());
|
|
SymCtx.Kind = EncodeKind::ObjectiveCInstanceVariable;
|
|
visitSymbolInDylib(R, SymCtx);
|
|
}
|
|
|
|
void DylibVerifier::accumulateSrcLocForDylibSymbols() {
|
|
if (DSYMPath.empty())
|
|
return;
|
|
|
|
assert(DWARFCtx != nullptr && "Expected an initialized DWARFContext");
|
|
if (DWARFCtx->ParsedDSYM)
|
|
return;
|
|
DWARFCtx->ParsedDSYM = true;
|
|
DWARFCtx->SourceLocs =
|
|
DylibReader::accumulateSourceLocFromDSYM(DSYMPath, Ctx.Target);
|
|
}
|
|
|
|
void DylibVerifier::visitObjCInterface(const ObjCInterfaceRecord &R) {
|
|
SymbolContext SymCtx;
|
|
SymCtx.SymbolName = R.getName();
|
|
SymCtx.ObjCIFKind = assignObjCIFSymbolKind(&R);
|
|
if (SymCtx.ObjCIFKind > ObjCIFSymbolKind::EHType) {
|
|
if (R.hasExceptionAttribute()) {
|
|
SymCtx.Kind = EncodeKind::ObjectiveCClassEHType;
|
|
visitSymbolInDylib(R, SymCtx);
|
|
}
|
|
SymCtx.Kind = EncodeKind::ObjectiveCClass;
|
|
visitSymbolInDylib(R, SymCtx);
|
|
} else {
|
|
SymCtx.Kind = R.hasExceptionAttribute() ? EncodeKind::ObjectiveCClassEHType
|
|
: EncodeKind::ObjectiveCClass;
|
|
visitSymbolInDylib(R, SymCtx);
|
|
}
|
|
|
|
for (const ObjCIVarRecord *IV : R.getObjCIVars())
|
|
visitObjCIVar(*IV, R.getName());
|
|
}
|
|
|
|
void DylibVerifier::visitObjCCategory(const ObjCCategoryRecord &R) {
|
|
for (const ObjCIVarRecord *IV : R.getObjCIVars())
|
|
visitObjCIVar(*IV, R.getSuperClassName());
|
|
}
|
|
|
|
DylibVerifier::Result DylibVerifier::verifyRemainingSymbols() {
|
|
if (getState() == Result::NoVerify)
|
|
return Result::NoVerify;
|
|
assert(!Dylib.empty() && "No binary to verify against");
|
|
|
|
DWARFContext DWARFInfo;
|
|
DWARFCtx = &DWARFInfo;
|
|
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);
|
|
}
|
|
return getState();
|
|
}
|
|
|
|
bool DylibVerifier::verifyBinaryAttrs(const ArrayRef<Target> ProvidedTargets,
|
|
const BinaryAttrs &ProvidedBA,
|
|
const LibAttrs &ProvidedReexports,
|
|
const LibAttrs &ProvidedClients,
|
|
const LibAttrs &ProvidedRPaths,
|
|
const FileType &FT) {
|
|
assert(!Dylib.empty() && "Need dylib to verify.");
|
|
|
|
// Pickup any load commands that can differ per slice to compare.
|
|
TargetList DylibTargets;
|
|
LibAttrs DylibReexports;
|
|
LibAttrs DylibClients;
|
|
LibAttrs DylibRPaths;
|
|
for (const std::shared_ptr<RecordsSlice> &RS : Dylib) {
|
|
DylibTargets.push_back(RS->getTarget());
|
|
const BinaryAttrs &BinInfo = RS->getBinaryAttrs();
|
|
for (const StringRef LibName : BinInfo.RexportedLibraries)
|
|
DylibReexports.getArchSet(LibName).set(DylibTargets.back().Arch);
|
|
for (const StringRef LibName : BinInfo.AllowableClients)
|
|
DylibClients.getArchSet(LibName).set(DylibTargets.back().Arch);
|
|
// Compare attributes that are only representable in >= TBD_V5.
|
|
if (FT >= FileType::TBD_V5)
|
|
for (const StringRef Name : BinInfo.RPaths)
|
|
DylibRPaths.getArchSet(Name).set(DylibTargets.back().Arch);
|
|
}
|
|
|
|
// Check targets first.
|
|
ArchitectureSet ProvidedArchs = mapToArchitectureSet(ProvidedTargets);
|
|
ArchitectureSet DylibArchs = mapToArchitectureSet(DylibTargets);
|
|
if (ProvidedArchs != DylibArchs) {
|
|
Ctx.Diag->Report(diag::err_architecture_mismatch)
|
|
<< ProvidedArchs << DylibArchs;
|
|
return false;
|
|
}
|
|
auto ProvidedPlatforms = mapToPlatformVersionSet(ProvidedTargets);
|
|
auto DylibPlatforms = mapToPlatformVersionSet(DylibTargets);
|
|
if (ProvidedPlatforms != DylibPlatforms) {
|
|
const bool DiffMinOS =
|
|
mapToPlatformSet(ProvidedTargets) == mapToPlatformSet(DylibTargets);
|
|
if (DiffMinOS)
|
|
Ctx.Diag->Report(diag::warn_platform_mismatch)
|
|
<< ProvidedPlatforms << DylibPlatforms;
|
|
else {
|
|
Ctx.Diag->Report(diag::err_platform_mismatch)
|
|
<< ProvidedPlatforms << DylibPlatforms;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Because InstallAPI requires certain attributes to match across architecture
|
|
// slices, take the first one to compare those with.
|
|
const BinaryAttrs &DylibBA = (*Dylib.begin())->getBinaryAttrs();
|
|
|
|
if (ProvidedBA.InstallName != DylibBA.InstallName) {
|
|
Ctx.Diag->Report(diag::err_install_name_mismatch)
|
|
<< ProvidedBA.InstallName << DylibBA.InstallName;
|
|
return false;
|
|
}
|
|
|
|
if (ProvidedBA.CurrentVersion != DylibBA.CurrentVersion) {
|
|
Ctx.Diag->Report(diag::err_current_version_mismatch)
|
|
<< ProvidedBA.CurrentVersion << DylibBA.CurrentVersion;
|
|
return false;
|
|
}
|
|
|
|
if (ProvidedBA.CompatVersion != DylibBA.CompatVersion) {
|
|
Ctx.Diag->Report(diag::err_compatibility_version_mismatch)
|
|
<< ProvidedBA.CompatVersion << DylibBA.CompatVersion;
|
|
return false;
|
|
}
|
|
|
|
if (ProvidedBA.AppExtensionSafe != DylibBA.AppExtensionSafe) {
|
|
Ctx.Diag->Report(diag::err_appextension_safe_mismatch)
|
|
<< (ProvidedBA.AppExtensionSafe ? "true" : "false")
|
|
<< (DylibBA.AppExtensionSafe ? "true" : "false");
|
|
return false;
|
|
}
|
|
|
|
if (!DylibBA.TwoLevelNamespace) {
|
|
Ctx.Diag->Report(diag::err_no_twolevel_namespace);
|
|
return false;
|
|
}
|
|
|
|
if (ProvidedBA.OSLibNotForSharedCache != DylibBA.OSLibNotForSharedCache) {
|
|
Ctx.Diag->Report(diag::err_shared_cache_eligiblity_mismatch)
|
|
<< (ProvidedBA.OSLibNotForSharedCache ? "true" : "false")
|
|
<< (DylibBA.OSLibNotForSharedCache ? "true" : "false");
|
|
return false;
|
|
}
|
|
|
|
if (ProvidedBA.ParentUmbrella.empty() && !DylibBA.ParentUmbrella.empty()) {
|
|
Ctx.Diag->Report(diag::err_parent_umbrella_missing)
|
|
<< "installAPI option" << DylibBA.ParentUmbrella;
|
|
return false;
|
|
}
|
|
|
|
if (!ProvidedBA.ParentUmbrella.empty() && DylibBA.ParentUmbrella.empty()) {
|
|
Ctx.Diag->Report(diag::err_parent_umbrella_missing)
|
|
<< "binary file" << ProvidedBA.ParentUmbrella;
|
|
return false;
|
|
}
|
|
|
|
if ((!ProvidedBA.ParentUmbrella.empty()) &&
|
|
(ProvidedBA.ParentUmbrella != DylibBA.ParentUmbrella)) {
|
|
Ctx.Diag->Report(diag::err_parent_umbrella_mismatch)
|
|
<< ProvidedBA.ParentUmbrella << DylibBA.ParentUmbrella;
|
|
return false;
|
|
}
|
|
|
|
auto CompareLibraries = [&](const LibAttrs &Provided, const LibAttrs &Dylib,
|
|
unsigned DiagID_missing, unsigned DiagID_mismatch,
|
|
bool Fatal = true) {
|
|
if (Provided == Dylib)
|
|
return true;
|
|
|
|
for (const LibAttrs::Entry &PEntry : Provided.get()) {
|
|
const auto &[PAttr, PArchSet] = PEntry;
|
|
auto DAttrEntry = Dylib.find(PAttr);
|
|
if (!DAttrEntry) {
|
|
Ctx.Diag->Report(DiagID_missing) << "binary file" << PEntry;
|
|
if (Fatal)
|
|
return false;
|
|
}
|
|
|
|
if (PArchSet != DAttrEntry->second) {
|
|
Ctx.Diag->Report(DiagID_mismatch) << PEntry << *DAttrEntry;
|
|
if (Fatal)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (const LibAttrs::Entry &DEntry : Dylib.get()) {
|
|
const auto &[DAttr, DArchSet] = DEntry;
|
|
const auto &PAttrEntry = Provided.find(DAttr);
|
|
if (!PAttrEntry) {
|
|
Ctx.Diag->Report(DiagID_missing) << "installAPI option" << DEntry;
|
|
if (!Fatal)
|
|
continue;
|
|
return false;
|
|
}
|
|
|
|
if (PAttrEntry->second != DArchSet) {
|
|
if (Fatal)
|
|
llvm_unreachable("this case was already covered above.");
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
if (!CompareLibraries(ProvidedReexports, DylibReexports,
|
|
diag::err_reexported_libraries_missing,
|
|
diag::err_reexported_libraries_mismatch))
|
|
return false;
|
|
|
|
if (!CompareLibraries(ProvidedClients, DylibClients,
|
|
diag::err_allowable_clients_missing,
|
|
diag::err_allowable_clients_mismatch))
|
|
return false;
|
|
|
|
if (FT >= FileType::TBD_V5) {
|
|
// Ignore rpath differences if building an asan variant, since the
|
|
// compiler injects additional paths.
|
|
// FIXME: Building with sanitizers does not always change the install
|
|
// name, so this is not a foolproof solution.
|
|
if (!ProvidedBA.InstallName.ends_with("_asan")) {
|
|
if (!CompareLibraries(ProvidedRPaths, DylibRPaths,
|
|
diag::warn_rpaths_missing,
|
|
diag::warn_rpaths_mismatch,
|
|
/*Fatal=*/false))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::unique_ptr<SymbolSet> DylibVerifier::takeExports() {
|
|
for (const auto &[Alias, Base] : Aliases) {
|
|
TargetList Targets;
|
|
SymbolFlags Flags = SymbolFlags::None;
|
|
if (const Symbol *Sym = Exports->findSymbol(Base.second, Base.first)) {
|
|
Flags = Sym->getFlags();
|
|
Targets = {Sym->targets().begin(), Sym->targets().end()};
|
|
}
|
|
|
|
Record R(Alias.first, RecordLinkage::Exported, Flags);
|
|
SymbolContext SymCtx;
|
|
SymCtx.SymbolName = Alias.first;
|
|
SymCtx.Kind = Alias.second;
|
|
addSymbol(&R, SymCtx, std::move(Targets));
|
|
}
|
|
|
|
return std::move(Exports);
|
|
}
|
|
|
|
} // namespace installapi
|
|
} // namespace clang
|