[InstallAPI] Verify that declarations in headers map to exports found in dylib (#85348)

* This completes support for verifying every declaration found in a
header is discovered in the dylib. Diagnostics are reported for each
class for differences that are representable in TBD files.

* This patch also now captures unavailable attributes that depend on
target triples. This is needed for proper tbd file generation.
This commit is contained in:
Cyndy Ishida 2024-03-19 18:36:46 -07:00 committed by GitHub
parent de159aeca9
commit 936519f25c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 1782 additions and 26 deletions

View File

@ -67,6 +67,7 @@ struct AvailabilityInfo {
VersionTuple Introduced;
VersionTuple Deprecated;
VersionTuple Obsoleted;
bool Unavailable = false;
bool UnconditionallyDeprecated = false;
bool UnconditionallyUnavailable = false;
@ -78,6 +79,12 @@ struct AvailabilityInfo {
/// Check if the symbol has been obsoleted.
bool isObsoleted() const { return !Obsoleted.empty(); }
/// Check if the symbol is unavailable unconditionally or
/// on the active platform and os version.
bool isUnavailable() const {
return Unavailable || isUnconditionallyUnavailable();
}
/// Check if the symbol is unconditionally deprecated.
///
/// i.e. \code __attribute__((deprecated)) \endcode
@ -91,9 +98,10 @@ struct AvailabilityInfo {
}
AvailabilityInfo(StringRef Domain, VersionTuple I, VersionTuple D,
VersionTuple O, bool UD, bool UU)
VersionTuple O, bool U, bool UD, bool UU)
: Domain(Domain), Introduced(I), Deprecated(D), Obsoleted(O),
UnconditionallyDeprecated(UD), UnconditionallyUnavailable(UU) {}
Unavailable(U), UnconditionallyDeprecated(UD),
UnconditionallyUnavailable(UU) {}
friend bool operator==(const AvailabilityInfo &Lhs,
const AvailabilityInfo &Rhs);
@ -105,10 +113,10 @@ public:
inline bool operator==(const AvailabilityInfo &Lhs,
const AvailabilityInfo &Rhs) {
return std::tie(Lhs.Introduced, Lhs.Deprecated, Lhs.Obsoleted,
Lhs.UnconditionallyDeprecated,
Lhs.Unavailable, Lhs.UnconditionallyDeprecated,
Lhs.UnconditionallyUnavailable) ==
std::tie(Rhs.Introduced, Rhs.Deprecated, Rhs.Obsoleted,
Rhs.UnconditionallyDeprecated,
Rhs.Unavailable, Rhs.UnconditionallyDeprecated,
Rhs.UnconditionallyUnavailable);
}

View File

@ -1508,3 +1508,7 @@ def ReadOnlyPlacementChecks : DiagGroup<"read-only-types">;
// Warnings and fixes to support the "safe buffers" programming model.
def UnsafeBufferUsageInContainer : DiagGroup<"unsafe-buffer-usage-in-container">;
def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer]>;
// Warnings and notes InstallAPI verification.
def InstallAPIViolation : DiagGroup<"installapi-violation">;

View File

@ -17,4 +17,27 @@ def err_no_install_name : Error<"no install name specified: add -install_name <p
def err_no_output_file: Error<"no output file specified">;
} // end of command line category.
let CategoryName = "Verification" in {
def warn_target: Warning<"violations found for %0">, InGroup<InstallAPIViolation>;
def err_library_missing_symbol : Error<"declaration has external linkage, but dynamic library doesn't have symbol '%0'">;
def warn_library_missing_symbol : Warning<"declaration has external linkage, but dynamic library doesn't have symbol '%0'">, InGroup<InstallAPIViolation>;
def err_library_hidden_symbol : Error<"declaration has external linkage, but symbol has internal linkage in dynamic library '%0'">;
def warn_library_hidden_symbol : Warning<"declaration has external linkage, but symbol has internal linkage in dynamic library '%0'">, InGroup<InstallAPIViolation>;
def warn_header_hidden_symbol : Warning<"symbol exported in dynamic library, but marked hidden in declaration '%0'">, InGroup<InstallAPIViolation>;
def err_header_hidden_symbol : Error<"symbol exported in dynamic library, but marked hidden in declaration '%0'">;
def err_header_symbol_missing : Error<"no declaration found for exported symbol '%0' in dynamic library">;
def warn_header_availability_mismatch : Warning<"declaration '%0' is marked %select{available|unavailable}1,"
" but symbol is %select{not |}2exported in dynamic library">, InGroup<InstallAPIViolation>;
def err_header_availability_mismatch : Error<"declaration '%0' is marked %select{available|unavailable}1,"
" but symbol is %select{not |}2exported in dynamic library">;
def warn_dylib_symbol_flags_mismatch : Warning<"dynamic library symbol '%0' is "
"%select{weak defined|thread local}1, but its declaration is not">, InGroup<InstallAPIViolation>;
def warn_header_symbol_flags_mismatch : Warning<"declaration '%0' is "
"%select{weak defined|thread local}1, but symbol is not in dynamic library">, InGroup<InstallAPIViolation>;
def err_dylib_symbol_flags_mismatch : Error<"dynamic library symbol '%0' is "
"%select{weak defined|thread local}1, but its declaration is not">;
def err_header_symbol_flags_mismatch : Error<"declaration '%0' is "
"%select{weak defined|thread local}1, but symbol is not in dynamic library">;
} // end of Verification category.
} // end of InstallAPI component

