diff --git a/clang/include/clang/AST/Availability.h b/clang/include/clang/AST/Availability.h index ae3acbeffe7f1..5cfbaf0cdfbd2 100644 --- a/clang/include/clang/AST/Availability.h +++ b/clang/include/clang/AST/Availability.h @@ -75,6 +75,9 @@ struct AvailabilityInfo { /// Determine if this AvailabilityInfo represents the default availability. bool isDefault() const { return *this == AvailabilityInfo(); } + /// Check if the symbol has been obsoleted. + bool isObsoleted() const { return !Obsoleted.empty(); } + /// Check if the symbol is unconditionally deprecated. /// /// i.e. \code __attribute__((deprecated)) \endcode diff --git a/clang/include/clang/InstallAPI/Context.h b/clang/include/clang/InstallAPI/Context.h index bdb576d7d85fb..54e517544b8ed 100644 --- a/clang/include/clang/InstallAPI/Context.h +++ b/clang/include/clang/InstallAPI/Context.h @@ -11,6 +11,7 @@ #include "clang/Basic/Diagnostic.h" #include "clang/Basic/FileManager.h" +#include "clang/InstallAPI/DylibVerifier.h" #include "clang/InstallAPI/HeaderFile.h" #include "clang/InstallAPI/MachO.h" #include "llvm/ADT/DenseMap.h" @@ -45,6 +46,9 @@ struct InstallAPIContext { /// DiagnosticsEngine for all error reporting. DiagnosticsEngine *Diags = nullptr; + /// Verifier when binary dylib is passed as input. + std::unique_ptr Verifier = nullptr; + /// File Path of output location. llvm::StringRef OutputLoc{}; diff --git a/clang/include/clang/InstallAPI/DylibVerifier.h b/clang/include/clang/InstallAPI/DylibVerifier.h index 1a6121b3a258b..72c4743fdf65e 100644 --- a/clang/include/clang/InstallAPI/DylibVerifier.h +++ b/clang/include/clang/InstallAPI/DylibVerifier.h @@ -9,10 +9,12 @@ #ifndef LLVM_CLANG_INSTALLAPI_DYLIBVERIFIER_H #define LLVM_CLANG_INSTALLAPI_DYLIBVERIFIER_H -#include "llvm/TextAPI/Target.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/InstallAPI/MachO.h" namespace clang { namespace installapi { +struct FrontendAttrs; /// A list of InstallAPI verification modes. enum class VerificationMode { @@ -22,6 +24,81 @@ enum class VerificationMode { Pedantic, }; +/// Service responsible to tracking state of verification across the +/// lifetime of InstallAPI. +/// As declarations are collected during AST traversal, they are +/// compared as symbols against what is available in the binary dylib. +class DylibVerifier { +private: + struct SymbolContext; + +public: + enum class Result { NoVerify, Ignore, Valid, Invalid }; + struct VerifierContext { + // Current target being verified against the AST. + llvm::MachO::Target Target; + + // Query state of verification after AST has been traversed. + Result FrontendState; + + // First error for AST traversal, which is tied to the target triple. + bool DiscoveredFirstError; + }; + + 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()) {} + + Result verify(GlobalRecord *R, const FrontendAttrs *FA); + Result verify(ObjCInterfaceRecord *R, const FrontendAttrs *FA); + Result verify(ObjCIVarRecord *R, const FrontendAttrs *FA, + const StringRef SuperClass); + + /// Initialize target for verification. + void setTarget(const Target &T); + + /// Release ownership over exports. + std::unique_ptr getExports() { return std::move(Exports); } + + /// Get result of verification. + Result getState() const { return Ctx.FrontendState; } + +private: + /// Determine whether to compare declaration to symbol in binary. + bool canVerify(); + + /// Shared implementation for verifying exported symbols. + Result verifyImpl(Record *R, SymbolContext &SymCtx); + + /// 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 = {}); + + // 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; + + // Attempt to demangle when reporting violations. + bool Demangle = false; + + // Valid symbols in final text file. + std::unique_ptr Exports = std::make_unique(); + + // Track current state of verification while traversing AST. + VerifierContext Ctx; +}; + } // namespace installapi } // namespace clang #endif // LLVM_CLANG_INSTALLAPI_DYLIBVERIFIER_H diff --git a/clang/include/clang/InstallAPI/Frontend.h b/clang/include/clang/InstallAPI/Frontend.h index 873cb50d60a54..660fc8cd69a59 100644 --- a/clang/include/clang/InstallAPI/Frontend.h +++ b/clang/include/clang/InstallAPI/Frontend.h @@ -14,7 +14,6 @@ #define LLVM_CLANG_INSTALLAPI_FRONTEND_H #include "clang/AST/ASTConsumer.h" -#include "clang/AST/Availability.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/InstallAPI/Context.h" diff --git a/clang/include/clang/InstallAPI/FrontendRecords.h b/clang/include/clang/InstallAPI/FrontendRecords.h index 1f5bc37798bef..59271e81e230c 100644 --- a/clang/include/clang/InstallAPI/FrontendRecords.h +++ b/clang/include/clang/InstallAPI/FrontendRecords.h @@ -43,13 +43,13 @@ class FrontendRecordsSlice : public llvm::MachO::RecordsSlice { /// \param Flags The flags that describe attributes of the symbol. /// \param Inlined Whether declaration is inlined, only applicable to /// functions. - /// \return The non-owning pointer to added record in slice. - GlobalRecord *addGlobal(StringRef Name, RecordLinkage Linkage, - GlobalRecord::Kind GV, - const clang::AvailabilityInfo Avail, const Decl *D, - const HeaderType Access, - SymbolFlags Flags = SymbolFlags::None, - bool Inlined = false); + /// \return The non-owning pointer to added record in slice with it's frontend + /// attributes. + std::pair + addGlobal(StringRef Name, RecordLinkage Linkage, GlobalRecord::Kind GV, + const clang::AvailabilityInfo Avail, const Decl *D, + const HeaderType Access, SymbolFlags Flags = SymbolFlags::None, + bool Inlined = false); /// Add ObjC Class record with attributes from AST. /// @@ -60,11 +60,12 @@ class FrontendRecordsSlice : public llvm::MachO::RecordsSlice { /// \param D The pointer to the declaration from traversing AST. /// \param Access The intended access level of symbol. /// \param IsEHType Whether declaration has an exception attribute. - /// \return The non-owning pointer to added record in slice. - ObjCInterfaceRecord *addObjCInterface(StringRef Name, RecordLinkage Linkage, - const clang::AvailabilityInfo Avail, - const Decl *D, HeaderType Access, - bool IsEHType); + /// \return The non-owning pointer to added record in slice with it's frontend + /// attributes. + std::pair + addObjCInterface(StringRef Name, RecordLinkage Linkage, + const clang::AvailabilityInfo Avail, const Decl *D, + HeaderType Access, bool IsEHType); /// Add ObjC Category record with attributes from AST. /// @@ -75,11 +76,12 @@ class FrontendRecordsSlice : public llvm::MachO::RecordsSlice { /// to the active target triple. /// \param D The pointer to the declaration from traversing AST. /// \param Access The intended access level of symbol. - /// \return The non-owning pointer to added record in slice. - ObjCCategoryRecord *addObjCCategory(StringRef ClassToExtend, - StringRef CategoryName, - const clang::AvailabilityInfo Avail, - const Decl *D, HeaderType Access); + /// \return The non-owning pointer to added record in slice with it's frontend + /// attributes. + std::pair + addObjCCategory(StringRef ClassToExtend, StringRef CategoryName, + const clang::AvailabilityInfo Avail, const Decl *D, + HeaderType Access); /// Add ObjC IVar record with attributes from AST. /// @@ -91,12 +93,13 @@ class FrontendRecordsSlice : public llvm::MachO::RecordsSlice { /// \param D The pointer to the declaration from traversing AST. /// \param Access The intended access level of symbol. /// \param AC The access control tied to the ivar declaration. - /// \return The non-owning pointer to added record in slice. - ObjCIVarRecord *addObjCIVar(ObjCContainerRecord *Container, - StringRef IvarName, RecordLinkage Linkage, - const clang::AvailabilityInfo Avail, - const Decl *D, HeaderType Access, - const clang::ObjCIvarDecl::AccessControl AC); + /// \return The non-owning pointer to added record in slice with it's frontend + /// attributes. + std::pair + addObjCIVar(ObjCContainerRecord *Container, StringRef IvarName, + RecordLinkage Linkage, const clang::AvailabilityInfo Avail, + const Decl *D, HeaderType Access, + const clang::ObjCIvarDecl::AccessControl AC); private: /// Mapping of records stored in slice to their frontend attributes. diff --git a/clang/include/clang/InstallAPI/MachO.h b/clang/include/clang/InstallAPI/MachO.h index 55e5591389ce1..6dee6f2242038 100644 --- a/clang/include/clang/InstallAPI/MachO.h +++ b/clang/include/clang/InstallAPI/MachO.h @@ -18,6 +18,7 @@ #include "llvm/TextAPI/PackedVersion.h" #include "llvm/TextAPI/Platform.h" #include "llvm/TextAPI/RecordVisitor.h" +#include "llvm/TextAPI/Symbol.h" #include "llvm/TextAPI/Target.h" #include "llvm/TextAPI/TextAPIWriter.h" #include "llvm/TextAPI/Utils.h" @@ -33,8 +34,10 @@ using ObjCIVarRecord = llvm::MachO::ObjCIVarRecord; using Records = llvm::MachO::Records; using BinaryAttrs = llvm::MachO::RecordsSlice::BinaryAttrs; using SymbolSet = llvm::MachO::SymbolSet; +using SimpleSymbol = llvm::MachO::SimpleSymbol; using FileType = llvm::MachO::FileType; using PackedVersion = llvm::MachO::PackedVersion; using Target = llvm::MachO::Target; +using TargetList = llvm::MachO::TargetList; #endif // LLVM_CLANG_INSTALLAPI_MACHO_H diff --git a/clang/lib/InstallAPI/CMakeLists.txt b/clang/lib/InstallAPI/CMakeLists.txt index dc90d6370de41..894db699578f2 100644 --- a/clang/lib/InstallAPI/CMakeLists.txt +++ b/clang/lib/InstallAPI/CMakeLists.txt @@ -1,10 +1,12 @@ set(LLVM_LINK_COMPONENTS Support TextAPI + Demangle Core ) add_clang_library(clangInstallAPI + DylibVerifier.cpp FileList.cpp Frontend.cpp HeaderFile.cpp diff --git a/clang/lib/InstallAPI/DylibVerifier.cpp b/clang/lib/InstallAPI/DylibVerifier.cpp new file mode 100644 index 0000000000000..b7dd85d63fa14 --- /dev/null +++ b/clang/lib/InstallAPI/DylibVerifier.cpp @@ -0,0 +1,212 @@ +#include "clang/InstallAPI/DylibVerifier.h" +#include "clang/InstallAPI/FrontendRecords.h" +#include "llvm/Demangle/Demangle.h" + +using namespace llvm::MachO; + +namespace clang { +namespace installapi { + +/// Metadata stored about a mapping of a declaration to a symbol. +struct DylibVerifier::SymbolContext { + // Name to use for printing in diagnostics. + std::string PrettyPrintName{""}; + + // 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; +}; + +static std::string +getAnnotatedName(const Record *R, EncodeKind Kind, StringRef Name, + bool ValidSourceLoc = true, + ObjCIFSymbolKind ObjCIF = ObjCIFSymbolKind::None) { + assert(!Name.empty() && "Need symbol name for printing"); + + 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. + const bool IsAnnotatedObjCClass = ((ObjCIF != ObjCIFSymbolKind::None) && + (ObjCIF <= ObjCIFSymbolKind::EHType)); + + if (IsAnnotatedObjCClass) { + if (ObjCIF == ObjCIFSymbolKind::EHType) + Annotation += "Exception Type of "; + if (ObjCIF == ObjCIFSymbolKind::MetaClass) + Annotation += "Metaclass of "; + if (ObjCIF == ObjCIFSymbolKind::Class) + Annotation += "Class of "; + } + + // 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) { + if ((Kind == EncodeKind::GlobalSymbol) && Name.starts_with("_")) + return Annotation + Name.drop_front(1).str(); + return Annotation + Name.str(); + } + + if (IsAnnotatedObjCClass) + return Annotation + Name.str(); + + switch (Kind) { + case EncodeKind::GlobalSymbol: + return Annotation + Name.str(); + case EncodeKind::ObjectiveCInstanceVariable: + return Annotation + "(ObjC IVar) " + Name.str(); + case EncodeKind::ObjectiveCClass: + return Annotation + "(ObjC Class) " + Name.str(); + case EncodeKind::ObjectiveCClassEHType: + return Annotation + "(ObjC Class EH) " + Name.str(); + } + + llvm_unreachable("unexpected case for EncodeKind"); +} + +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"))) + return Name.str(); + char *Result = llvm::itaniumDemangle(Name.data()); + if (!Result) + return Name.str(); + + std::string Demangled(Result); + free(Result); + return Demangled; +} + +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; +} + +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); +} + +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() && + !SymCtx.FA->Avail.isObsoleted()) { + addSymbol(R, SymCtx); + } + return Ctx.FrontendState; + } + return Ctx.FrontendState; +} + +bool DylibVerifier::canVerify() { + return Ctx.FrontendState != Result::NoVerify; +} + +void DylibVerifier::setTarget(const Target &T) { + Ctx.Target = T; + Ctx.DiscoveredFirstError = false; + updateState(Dylib.empty() ? Result::NoVerify : Result::Ignore); +} + +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{ + getAnnotatedName(R, EncodeKind::ObjectiveCInstanceVariable, + Demangle ? demangle(FullName) : FullName), + 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); + + std::string DisplayName = + Demangle ? demangle(SymCtx.SymbolName) : SymCtx.SymbolName; + SymCtx.Kind = R->hasExceptionAttribute() ? EncodeKind::ObjectiveCClassEHType + : EncodeKind::ObjectiveCClass; + SymCtx.PrettyPrintName = getAnnotatedName(R, SymCtx.Kind, DisplayName); + 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.PrettyPrintName = + getAnnotatedName(R, Sym.Kind, Demangle ? demangle(Sym.Name) : Sym.Name); + SymCtx.Kind = Sym.Kind; + SymCtx.FA = FA; + return verifyImpl(R, SymCtx); +} + +} // namespace installapi +} // namespace clang diff --git a/clang/lib/InstallAPI/Frontend.cpp b/clang/lib/InstallAPI/Frontend.cpp index 707aeb17dc890..12cd5fcbc22bf 100644 --- a/clang/lib/InstallAPI/Frontend.cpp +++ b/clang/lib/InstallAPI/Frontend.cpp @@ -16,41 +16,47 @@ using namespace llvm; using namespace llvm::MachO; namespace clang::installapi { - -GlobalRecord *FrontendRecordsSlice::addGlobal( +std::pair FrontendRecordsSlice::addGlobal( StringRef Name, RecordLinkage Linkage, GlobalRecord::Kind GV, const clang::AvailabilityInfo Avail, const Decl *D, const HeaderType Access, SymbolFlags Flags, bool Inlined) { - auto *GR = + GlobalRecord *GR = llvm::MachO::RecordsSlice::addGlobal(Name, Linkage, GV, Flags, Inlined); - FrontendRecords.insert({GR, FrontendAttrs{Avail, D, Access}}); - return GR; + auto Result = FrontendRecords.insert({GR, FrontendAttrs{Avail, D, Access}}); + return {GR, &(Result.first->second)}; } -ObjCInterfaceRecord *FrontendRecordsSlice::addObjCInterface( - StringRef Name, RecordLinkage Linkage, const clang::AvailabilityInfo Avail, - const Decl *D, HeaderType Access, bool IsEHType) { +std::pair +FrontendRecordsSlice::addObjCInterface(StringRef Name, RecordLinkage Linkage, + const clang::AvailabilityInfo Avail, + const Decl *D, HeaderType Access, + bool IsEHType) { ObjCIFSymbolKind SymType = ObjCIFSymbolKind::Class | ObjCIFSymbolKind::MetaClass; if (IsEHType) SymType |= ObjCIFSymbolKind::EHType; - auto *ObjCR = + + ObjCInterfaceRecord *ObjCR = llvm::MachO::RecordsSlice::addObjCInterface(Name, Linkage, SymType); - FrontendRecords.insert({ObjCR, FrontendAttrs{Avail, D, Access}}); - return ObjCR; + auto Result = + FrontendRecords.insert({ObjCR, FrontendAttrs{Avail, D, Access}}); + return {ObjCR, &(Result.first->second)}; } -ObjCCategoryRecord *FrontendRecordsSlice::addObjCCategory( - StringRef ClassToExtend, StringRef CategoryName, - const clang::AvailabilityInfo Avail, const Decl *D, HeaderType Access) { - auto *ObjCR = +std::pair +FrontendRecordsSlice::addObjCCategory(StringRef ClassToExtend, + StringRef CategoryName, + const clang::AvailabilityInfo Avail, + const Decl *D, HeaderType Access) { + ObjCCategoryRecord *ObjCR = llvm::MachO::RecordsSlice::addObjCCategory(ClassToExtend, CategoryName); - FrontendRecords.insert({ObjCR, FrontendAttrs{Avail, D, Access}}); - return ObjCR; + auto Result = + FrontendRecords.insert({ObjCR, FrontendAttrs{Avail, D, Access}}); + return {ObjCR, &(Result.first->second)}; } -ObjCIVarRecord *FrontendRecordsSlice::addObjCIVar( +std::pair FrontendRecordsSlice::addObjCIVar( ObjCContainerRecord *Container, StringRef IvarName, RecordLinkage Linkage, const clang::AvailabilityInfo Avail, const Decl *D, HeaderType Access, const clang::ObjCIvarDecl::AccessControl AC) { @@ -59,11 +65,12 @@ ObjCIVarRecord *FrontendRecordsSlice::addObjCIVar( if ((Linkage == RecordLinkage::Exported) && ((AC == ObjCIvarDecl::Private) || (AC == ObjCIvarDecl::Package))) Linkage = RecordLinkage::Internal; - auto *ObjCR = + ObjCIVarRecord *ObjCR = llvm::MachO::RecordsSlice::addObjCIVar(Container, IvarName, Linkage); - FrontendRecords.insert({ObjCR, FrontendAttrs{Avail, D, Access}}); + auto Result = + FrontendRecords.insert({ObjCR, FrontendAttrs{Avail, D, Access}}); - return nullptr; + return {ObjCR, &(Result.first->second)}; } std::optional diff --git a/clang/lib/InstallAPI/Visitor.cpp b/clang/lib/InstallAPI/Visitor.cpp index b4ed5974a0570..187afe59309d3 100644 --- a/clang/lib/InstallAPI/Visitor.cpp +++ b/clang/lib/InstallAPI/Visitor.cpp @@ -11,6 +11,7 @@ #include "clang/AST/ParentMapContext.h" #include "clang/AST/VTableBuilder.h" #include "clang/Basic/Linkage.h" +#include "clang/InstallAPI/DylibVerifier.h" #include "clang/InstallAPI/FrontendRecords.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" @@ -156,7 +157,9 @@ void InstallAPIVisitor::recordObjCInstanceVariables( StringRef Name = IV->getName(); const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(IV); auto AC = IV->getCanonicalAccessControl(); - Ctx.Slice->addObjCIVar(Record, Name, Linkage, Avail, IV, *Access, AC); + auto [ObjCIVR, FA] = + Ctx.Slice->addObjCIVar(Record, Name, Linkage, Avail, IV, *Access, AC); + Ctx.Verifier->verify(ObjCIVR, FA, SuperClass); } } @@ -178,15 +181,16 @@ bool InstallAPIVisitor::VisitObjCInterfaceDecl(const ObjCInterfaceDecl *D) { (!D->getASTContext().getLangOpts().ObjCRuntime.isFragile() && hasObjCExceptionAttribute(D)); - ObjCInterfaceRecord *Class = + auto [Class, FA] = Ctx.Slice->addObjCInterface(Name, Linkage, Avail, D, *Access, IsEHType); + Ctx.Verifier->verify(Class, FA); // Get base class. StringRef SuperClassName; if (const auto *SuperClass = D->getSuperClass()) SuperClassName = SuperClass->getObjCRuntimeNameAsString(); - recordObjCInstanceVariables(D->getASTContext(), Class, SuperClassName, + recordObjCInstanceVariables(D->getASTContext(), Class, Class->getName(), D->ivars()); return true; } @@ -201,8 +205,8 @@ bool InstallAPIVisitor::VisitObjCCategoryDecl(const ObjCCategoryDecl *D) { const ObjCInterfaceDecl *InterfaceD = D->getClassInterface(); const StringRef InterfaceName = InterfaceD->getName(); - ObjCCategoryRecord *Category = Ctx.Slice->addObjCCategory( - InterfaceName, CategoryName, Avail, D, *Access); + auto [Category, FA] = Ctx.Slice->addObjCCategory(InterfaceName, CategoryName, + Avail, D, *Access); recordObjCInstanceVariables(D->getASTContext(), Category, InterfaceName, D->ivars()); return true; @@ -236,8 +240,10 @@ bool InstallAPIVisitor::VisitVarDecl(const VarDecl *D) { const bool WeakDef = D->hasAttr(); const bool ThreadLocal = D->getTLSKind() != VarDecl::TLS_None; const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(D); - Ctx.Slice->addGlobal(getMangledName(D), Linkage, GlobalRecord::Kind::Variable, - Avail, D, *Access, getFlags(WeakDef, ThreadLocal)); + auto [GR, FA] = Ctx.Slice->addGlobal(getMangledName(D), Linkage, + GlobalRecord::Kind::Variable, Avail, D, + *Access, getFlags(WeakDef, ThreadLocal)); + Ctx.Verifier->verify(GR, FA); return true; } @@ -287,8 +293,10 @@ bool InstallAPIVisitor::VisitFunctionDecl(const FunctionDecl *D) { const RecordLinkage Linkage = (Inlined || !isExported(D)) ? RecordLinkage::Internal : RecordLinkage::Exported; - Ctx.Slice->addGlobal(Name, Linkage, GlobalRecord::Kind::Function, Avail, D, - *Access, getFlags(WeakDef), Inlined); + auto [GR, FA] = + Ctx.Slice->addGlobal(Name, Linkage, GlobalRecord::Kind::Function, Avail, + D, *Access, getFlags(WeakDef), Inlined); + Ctx.Verifier->verify(GR, FA); return true; } @@ -478,9 +486,10 @@ void InstallAPIVisitor::emitVTableSymbols(const CXXRecordDecl *D, VTableLinkage == CXXLinkage::WeakODRLinkage) { const std::string Name = getMangledCXXVTableName(D); const bool WeakDef = VTableLinkage == CXXLinkage::WeakODRLinkage; - Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, - GlobalRecord::Kind::Variable, Avail, D, Access, - getFlags(WeakDef)); + auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, + GlobalRecord::Kind::Variable, Avail, + D, Access, getFlags(WeakDef)); + Ctx.Verifier->verify(GR, FA); if (!D->getDescribedClassTemplate() && !D->isInvalidDecl()) { VTableContextBase *VTable = D->getASTContext().getVTableContext(); auto AddThunk = [&](GlobalDecl GD) { @@ -491,9 +500,10 @@ void InstallAPIVisitor::emitVTableSymbols(const CXXRecordDecl *D, for (const auto &Thunk : *Thunks) { const std::string Name = getMangledCXXThunk(GD, Thunk); - Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, - GlobalRecord::Kind::Function, Avail, - GD.getDecl(), Access); + auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, + GlobalRecord::Kind::Function, + Avail, GD.getDecl(), Access); + Ctx.Verifier->verify(GR, FA); } }; @@ -519,12 +529,16 @@ void InstallAPIVisitor::emitVTableSymbols(const CXXRecordDecl *D, if (hasRTTI(D)) { std::string Name = getMangledCXXRTTI(D); - Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, - GlobalRecord::Kind::Variable, Avail, D, Access); + auto [GR, FA] = + Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, + GlobalRecord::Kind::Variable, Avail, D, Access); + Ctx.Verifier->verify(GR, FA); Name = getMangledCXXRTTIName(D); - Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, - GlobalRecord::Kind::Variable, Avail, D, Access); + auto [NamedGR, NamedFA] = + Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, + GlobalRecord::Kind::Variable, Avail, D, Access); + Ctx.Verifier->verify(NamedGR, NamedFA); } for (const auto &It : D->bases()) { @@ -615,15 +629,17 @@ bool InstallAPIVisitor::VisitCXXRecordDecl(const CXXRecordDecl *D) { continue; std::string Name = getMangledCtorDtor(M, Ctor_Base); - Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, - GlobalRecord::Kind::Function, Avail, D, *Access, - getFlags(WeakDef)); + auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, + GlobalRecord::Kind::Function, Avail, + D, *Access, getFlags(WeakDef)); + Ctx.Verifier->verify(GR, FA); if (!D->isAbstract()) { std::string Name = getMangledCtorDtor(M, Ctor_Complete); - Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, - GlobalRecord::Kind::Function, Avail, D, *Access, - getFlags(WeakDef)); + auto [GR, FA] = Ctx.Slice->addGlobal( + Name, RecordLinkage::Exported, GlobalRecord::Kind::Function, Avail, + D, *Access, getFlags(WeakDef)); + Ctx.Verifier->verify(GR, FA); } continue; @@ -635,20 +651,23 @@ bool InstallAPIVisitor::VisitCXXRecordDecl(const CXXRecordDecl *D) { continue; std::string Name = getMangledCtorDtor(M, Dtor_Base); - Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, - GlobalRecord::Kind::Function, Avail, D, *Access, - getFlags(WeakDef)); + auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, + GlobalRecord::Kind::Function, Avail, + D, *Access, getFlags(WeakDef)); + Ctx.Verifier->verify(GR, FA); Name = getMangledCtorDtor(M, Dtor_Complete); - Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, - GlobalRecord::Kind::Function, Avail, D, *Access, - getFlags(WeakDef)); + auto [CompleteGR, CompleteFA] = Ctx.Slice->addGlobal( + Name, RecordLinkage::Exported, GlobalRecord::Kind::Function, Avail, D, + *Access, getFlags(WeakDef)); + Ctx.Verifier->verify(CompleteGR, CompleteFA); if (Dtor->isVirtual()) { Name = getMangledCtorDtor(M, Dtor_Deleting); - Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, - GlobalRecord::Kind::Function, Avail, D, *Access, - getFlags(WeakDef)); + auto [VirtualGR, VirtualFA] = Ctx.Slice->addGlobal( + Name, RecordLinkage::Exported, GlobalRecord::Kind::Function, Avail, + D, *Access, getFlags(WeakDef)); + Ctx.Verifier->verify(VirtualGR, VirtualFA); } continue; @@ -661,9 +680,10 @@ bool InstallAPIVisitor::VisitCXXRecordDecl(const CXXRecordDecl *D) { continue; std::string Name = getMangledName(M); - Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, - GlobalRecord::Kind::Function, Avail, D, *Access, - getFlags(WeakDef)); + auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, + GlobalRecord::Kind::Function, Avail, D, + *Access, getFlags(WeakDef)); + Ctx.Verifier->verify(GR, FA); } if (auto *Templ = dyn_cast(D)) { @@ -694,9 +714,10 @@ bool InstallAPIVisitor::VisitCXXRecordDecl(const CXXRecordDecl *D) { const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(Var); const bool WeakDef = Var->hasAttr() || KeepInlineAsWeak; - Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, - GlobalRecord::Kind::Variable, Avail, D, *Access, - getFlags(WeakDef)); + auto [GR, FA] = Ctx.Slice->addGlobal(Name, RecordLinkage::Exported, + GlobalRecord::Kind::Variable, Avail, D, + *Access, getFlags(WeakDef)); + Ctx.Verifier->verify(GR, FA); } return true; diff --git a/clang/test/InstallAPI/asm.test b/clang/test/InstallAPI/asm.test new file mode 100644 index 0000000000000..b6af7f643d72f --- /dev/null +++ b/clang/test/InstallAPI/asm.test @@ -0,0 +1,90 @@ +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: sed -e "s|DSTROOT|%/t|g" %t/inputs.json.in > %t/inputs.json + +// RUN: clang-installapi -target arm64-apple-macos13.1 \ +// RUN: -I%t/usr/include \ +// RUN: -install_name @rpath/lib/libasm.dylib \ +// RUN: %t/inputs.json -o %t/output.tbd 2>&1 | FileCheck %s --allow-empty +// RUN: llvm-readtapi -compare %t/output.tbd %t/expected.tbd 2>&1 | FileCheck %s --allow-empty + +// CHECK-NOT: error: +// CHECK-NOT: warning: + +//--- usr/include/asm.h +#ifndef ASM_H +#define ASM_H + +extern int ivar __asm("_OBJC_IVAR_$_SomeClass._ivar1"); +extern int objcClass1 __asm("_OBJC_CLASS_$_SomeClass"); +extern int objcClass2 __asm("_OBJC_METACLASS_$_SomeClass"); +extern int objcClass3 __asm("_OBJC_EHTYPE_$_SomeClass"); +extern int objcClass4 __asm(".objc_class_name_SomeClass"); + +__attribute__((visibility("hidden"))) +@interface NSString { +} +@end + +extern int ivarExtra __asm("_OBJC_IVAR_$_NSString._ivar1"); +#endif // ASM_H + +//--- inputs.json.in +{ + "headers": [ { + "path" : "DSTROOT/usr/include/asm.h", + "type" : "public" + }], + "version": "3" +} + +//--- expected.tbd +{ + "main_library": { + "compatibility_versions": [ + { + "version": "0" + } + ], + "current_versions": [ + { + "version": "0" + } + ], + "exported_symbols": [ + { + "data": { + "objc_class": [ + "SomeClass" + ], + "objc_eh_type": [ + "SomeClass" + ], + "objc_ivar": [ + "NSString._ivar1", + "SomeClass._ivar1" + ] + } + } + ], + "flags": [ + { + "attributes": [ + "not_app_extension_safe" + ] + } + ], + "install_names": [ + { + "name": "@rpath/lib/libasm.dylib" + } + ], + "target_info": [ + { + "min_deployment": "13.1", + "target": "arm64-macos" + } + ] + }, + "tapi_tbd_version": 5 +} diff --git a/clang/tools/clang-installapi/ClangInstallAPI.cpp b/clang/tools/clang-installapi/ClangInstallAPI.cpp index fdf7628cabd80..d758f73117900 100644 --- a/clang/tools/clang-installapi/ClangInstallAPI.cpp +++ b/clang/tools/clang-installapi/ClangInstallAPI.cpp @@ -116,6 +116,7 @@ static bool run(ArrayRef Args, const char *ProgName) { for (const HeaderType Type : {HeaderType::Public, HeaderType::Private, HeaderType::Project}) { Ctx.Slice = std::make_shared(Trip); + Ctx.Verifier->setTarget(Targ); Ctx.Type = Type; if (!runFrontend(ProgName, Opts.DriverOpts.Verbose, Ctx, InMemoryFileSystem.get(), Opts.getClangFrontendArgs())) @@ -124,6 +125,9 @@ static bool run(ArrayRef Args, const char *ProgName) { } } + if (Ctx.Verifier->getState() == DylibVerifier::Result::Invalid) + return EXIT_FAILURE; + // After symbols have been collected, prepare to write output. auto Out = CI->createOutputFile(Ctx.OutputLoc, /*Binary=*/false, /*RemoveFileOnSignal=*/false, @@ -133,13 +137,7 @@ static bool run(ArrayRef Args, const char *ProgName) { return EXIT_FAILURE; // Assign attributes for serialization. - auto Symbols = std::make_unique(); - for (const auto &FR : FrontendResults) { - SymbolConverter Converter(Symbols.get(), FR->getTarget()); - FR->visit(Converter); - } - - InterfaceFile IF(std::move(Symbols)); + InterfaceFile IF(Ctx.Verifier->getExports()); for (const auto &TargetInfo : Opts.DriverOpts.Targets) { IF.addTarget(TargetInfo.first); IF.setFromBinaryAttrs(Ctx.BA, TargetInfo.first); diff --git a/clang/tools/clang-installapi/Options.cpp b/clang/tools/clang-installapi/Options.cpp index 7c8272a8ba7e0..e5ff8a1aaa4fe 100644 --- a/clang/tools/clang-installapi/Options.cpp +++ b/clang/tools/clang-installapi/Options.cpp @@ -301,10 +301,11 @@ InstallAPIContext Options::createContext() { } } - // Parse binary dylib. - // TODO: Initialize verifier. - if (DriverOpts.DylibToVerify.empty()) + // Parse binary dylib and initialize verifier. + if (DriverOpts.DylibToVerify.empty()) { + Ctx.Verifier = std::make_unique(); return Ctx; + } auto Buffer = FM->getBufferForFile(DriverOpts.DylibToVerify); if (auto Err = Buffer.getError()) { @@ -322,6 +323,8 @@ InstallAPIContext Options::createContext() { return Ctx; } + Ctx.Verifier = std::make_unique( + std::move(*Slices), Diags, DriverOpts.VerifyMode, DriverOpts.Demangle); return Ctx; } diff --git a/llvm/include/llvm/TextAPI/Record.h b/llvm/include/llvm/TextAPI/Record.h index 98639b064eaad..ef152ce433877 100644 --- a/llvm/include/llvm/TextAPI/Record.h +++ b/llvm/include/llvm/TextAPI/Record.h @@ -51,7 +51,8 @@ class Record { public: Record() = default; Record(StringRef Name, RecordLinkage Linkage, SymbolFlags Flags) - : Name(Name), Linkage(Linkage), Flags(mergeFlags(Flags, Linkage)) {} + : Name(Name), Linkage(Linkage), Flags(mergeFlags(Flags, Linkage)), + Verified(false) {} bool isWeakDefined() const { return (Flags & SymbolFlags::WeakDefined) == SymbolFlags::WeakDefined; @@ -79,6 +80,9 @@ class Record { bool isExported() const { return Linkage >= RecordLinkage::Rexported; } bool isRexported() const { return Linkage == RecordLinkage::Rexported; } + bool isVerified() const { return Verified; } + void setVerify(bool V = true) { Verified = V; } + StringRef getName() const { return Name; } SymbolFlags getFlags() const { return Flags; } @@ -89,6 +93,7 @@ class Record { StringRef Name; RecordLinkage Linkage; SymbolFlags Flags; + bool Verified; friend class RecordsSlice; };