Skip to content

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

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 5 commits into from
Mar 20, 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
16 changes: 12 additions & 4 deletions clang/include/clang/AST/Availability.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ struct AvailabilityInfo {
VersionTuple Introduced;
VersionTuple Deprecated;
VersionTuple Obsoleted;
bool Unavailable = false;
bool UnconditionallyDeprecated = false;
bool UnconditionallyUnavailable = false;

Expand All @@ -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
Expand All @@ -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);
Expand All @@ -105,10 +113,10 @@ struct AvailabilityInfo {
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);
}

Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -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">;

23 changes: 23 additions & 0 deletions clang/include/clang/Basic/DiagnosticInstallAPIKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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
61 changes: 53 additions & 8 deletions clang/include/clang/InstallAPI/DylibVerifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,34 @@ class DylibVerifier {
// 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);
Expand All @@ -66,28 +81,58 @@ class DylibVerifier {
/// 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();

/// 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);

/// Add verified exported symbol.
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;
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/InstallAPI/Frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -34,6 +35,8 @@ class InstallAPIAction : public ASTFrontendAction {

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());
}
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/InstallAPI/MachO.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions clang/lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Loading