View File

@ -38,19 +38,34 @@ public:
// Current target being verified against the AST.
llvm::MachO::Target Target;
// Target specific API from binary.
RecordsSlice *DylibSlice = nullptr;
// Query state of verification after AST has been traversed.
Result FrontendState;
Result FrontendState = Result::Ignore;
// First error for AST traversal, which is tied to the target triple.
bool DiscoveredFirstError;
bool DiscoveredFirstError = false;
// Determines what kind of banner to print a violation for.
bool PrintArch = false;
// Engine for reporting violations.
DiagnosticsEngine *Diag = nullptr;
// Handle diagnostics reporting for target level violations.
void emitDiag(llvm::function_ref<void()> Report);
VerifierContext() = default;
VerifierContext(DiagnosticsEngine *Diag) : Diag(Diag) {}
};
DylibVerifier() = default;
DylibVerifier(llvm::MachO::Records &&Dylib, DiagnosticsEngine *Diag,
VerificationMode Mode, bool Demangle)
: Dylib(std::move(Dylib)), Diag(Diag), Mode(Mode), Demangle(Demangle),
Exports(std::make_unique<SymbolSet>()) {}
: Dylib(std::move(Dylib)), Mode(Mode), Demangle(Demangle),
Exports(std::make_unique<SymbolSet>()), Ctx(VerifierContext{Diag}) {}
Result verify(GlobalRecord *R, const FrontendAttrs *FA);
Result verify(ObjCInterfaceRecord *R, const FrontendAttrs *FA);
@ -66,6 +81,13 @@ public:
/// Get result of verification.
Result getState() const { return Ctx.FrontendState; }
/// Set different source managers to the same diagnostics engine.
void setSourceManager(SourceManager &SourceMgr) const {
if (!Ctx.Diag)
return;
Ctx.Diag->setSourceManager(&SourceMgr);
}
private:
/// Determine whether to compare declaration to symbol in binary.
bool canVerify();
@ -73,6 +95,29 @@ private:
/// Shared implementation for verifying exported symbols.
Result verifyImpl(Record *R, SymbolContext &SymCtx);
/// Check if declaration is marked as obsolete, they are
// expected to result in a symbol mismatch.
bool shouldIgnoreObsolete(const Record *R, SymbolContext &SymCtx,
const Record *DR);
/// Compare the visibility declarations to the linkage of symbol found in
/// dylib.
Result compareVisibility(const Record *R, SymbolContext &SymCtx,
const Record *DR);
/// An ObjCInterfaceRecord can represent up to three symbols. When verifying,
// account for this granularity.
bool compareObjCInterfaceSymbols(const Record *R, SymbolContext &SymCtx,
const ObjCInterfaceRecord *DR);
/// Validate availability annotations against dylib.
Result compareAvailability(const Record *R, SymbolContext &SymCtx,
const Record *DR);
/// Compare and validate matching symbol flags.
bool compareSymbolFlags(const Record *R, SymbolContext &SymCtx,
const Record *DR);
/// Update result state on each call to `verify`.
void updateState(Result State);
@ -80,14 +125,14 @@ private:
void addSymbol(const Record *R, SymbolContext &SymCtx,
TargetList &&Targets = {});
/// Find matching dylib slice for target triple that is being parsed.
void assignSlice(const Target &T);
// Symbols in dylib.
llvm::MachO::Records Dylib;
// Engine for reporting violations.
[[maybe_unused]] DiagnosticsEngine *Diag = nullptr;
// Controls what class of violations to report.
[[maybe_unused]] VerificationMode Mode = VerificationMode::Invalid;
VerificationMode Mode = VerificationMode::Invalid;
// Attempt to demangle when reporting violations.
bool Demangle = false;

View File

@ -17,6 +17,7 @@
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/InstallAPI/Context.h"
#include "clang/InstallAPI/DylibVerifier.h"
#include "clang/InstallAPI/Visitor.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/MemoryBuffer.h"
@ -34,6 +35,8 @@ public:
std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
StringRef InFile) override {
Ctx.Diags->getClient()->BeginSourceFile(CI.getLangOpts());
Ctx.Verifier->setSourceManager(CI.getSourceManager());
return std::make_unique<InstallAPIVisitor>(
CI.getASTContext(), Ctx, CI.getSourceManager(), CI.getPreprocessor());
}

View File

@ -32,6 +32,7 @@ using ObjCInterfaceRecord = llvm::MachO::ObjCInterfaceRecord;
using ObjCCategoryRecord = llvm::MachO::ObjCCategoryRecord;
using ObjCIVarRecord = llvm::MachO::ObjCIVarRecord;
using Records = llvm::MachO::Records;
using RecordsSlice = llvm::MachO::RecordsSlice;
using BinaryAttrs = llvm::MachO::RecordsSlice::BinaryAttrs;
using SymbolSet = llvm::MachO::SymbolSet;
using SimpleSymbol = llvm::MachO::SimpleSymbol;

View File

@ -28,9 +28,9 @@ AvailabilityInfo AvailabilityInfo::createFromDecl(const Decl *Decl) {
for (const auto *A : RD->specific_attrs<AvailabilityAttr>()) {
if (A->getPlatform()->getName() != PlatformName)
continue;
Availability =
AvailabilityInfo(A->getPlatform()->getName(), A->getIntroduced(),
A->getDeprecated(), A->getObsoleted(), false, false);
Availability = AvailabilityInfo(
A->getPlatform()->getName(), A->getIntroduced(), A->getDeprecated(),
A->getObsoleted(), A->getUnavailable(), false, false);
break;
}

View File

@ -1,5 +1,6 @@
#include "clang/InstallAPI/DylibVerifier.h"
#include "clang/InstallAPI/FrontendRecords.h"
#include "clang/InstallAPI/InstallAPIDiagnostic.h"
#include "llvm/Demangle/Demangle.h"
using namespace llvm::MachO;
@ -24,6 +25,9 @@ struct DylibVerifier::SymbolContext {
// The ObjCInterface symbol type, if applicable.
ObjCIFSymbolKind ObjCIFKind = ObjCIFSymbolKind::None;
// Whether Decl is inlined.
bool Inlined = false;
};
static std::string
@ -80,10 +84,11 @@ getAnnotatedName(const Record *R, EncodeKind Kind, StringRef Name,
}
static std::string demangle(StringRef Name) {
// Itanium encoding requires 1 or 3 leading underscores, followed by 'Z'.
if (!(Name.starts_with("_Z") || Name.starts_with("___Z")))
// InstallAPI currently only supports itanium manglings.
if (!(Name.starts_with("_Z") || Name.starts_with("__Z") ||
Name.starts_with("___Z")))
return Name.str();
char *Result = llvm::itaniumDemangle(Name.data());
char *Result = llvm::itaniumDemangle(Name);
if (!Result)
return Name.str();
@ -109,6 +114,30 @@ static DylibVerifier::Result updateResult(const DylibVerifier::Result 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);
@ -122,17 +151,272 @@ void DylibVerifier::addSymbol(const Record *R, SymbolContext &SymCtx,
Exports->addGlobal(SymCtx.Kind, SymCtx.SymbolName, R->getFlags(), Targets);
}
bool DylibVerifier::shouldIgnoreObsolete(const Record *R, SymbolContext &SymCtx,
const Record *DR) {
return SymCtx.FA->Avail.isObsoleted();
}
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->D->getLocation(),
PrintAsWarning ? diag::warn_library_missing_symbol
: diag::err_library_missing_symbol)
<< SymName;
});
else
Ctx.emitDiag([&]() {
Ctx.Diag->Report(SymCtx.FA->D->getLocation(),
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))
PrintDiagnostic(DR->getLinkageForSymbol(ObjCIFSymbolKind::Class), R,
getAnnotatedName(R, SymCtx.Kind, SymCtx.PrettyPrintName,
/*ValidSourceLoc=*/true,
ObjCIFSymbolKind::Class),
/*PrintAsWarning=*/true);
if (!DR->isExportedSymbol(ObjCIFSymbolKind::MetaClass))
PrintDiagnostic(DR->getLinkageForSymbol(ObjCIFSymbolKind::MetaClass), R,
getAnnotatedName(R, SymCtx.Kind, SymCtx.PrettyPrintName,
/*ValidSourceLoc=*/true,
ObjCIFSymbolKind::MetaClass),
/*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.PrettyPrintName);
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->D->getLocation(),
diag::err_library_missing_symbol)
<< SymCtx.PrettyPrintName;
});
return Result::Invalid;
}
if (DR->isInternal()) {
Ctx.emitDiag([&]() {
Ctx.Diag->Report(SymCtx.FA->D->getLocation(),
diag::err_library_hidden_symbol)
<< SymCtx.PrettyPrintName;
});
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;
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->D->getLocation(), ID)
<< SymCtx.PrettyPrintName;
});
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;
const bool IsDeclAvailable = SymCtx.FA->Avail.isUnavailable();
switch (Mode) {
case VerificationMode::ErrorsAndWarnings:
Ctx.emitDiag([&]() {
Ctx.Diag->Report(SymCtx.FA->D->getLocation(),
diag::warn_header_availability_mismatch)
<< SymCtx.PrettyPrintName << IsDeclAvailable << IsDeclAvailable;
});
return Result::Ignore;
case VerificationMode::Pedantic:
Ctx.emitDiag([&]() {
Ctx.Diag->Report(SymCtx.FA->D->getLocation(),
diag::err_header_availability_mismatch)
<< SymCtx.PrettyPrintName << 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) {
std::string DisplayName =
Demangle ? demangle(DR->getName()) : DR->getName().str();
if (DR->isThreadLocalValue() && !R->isThreadLocalValue()) {
Ctx.emitDiag([&]() {
Ctx.Diag->Report(SymCtx.FA->D->getLocation(),
diag::err_dylib_symbol_flags_mismatch)
<< getAnnotatedName(DR, SymCtx.Kind, DisplayName)
<< DR->isThreadLocalValue();
});
return false;
}
if (!DR->isThreadLocalValue() && R->isThreadLocalValue()) {
Ctx.emitDiag([&]() {
SymCtx.FA->D->getLocation(),
Ctx.Diag->Report(diag::err_header_symbol_flags_mismatch)
<< SymCtx.PrettyPrintName << R->isThreadLocalValue();
});
return false;
}
if (DR->isWeakDefined() && !R->isWeakDefined()) {
Ctx.emitDiag([&]() {
Ctx.Diag->Report(SymCtx.FA->D->getLocation(),
diag::err_dylib_symbol_flags_mismatch)
<< getAnnotatedName(DR, SymCtx.Kind, DisplayName)
<< R->isWeakDefined();
});
return false;
}
if (!DR->isWeakDefined() && R->isWeakDefined()) {
Ctx.emitDiag([&]() {
Ctx.Diag->Report(SymCtx.FA->D->getLocation(),
diag::err_header_symbol_flags_mismatch)
<< SymCtx.PrettyPrintName << 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.isUnconditionallyUnavailable() &&
if (R->isExported() && !SymCtx.FA->Avail.isUnavailable() &&
!SymCtx.FA->Avail.isObsoleted()) {
addSymbol(R, SymCtx);
}
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;
}
@ -140,10 +424,29 @@ 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;
updateState(Dylib.empty() ? Result::NoVerify : Result::Ignore);
if (Dylib.empty()) {
updateState(Result::NoVerify);
return;
}
updateState(Result::Ignore);
assignSlice(T);
}
DylibVerifier::Result DylibVerifier::verify(ObjCIVarRecord *R,
@ -205,8 +508,21 @@ DylibVerifier::Result DylibVerifier::verify(GlobalRecord *R,
getAnnotatedName(R, Sym.Kind, Demangle ? demangle(Sym.Name) : 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) {
if (!DiscoveredFirstError) {
Diag->Report(diag::warn_target)
<< (PrintArch ? getArchitectureName(Target.Arch)
: getTargetTripleName(Target));
DiscoveredFirstError = true;
}
Report();
}
} // namespace installapi
} // namespace clang

View File

@ -681,7 +681,7 @@ bool InstallAPIVisitor::VisitCXXRecordDecl(const CXXRecordDecl *D) {
std::string Name = getMangledName(M);
auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported,
GlobalRecord::Kind::Function, Avail, D,
GlobalRecord::Kind::Function, Avail, M,
*Access, getFlags(WeakDef));
Ctx.Verifier->verify(GR, FA);
}

View File

@ -0,0 +1,631 @@
; RUN: rm -rf %t
; RUN: split-file %s %t
; RUN: sed -e "s|DSTROOT|%/t|g" %t/inputs.json.in > %t/inputs.json
; RUN: yaml2obj %t/Availability.yaml -o %t/System/Library/Frameworks/Availability.framework/Availability
; RUN: clang-installapi \
; RUN: --target=x86_64-apple-macos13 \
; RUN: -install_name /System/Library/Frameworks/Availability.framework/Versions/A/Availability \
; RUN: -current_version 1 -compatibility_version 1 \
; RUN: -F %t/System/Library/Frameworks \
; RUN: %t/inputs.json -o %t/output.tbd \
; RUN: --verify-against=%t/System/Library/Frameworks/Availability.framework/Availability \
; RUN: --verify-mode=ErrorsOnly --filetype=tbd-v5 2> %t/errors.log
; RUN: FileCheck -allow-empty -check-prefix=ERRORSONLY -input-file %t/errors.log %s
; RUN: clang-installapi \
; RUN: --target=x86_64-apple-macos13 \
; RUN: -install_name /System/Library/Frameworks/Availability.framework/Versions/A/Availability \
; RUN: -current_version 1 -compatibility_version 1 \
; RUN: -F %t/System/Library/Frameworks \
; RUN: %t/inputs.json -o %t/output-warnings.tbd \
; RUN: --verify-against=%t/System/Library/Frameworks/Availability.framework/Availability \
; RUN: --verify-mode=ErrorsAndWarnings 2> %t/errors.log
; RUN: FileCheck -check-prefixes=VIOLATIONS,ERRORSANDWARNINGS -input-file %t/errors.log %s
; RUN: not clang-installapi \
; RUN: --target=x86_64-apple-macos13 \
; RUN: -install_name /System/Library/Frameworks/Availability.framework/Versions/A/Availability \
; RUN: -current_version 1 -compatibility_version 1 \
; RUN: -F %t/System/Library/Frameworks \
; RUN: %t/inputs.json -o %t/output-pedantic.tbd \
; RUN: --verify-against=%t/System/Library/Frameworks/Availability.framework/Availability \
; RUN: --verify-mode=Pedantic 2> %t/errors.log
; RUN: FileCheck -check-prefixes=VIOLATIONS,PEDANTIC -input-file %t/errors.log %s
; ERRORSONLY-NOT: error
; ERRORSONLY-NOT: warning
; ERRORSANDWARNINGS-NOT: error
; VIOLATIONS: warning: violations found for x86_64-apple-macos
; VIOLATIONS: declaration 'publicGlobalVariable' is marked unavailable, but symbol is exported in dynamic library
; VIOLATIONS-NEXT: extern int publicGlobalVariable NS_AVAILABLE
; VIOLATIONS: declaration 'Foo' is marked unavailable, but symbol is exported in dynamic library
; VIOLATIONS-NEXT: @interface Foo : NSObject
; VIOLATIONS: declaration 'publicGlobalVariable3' is marked unavailable, but symbol is exported in dynamic library
; VIOLATIONS-NEXT: extern int publicGlobalVariable3 __attribute__((unavailable))
; VIOLATIONS: declaration 'privateGlobalVariable' is marked unavailable, but symbol is exported in dynamic library
; VIOLATIONS-NEXT: extern int privateGlobalVariable;
; ERRORSANDWARNINGS-NOT: warning
; PEDANTIC-NOT: error
;--- inputs.json.in
{
"headers": [ {
"path" : "DSTROOT/System/Library/Frameworks/Availability.framework/Headers/Availability.h",
"type" : "public"
},
{
"path" : "DSTROOT/System/Library/Frameworks/Availability.framework/PrivateHeaders/AvailabilityPrivate.h",
"type" : "private"
}
],
"version": "3"
}
;--- System/Library/Frameworks/Availability.framework/Headers/AV_Defines.h
#ifndef AV_DEFINES
#define AV_DEFINES
#define NS_AVAILABLE __attribute__((availability(macosx,introduced=NA)))
@interface NSObject
@end
#endif //AV_DEFINES
;--- System/Library/Frameworks/Availability.framework/PrivateHeaders/AvailabilityPrivate.h
#import <Availability/AV_Defines.h>
// Test private global variable.
NS_AVAILABLE
extern int privateGlobalVariable;
;--- System/Library/Frameworks/Availability.framework/Headers/Availability.h
#import <Availability/AV_Defines.h>
extern int publicGlobalVariable NS_AVAILABLE;
// Test public ObjC class
NS_AVAILABLE
@interface Foo : NSObject
@end
// Test unavailable attribute.
#ifdef __i386__
#define UNAVAILABLE_I386 __attribute__((unavailable))
#else
#define UNAVAILABLE_I386
#endif
extern int publicGlobalVariable2 UNAVAILABLE_I386;
extern int publicGlobalVariable3 __attribute__((unavailable))
__attribute__((availability(macosx, introduced = 10.9)));
// Test obsoleted with exported variable.
extern int publicGlobalVariable4 __attribute__((availability(
macosx, introduced = 10.9, deprecated = 10.10, obsoleted = 10.11)));
// Test obsoleted with non-existent variable.
extern int publicGlobalVariable5 __attribute__((availability(
macosx, introduced = 10.9, deprecated = 10.10, obsoleted = 10.11)));
#ifdef __i386__
#define OBSOLETE_I386 __attribute__((availability(macosx, obsoleted = 10.11)))
#else
#define OBSOLETE_I386
#endif
extern int publicGlobalVariable6 OBSOLETE_I386;
/// Created from:
// int publicGlobalVariable; int privateGlobalVariable;
//
// @implementation Foo
// @end
//
// #ifndef __i386__
// int publicGlobalVariable2;
// #endif
//
// int publicGlobalVariable3;
// int publicGlobalVariable4;
//
// #ifndef __i386__
// int publicGlobalVariable6;
// #endif
;--- Availability.yaml
--- !mach-o
FileHeader:
magic: 0xFEEDFACF
cputype: 0x1000007
cpusubtype: 0x3
filetype: 0x6
ncmds: 14
sizeofcmds: 1312
flags: 0x100085
reserved: 0x0
LoadCommands:
- cmd: LC_SEGMENT_64
cmdsize: 232
segname: __TEXT
vmaddr: 0
vmsize: 8192
fileoff: 0
filesize: 8192
maxprot: 5
initprot: 5
nsects: 2
flags: 0
Sections:
- sectname: __text
segname: __TEXT
addr: 0x1140
size: 0
offset: 0x1140
align: 0
reloff: 0x0
nreloc: 0
flags: 0x80000000
reserved1: 0x0
reserved2: 0x0
reserved3: 0x0
content: ''
- sectname: __cstring
segname: __TEXT
addr: 0x1140
size: 4
offset: 0x1140
align: 0
reloff: 0x0
nreloc: 0
flags: 0x2
reserved1: 0x0
reserved2: 0x0
reserved3: 0x0
content: 466F6F00
- cmd: LC_SEGMENT_64
cmdsize: 232
segname: __DATA_CONST
vmaddr: 8192
vmsize: 4096
fileoff: 8192
filesize: 4096
maxprot: 3
initprot: 3
nsects: 2
flags: 16
Sections:
- sectname: __objc_classlist
segname: __DATA_CONST
addr: 0x2000
size: 8
offset: 0x2000
align: 3
reloff: 0x0
nreloc: 0
flags: 0x10000000
reserved1: 0x0
reserved2: 0x0
reserved3: 0x0
content: B830000000000000
- sectname: __objc_imageinfo
segname: __DATA_CONST
addr: 0x2008
size: 8
offset: 0x2008
align: 0
reloff: 0x0
nreloc: 0
flags: 0x0
reserved1: 0x0
reserved2: 0x0
reserved3: 0x0
content: '0000000040000000'
- cmd: LC_SEGMENT_64
cmdsize: 312
segname: __DATA
vmaddr: 12288
vmsize: 4096
fileoff: 12288
filesize: 4096
maxprot: 3
initprot: 3
nsects: 3
flags: 0
Sections:
- sectname: __objc_const
segname: __DATA
addr: 0x3000
size: 144
offset: 0x3000
align: 3
reloff: 0x0
nreloc: 0
flags: 0x0
reserved1: 0x0
reserved2: 0x0
reserved3: 0x0
content: '010000002800000028000000000000000000000000000000401100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000008000000000000000000000000000000401100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
- sectname: __objc_data
segname: __DATA
addr: 0x3090
size: 80
offset: 0x3090
align: 3
reloff: 0x0
nreloc: 0
flags: 0x0
reserved1: 0x0
reserved2: 0x0
reserved3: 0x0
content: '0000000000000000000000000000000000000000000000000000000000000000003000000000000090300000000000000000000000000000000000000000000000000000000000004830000000000000'
- sectname: __common
segname: __DATA
addr: 0x30E0
size: 24
offset: 0x0
align: 2
reloff: 0x0
nreloc: 0
flags: 0x1
reserved1: 0x0
reserved2: 0x0
reserved3: 0x0
- cmd: LC_SEGMENT_64
cmdsize: 72
segname: __LINKEDIT
vmaddr: 16384
vmsize: 824
fileoff: 16384
filesize: 824
maxprot: 1
initprot: 1
nsects: 0
flags: 0
- cmd: LC_DYLD_INFO_ONLY
cmdsize: 48
rebase_off: 16384
rebase_size: 16
bind_off: 16400
bind_size: 104
weak_bind_off: 0
weak_bind_size: 0
lazy_bind_off: 0
lazy_bind_size: 0
export_off: 16504
export_size: 152
- cmd: LC_SYMTAB
cmdsize: 24
symoff: 16664
nsyms: 14
stroff: 16888
strsize: 320
- cmd: LC_DYSYMTAB
cmdsize: 80
ilocalsym: 0
nlocalsym: 2
iextdefsym: 2
nextdefsym: 8
iundefsym: 10
nundefsym: 4
tocoff: 0
ntoc: 0
modtaboff: 0
nmodtab: 0
extrefsymoff: 0
nextrefsyms: 0
indirectsymoff: 0
nindirectsyms: 0
extreloff: 0
nextrel: 0
locreloff: 0
nlocrel: 0
- cmd: LC_ID_DYLIB
cmdsize: 112
dylib:
name: 24
timestamp: 0
current_version: 65536
compatibility_version: 65536
Content: '/System/Library/Frameworks/Availability.framework/Versions/A/Availability'
ZeroPadBytes: 7
- cmd: LC_UUID
cmdsize: 24
uuid: 4C4C4470-5555-3144-A142-4EE44DA08D2F
- cmd: LC_BUILD_VERSION
cmdsize: 32
platform: 1
minos: 851968
sdk: 983040
ntools: 1
Tools:
- tool: 4
version: 1245184
- cmd: LC_LOAD_DYLIB
cmdsize: 56
dylib:
name: 24
timestamp: 0
current_version: 14942208
compatibility_version: 65536
Content: '/usr/lib/libobjc.A.dylib'
ZeroPadBytes: 8
- cmd: LC_LOAD_DYLIB
cmdsize: 56
dylib:
name: 24
timestamp: 0
current_version: 88473600
compatibility_version: 65536
Content: '/usr/lib/libSystem.B.dylib'
ZeroPadBytes: 6
- cmd: LC_FUNCTION_STARTS
cmdsize: 16
dataoff: 16656
datasize: 8
- cmd: LC_DATA_IN_CODE
cmdsize: 16
dataoff: 16664
datasize: 0
LinkEditData:
RebaseOpcodes:
- Opcode: REBASE_OPCODE_SET_TYPE_IMM
Imm: 1
- Opcode: REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB
Imm: 1
ExtraData: [ 0x0 ]
- Opcode: REBASE_OPCODE_DO_REBASE_IMM_TIMES
Imm: 1
- Opcode: REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB
Imm: 2
ExtraData: [ 0x18 ]
- Opcode: REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB
Imm: 0
ExtraData: [ 0x2, 0x40 ]
- Opcode: REBASE_OPCODE_ADD_ADDR_IMM_SCALED
Imm: 1
- Opcode: REBASE_OPCODE_DO_REBASE_IMM_TIMES
Imm: 2
- Opcode: REBASE_OPCODE_ADD_ADDR_IMM_SCALED
Imm: 3
- Opcode: REBASE_OPCODE_DO_REBASE_IMM_TIMES
Imm: 1
- Opcode: REBASE_OPCODE_DONE
Imm: 0
BindOpcodes:
- Opcode: BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM
Imm: 0
Symbol: '_OBJC_METACLASS_$_NSObject'
- Opcode: BIND_OPCODE_SET_TYPE_IMM
Imm: 1
Symbol: ''
- Opcode: BIND_OPCODE_SET_DYLIB_ORDINAL_IMM
Imm: 1
Symbol: ''
- Opcode: BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB
Imm: 2
ULEBExtraData: [ 0x90 ]
Symbol: ''
- Opcode: BIND_OPCODE_DO_BIND
Imm: 0
Symbol: ''
- Opcode: BIND_OPCODE_DO_BIND
Imm: 0
Symbol: ''
- Opcode: BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM
Imm: 0
Symbol: __objc_empty_cache
- Opcode: BIND_OPCODE_SET_TYPE_IMM
Imm: 1
Symbol: ''
- Opcode: BIND_OPCODE_DO_BIND
Imm: 0
Symbol: ''
- Opcode: BIND_OPCODE_ADD_ADDR_ULEB
Imm: 0
ULEBExtraData: [ 0x20 ]
Symbol: ''
- Opcode: BIND_OPCODE_DO_BIND
Imm: 0
Symbol: ''
- Opcode: BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM
Imm: 0
Symbol: '_OBJC_CLASS_$_NSObject'
- Opcode: BIND_OPCODE_SET_TYPE_IMM
Imm: 1
Symbol: ''
- Opcode: BIND_OPCODE_ADD_ADDR_ULEB
Imm: 0
ULEBExtraData: [ 0xFFFFFFFFFFFFFFF0 ]
Symbol: ''
- Opcode: BIND_OPCODE_DO_BIND
Imm: 0
Symbol: ''
- Opcode: BIND_OPCODE_DONE
Imm: 0
Symbol: ''
ExportTrie:
TerminalSize: 0
NodeOffset: 0
Name: ''
Flags: 0x0
Address: 0x0
Other: 0x0
ImportName: ''
Children:
- TerminalSize: 0
NodeOffset: 5
Name: _
Flags: 0x0
Address: 0x0
Other: 0x0
ImportName: ''
Children:
- TerminalSize: 0
NodeOffset: 17
Name: OBJC_
Flags: 0x0
Address: 0x0
Other: 0x0
ImportName: ''
Children:
- TerminalSize: 3
NodeOffset: 49
Name: 'METACLASS_$_Foo'
Flags: 0x0
Address: 0x3090
Other: 0x0
ImportName: ''
- TerminalSize: 3
NodeOffset: 54
Name: 'CLASS_$_Foo'
Flags: 0x0
Address: 0x30B8
Other: 0x0
ImportName: ''
- TerminalSize: 0
NodeOffset: 59
Name: p
Flags: 0x0
Address: 0x0
Other: 0x0
ImportName: ''
Children:
- TerminalSize: 3
NodeOffset: 104
Name: rivateGlobalVariable
Flags: 0x0
Address: 0x30E0
Other: 0x0
ImportName: ''
- TerminalSize: 3
NodeOffset: 109
Name: ublicGlobalVariable
Flags: 0x0
Address: 0x30E4
Other: 0x0
ImportName: ''
Children:
- TerminalSize: 3
NodeOffset: 130
Name: '4'
Flags: 0x0
Address: 0x30F0
Other: 0x0
ImportName: ''
- TerminalSize: 3
NodeOffset: 135
Name: '3'
Flags: 0x0
Address: 0x30EC
Other: 0x0
ImportName: ''
- TerminalSize: 3
NodeOffset: 140
Name: '2'
Flags: 0x0
Address: 0x30E8
Other: 0x0
ImportName: ''
- TerminalSize: 3
NodeOffset: 145
Name: '6'
Flags: 0x0
Address: 0x30F4
Other: 0x0
ImportName: ''
NameList:
- n_strx: 2
n_type: 0xE
n_sect: 5
n_desc: 0
n_value: 12288
- n_strx: 28
n_type: 0xE
n_sect: 5
n_desc: 0
n_value: 12360
- n_strx: 50
n_type: 0xF
n_sect: 7
n_desc: 0
n_value: 12512
- n_strx: 73
n_type: 0xF
n_sect: 7
n_desc: 0
n_value: 12516
- n_strx: 95
n_type: 0xF
n_sect: 7
n_desc: 0
n_value: 12520
- n_strx: 118
n_type: 0xF
n_sect: 7
n_desc: 0
n_value: 12524
- n_strx: 141
n_type: 0xF
n_sect: 7
n_desc: 0
n_value: 12528
- n_strx: 164
n_type: 0xF
n_sect: 7
n_desc: 0
n_value: 12532
- n_strx: 187
n_type: 0xF
n_sect: 6
n_desc: 0
n_value: 12432
- n_strx: 209
n_type: 0xF
n_sect: 6
n_desc: 0
n_value: 12472
- n_strx: 227
n_type: 0x1
n_sect: 0
n_desc: 256
n_value: 0
- n_strx: 250
n_type: 0x1
n_sect: 0
n_desc: 256
n_value: 0
- n_strx: 277
n_type: 0x1
n_sect: 0
n_desc: 256
n_value: 0
- n_strx: 296
n_type: 0x1
n_sect: 0
n_desc: 512
n_value: 0
StringTable:
- ' '
- '__OBJC_METACLASS_RO_$_Foo'
- '__OBJC_CLASS_RO_$_Foo'
- _privateGlobalVariable
- _publicGlobalVariable
- _publicGlobalVariable2
- _publicGlobalVariable3
- _publicGlobalVariable4
- _publicGlobalVariable6
- '_OBJC_METACLASS_$_Foo'
- '_OBJC_CLASS_$_Foo'
- '_OBJC_CLASS_$_NSObject'
- '_OBJC_METACLASS_$_NSObject'
- __objc_empty_cache
- dyld_stub_binder
- ''
- ''
- ''
- ''
- ''
- ''
- ''
...

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -111,17 +111,15 @@ static bool run(ArrayRef<const char *> Args, const char *ProgName) {
// Execute and gather AST results.
// An invocation is ran for each unique target triple and for each header
// access level.
Records FrontendResults;
for (const auto &[Targ, Trip] : Opts.DriverOpts.Targets) {
Ctx.Verifier->setTarget(Targ);
Ctx.Slice = std::make_shared<FrontendRecordsSlice>(Trip);
for (const HeaderType Type :
{HeaderType::Public, HeaderType::Private, HeaderType::Project}) {
Ctx.Slice = std::make_shared<FrontendRecordsSlice>(Trip);
Ctx.Verifier->setTarget(Targ);
Ctx.Type = Type;
if (!runFrontend(ProgName, Opts.DriverOpts.Verbose, Ctx,
InMemoryFileSystem.get(), Opts.getClangFrontendArgs()))
return EXIT_FAILURE;
FrontendResults.emplace_back(std::move(Ctx.Slice));
}
}

View File

@ -261,6 +261,9 @@ Options::Options(DiagnosticsEngine &Diag, FileManager *FM,
if (!processFrontendOptions(ArgList))
return;
/// Force cc1 options that should always be on.
FrontendArgs = {"-fsyntax-only", "-Wprivate-extern"};
/// Any unclaimed arguments should be handled by invoking the clang frontend.
for (const Arg *A : ArgList) {
if (A->isClaimed())
@ -268,7 +271,6 @@ Options::Options(DiagnosticsEngine &Diag, FileManager *FM,
FrontendArgs.emplace_back(A->getSpelling());
llvm::copy(A->getValues(), std::back_inserter(FrontendArgs));
}
FrontendArgs.push_back("-fsyntax-only");
}
InstallAPIContext Options::createContext() {

View File

@ -42,6 +42,7 @@ static const DiagnosticRecord BuiltinDiagnosticsByID[] = {
#include "clang/Basic/DiagnosticSemaKinds.inc"
#include "clang/Basic/DiagnosticAnalysisKinds.inc"
#include "clang/Basic/DiagnosticRefactoringKinds.inc"
#include "clang/Basic/DiagnosticInstallAPIKinds.inc"
#undef DIAG
};