diff --git a/clang/include/clang/InstallAPI/Context.h b/clang/include/clang/InstallAPI/Context.h index 3e2046642c7fe..4e9e90e5d2dbe 100644 --- a/clang/include/clang/InstallAPI/Context.h +++ b/clang/include/clang/InstallAPI/Context.h @@ -12,12 +12,12 @@ #include "clang/Basic/Diagnostic.h" #include "clang/Basic/FileManager.h" #include "clang/InstallAPI/HeaderFile.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/TextAPI/InterfaceFile.h" -#include "llvm/TextAPI/RecordVisitor.h" -#include "llvm/TextAPI/RecordsSlice.h" namespace clang { namespace installapi { +class FrontendRecordsSlice; /// Struct used for generating validating InstallAPI. /// The attributes captured represent all necessary information @@ -37,7 +37,7 @@ struct InstallAPIContext { HeaderType Type = HeaderType::Unknown; /// Active TargetSlice for symbol record collection. - std::shared_ptr Slice; + std::shared_ptr Slice; /// FileManager for all I/O operations. FileManager *FM = nullptr; @@ -50,6 +50,30 @@ struct InstallAPIContext { /// What encoding to write output as. llvm::MachO::FileType FT = llvm::MachO::FileType::TBD_V5; + + /// Populate entries of headers that should be included for TextAPI + /// generation. + void addKnownHeader(const HeaderFile &H); + + /// Record visited files during frontend actions to determine whether to + /// include their declarations for TextAPI generation. + /// + /// \param FE Header that is being parsed. + /// \param PP Preprocesser used for querying how header was imported. + /// \return Access level of header if it should be included for TextAPI + /// generation. + std::optional findAndRecordFile(const FileEntry *FE, + const Preprocessor &PP); + +private: + using HeaderMap = llvm::DenseMap; + + // Collection of parsed header files and their access level. If set to + // HeaderType::Unknown, they are not used for TextAPI generation. + HeaderMap KnownFiles; + + // Collection of expected header includes and the access level for them. + llvm::DenseMap KnownIncludes; }; } // namespace installapi diff --git a/clang/include/clang/InstallAPI/Frontend.h b/clang/include/clang/InstallAPI/Frontend.h index 7ee87ae028d07..d72b4680fde40 100644 --- a/clang/include/clang/InstallAPI/Frontend.h +++ b/clang/include/clang/InstallAPI/Frontend.h @@ -14,6 +14,7 @@ #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" @@ -24,23 +25,78 @@ namespace clang { namespace installapi { +using SymbolFlags = llvm::MachO::SymbolFlags; +using RecordLinkage = llvm::MachO::RecordLinkage; +using GlobalRecord = llvm::MachO::GlobalRecord; +using ObjCInterfaceRecord = llvm::MachO::ObjCInterfaceRecord; + +// Represents a collection of frontend records for a library that are tied to a +// darwin target triple. +class FrontendRecordsSlice : public llvm::MachO::RecordsSlice { +public: + FrontendRecordsSlice(const llvm::Triple &T) + : llvm::MachO::RecordsSlice({T}) {} + + /// Add non-ObjC global record with attributes from AST. + /// + /// \param Name The name of symbol. + /// \param Linkage The linkage of symbol. + /// \param GV The kind of global. + /// \param Avail The availability information tied to the active target + /// triple. + /// \param D The pointer to the declaration from traversing AST. + /// \param Access The intended access level of symbol. + /// \param Flags The flags that describe attributes of the symbol. + /// \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); + + /// Add ObjC Class record with attributes from AST. + /// + /// \param Name The name of class, not symbol. + /// \param Linkage The linkage of symbol. + /// \param Avail The availability information tied to the active target + /// triple. + /// \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); + +private: + /// Frontend information captured about records. + struct FrontendAttrs { + const AvailabilityInfo Avail; + const Decl *D; + const HeaderType Access; + }; + + /// Mapping of records stored in slice to their frontend attributes. + llvm::DenseMap FrontendRecords; +}; + /// Create a buffer that contains all headers to scan /// for global symbols with. -std::unique_ptr -createInputBuffer(const InstallAPIContext &Ctx); +std::unique_ptr createInputBuffer(InstallAPIContext &Ctx); class InstallAPIAction : public ASTFrontendAction { public: - explicit InstallAPIAction(llvm::MachO::RecordsSlice &Records) - : Records(Records) {} + explicit InstallAPIAction(InstallAPIContext &Ctx) : Ctx(Ctx) {} std::unique_ptr CreateASTConsumer(CompilerInstance &CI, StringRef InFile) override { - return std::make_unique(CI.getASTContext(), Records); + return std::make_unique( + CI.getASTContext(), Ctx, CI.getSourceManager(), CI.getPreprocessor()); } private: - llvm::MachO::RecordsSlice &Records; + InstallAPIContext &Ctx; }; } // namespace installapi } // namespace clang diff --git a/clang/include/clang/InstallAPI/Visitor.h b/clang/include/clang/InstallAPI/Visitor.h index 95d669688e4f9..60a05005df841 100644 --- a/clang/include/clang/InstallAPI/Visitor.h +++ b/clang/include/clang/InstallAPI/Visitor.h @@ -17,8 +17,8 @@ #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Basic/TargetInfo.h" #include "clang/Frontend/FrontendActions.h" +#include "clang/InstallAPI/Context.h" #include "llvm/ADT/Twine.h" -#include "llvm/TextAPI/RecordsSlice.h" namespace clang { namespace installapi { @@ -27,8 +27,9 @@ namespace installapi { class InstallAPIVisitor final : public ASTConsumer, public RecursiveASTVisitor { public: - InstallAPIVisitor(ASTContext &ASTCtx, llvm::MachO::RecordsSlice &Slice) - : Slice(Slice), + InstallAPIVisitor(ASTContext &ASTCtx, InstallAPIContext &Ctx, + SourceManager &SrcMgr, Preprocessor &PP) + : Ctx(Ctx), SrcMgr(SrcMgr), PP(PP), MC(ItaniumMangleContext::create(ASTCtx, ASTCtx.getDiagnostics())), Layout(ASTCtx.getTargetInfo().getDataLayoutString()) {} void HandleTranslationUnit(ASTContext &ASTCtx) override; @@ -36,11 +37,19 @@ class InstallAPIVisitor final : public ASTConsumer, /// Collect global variables. bool VisitVarDecl(const VarDecl *D); + /// Collect Objective-C Interface declarations. + /// Every Objective-C class has an interface declaration that lists all the + /// ivars, properties, and methods of the class. + bool VisitObjCInterfaceDecl(const ObjCInterfaceDecl *D); + private: std::string getMangledName(const NamedDecl *D) const; std::string getBackendMangledName(llvm::Twine Name) const; + std::optional getAccessForDecl(const NamedDecl *D) const; - llvm::MachO::RecordsSlice &Slice; + InstallAPIContext &Ctx; + SourceManager &SrcMgr; + Preprocessor &PP; std::unique_ptr MC; StringRef Layout; }; diff --git a/clang/lib/InstallAPI/Frontend.cpp b/clang/lib/InstallAPI/Frontend.cpp index 133e49230ffa9..caa6e7e8a4054 100644 --- a/clang/lib/InstallAPI/Frontend.cpp +++ b/clang/lib/InstallAPI/Frontend.cpp @@ -16,6 +16,73 @@ using namespace llvm::MachO; namespace clang::installapi { +GlobalRecord *FrontendRecordsSlice::addGlobal( + StringRef Name, RecordLinkage Linkage, GlobalRecord::Kind GV, + const clang::AvailabilityInfo Avail, const Decl *D, const HeaderType Access, + SymbolFlags Flags) { + + auto *GR = llvm::MachO::RecordsSlice::addGlobal(Name, Linkage, GV, Flags); + FrontendRecords.insert({GR, FrontendAttrs{Avail, D, Access}}); + return GR; +} + +ObjCInterfaceRecord *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 = + llvm::MachO::RecordsSlice::addObjCInterface(Name, Linkage, SymType); + FrontendRecords.insert({ObjCR, FrontendAttrs{Avail, D, Access}}); + return ObjCR; +} + +std::optional +InstallAPIContext::findAndRecordFile(const FileEntry *FE, + const Preprocessor &PP) { + if (!FE) + return std::nullopt; + + // Check if header has been looked up already and whether it is something + // installapi should use. + auto It = KnownFiles.find(FE); + if (It != KnownFiles.end()) { + if (It->second != HeaderType::Unknown) + return It->second; + else + return std::nullopt; + } + + // If file was not found, search by how the header was + // included. This is primarily to resolve headers found + // in a different location than what passed directly as input. + StringRef IncludeName = PP.getHeaderSearchInfo().getIncludeNameForHeader(FE); + auto BackupIt = KnownIncludes.find(IncludeName.str()); + if (BackupIt != KnownIncludes.end()) { + KnownFiles[FE] = BackupIt->second; + return BackupIt->second; + } + + // Record that the file was found to avoid future string searches for the + // same file. + KnownFiles.insert({FE, HeaderType::Unknown}); + return std::nullopt; +} + +void InstallAPIContext::addKnownHeader(const HeaderFile &H) { + auto FE = FM->getFile(H.getPath()); + if (!FE) + return; // File does not exist. + KnownFiles[*FE] = H.getType(); + + if (!H.useIncludeName()) + return; + + KnownIncludes[H.getIncludeName()] = H.getType(); +} + static StringRef getFileExtension(clang::Language Lang) { switch (Lang) { default: @@ -31,7 +98,7 @@ static StringRef getFileExtension(clang::Language Lang) { } } -std::unique_ptr createInputBuffer(const InstallAPIContext &Ctx) { +std::unique_ptr createInputBuffer(InstallAPIContext &Ctx) { assert(Ctx.Type != HeaderType::Unknown && "unexpected access level for parsing"); SmallString<4096> Contents; @@ -47,6 +114,8 @@ std::unique_ptr createInputBuffer(const InstallAPIContext &Ctx) { OS << "<" << H.getIncludeName() << ">"; else OS << "\"" << H.getPath() << "\""; + + Ctx.addKnownHeader(H); } if (Contents.empty()) return nullptr; diff --git a/clang/lib/InstallAPI/Visitor.cpp b/clang/lib/InstallAPI/Visitor.cpp index 3806a69b52399..355a092520c3c 100644 --- a/clang/lib/InstallAPI/Visitor.cpp +++ b/clang/lib/InstallAPI/Visitor.cpp @@ -7,8 +7,8 @@ //===----------------------------------------------------------------------===// #include "clang/InstallAPI/Visitor.h" -#include "clang/AST/Availability.h" #include "clang/Basic/Linkage.h" +#include "clang/InstallAPI/Frontend.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" #include "llvm/IR/DataLayout.h" @@ -62,7 +62,66 @@ std::string InstallAPIVisitor::getBackendMangledName(Twine Name) const { return std::string(FinalName); } -/// Collect all global variables. +std::optional +InstallAPIVisitor::getAccessForDecl(const NamedDecl *D) const { + SourceLocation Loc = D->getLocation(); + if (Loc.isInvalid()) + return std::nullopt; + + // If the loc refers to a macro expansion, InstallAPI needs to first get the + // file location of the expansion. + auto FileLoc = SrcMgr.getFileLoc(Loc); + FileID ID = SrcMgr.getFileID(FileLoc); + if (ID.isInvalid()) + return std::nullopt; + + const FileEntry *FE = SrcMgr.getFileEntryForID(ID); + if (!FE) + return std::nullopt; + + auto Header = Ctx.findAndRecordFile(FE, PP); + if (!Header.has_value()) + return std::nullopt; + + HeaderType Access = Header.value(); + assert(Access != HeaderType::Unknown && "unexpected access level for global"); + return Access; +} + +/// Check if the interface itself or any of its super classes have an +/// exception attribute. InstallAPI needs to export an additional symbol +/// ("OBJC_EHTYPE_$CLASS_NAME") if any of the classes have the exception +/// attribute. +static bool hasObjCExceptionAttribute(const ObjCInterfaceDecl *D) { + for (; D != nullptr; D = D->getSuperClass()) + if (D->hasAttr()) + return true; + + return false; +} + +bool InstallAPIVisitor::VisitObjCInterfaceDecl(const ObjCInterfaceDecl *D) { + // Skip forward declaration for classes (@class) + if (!D->isThisDeclarationADefinition()) + return true; + + // Skip over declarations that access could not be collected for. + auto Access = getAccessForDecl(D); + if (!Access) + return true; + + StringRef Name = D->getObjCRuntimeNameAsString(); + const RecordLinkage Linkage = + isExported(D) ? RecordLinkage::Exported : RecordLinkage::Internal; + const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(D); + const bool IsEHType = + (!D->getASTContext().getLangOpts().ObjCRuntime.isFragile() && + hasObjCExceptionAttribute(D)); + + Ctx.Slice->addObjCInterface(Name, Linkage, Avail, D, *Access, IsEHType); + return true; +} + bool InstallAPIVisitor::VisitVarDecl(const VarDecl *D) { // Skip function parameters. if (isa(D)) @@ -81,13 +140,18 @@ bool InstallAPIVisitor::VisitVarDecl(const VarDecl *D) { D->getTemplateSpecializationKind() == TSK_Undeclared) return true; - // TODO: Capture SourceLocation & Availability for Decls. + // Skip over declarations that access could not collected for. + auto Access = getAccessForDecl(D); + if (!Access) + return true; + const RecordLinkage Linkage = isExported(D) ? RecordLinkage::Exported : RecordLinkage::Internal; const bool WeakDef = D->hasAttr(); const bool ThreadLocal = D->getTLSKind() != VarDecl::TLS_None; - Slice.addGlobal(getMangledName(D), Linkage, GlobalRecord::Kind::Variable, - getFlags(WeakDef, ThreadLocal)); + const AvailabilityInfo Avail = AvailabilityInfo::createFromDecl(D); + Ctx.Slice->addGlobal(getMangledName(D), Linkage, GlobalRecord::Kind::Variable, + Avail, D, *Access, getFlags(WeakDef, ThreadLocal)); return true; } diff --git a/clang/test/InstallAPI/objcclasses.test b/clang/test/InstallAPI/objcclasses.test new file mode 100644 index 0000000000000..d32291c64c472 --- /dev/null +++ b/clang/test/InstallAPI/objcclasses.test @@ -0,0 +1,85 @@ +// 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: -F%t -install_name /System/Library/Frameworks/Foo.framework/Foo \ +// RUN: %t/inputs.json -o %t/outputs.tbd -v 2>&1 | FileCheck %s --check-prefix=VERBOSE +// RUN: llvm-readtapi -compare %t/outputs.tbd %t/expected.tbd 2>&1 | FileCheck %s --allow-empty + +// VERBOSE: Public Headers: +// VERBOSE-NEXT: #import +// CHECK-NOT: error: +// CHECK-NOT: warning: + +//--- Foo.framework/Headers/Foo.h +// Ignore forward declaration. +@class NSObject; + +@interface Visible +@end + +__attribute__((visibility("hidden"))) +@interface Hidden +@end + +__attribute__((objc_exception)) +@interface Exception +@end + +//--- inputs.json.in +{ + "headers": [ { + "path" : "DSTROOT/Foo.framework/Headers/Foo.h", + "type" : "public" + }], + "version": "3" +} + +//--- expected.tbd +{ + "main_library": { + "compatibility_versions": [ + { + "version": "0" + } + ], + "current_versions": [ + { + "version": "0" + } + ], + "exported_symbols": [ + { + "data": { + "objc_class": [ + "Exception", + "Visible" + ], + "objc_eh_type": [ + "Exception" + ] + } + } + ], + "flags": [ + { + "attributes": [ + "not_app_extension_safe" + ] + } + ], + "install_names": [ + { + "name": "/System/Library/Frameworks/Foo.framework/Foo" + } + ], + "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 ff031e0236d0b..c6da1c80a673f 100644 --- a/clang/tools/clang-installapi/ClangInstallAPI.cpp +++ b/clang/tools/clang-installapi/ClangInstallAPI.cpp @@ -40,7 +40,7 @@ using namespace llvm::opt; using namespace llvm::MachO; static bool runFrontend(StringRef ProgName, bool Verbose, - const InstallAPIContext &Ctx, + InstallAPIContext &Ctx, llvm::vfs::InMemoryFileSystem *FS, const ArrayRef InitialArgs) { @@ -64,7 +64,7 @@ static bool runFrontend(StringRef ProgName, bool Verbose, // Create & run invocation. clang::tooling::ToolInvocation Invocation( - std::move(Args), std::make_unique(*Ctx.Slice), Ctx.FM); + std::move(Args), std::make_unique(Ctx), Ctx.FM); return Invocation.run(); } @@ -123,11 +123,13 @@ static bool run(ArrayRef Args, const char *ProgName) { return EXIT_FAILURE; // Execute and gather AST results. + // An invocation is ran for each unique target triple and for each header + // access level. llvm::MachO::Records FrontendResults; for (const auto &[Targ, Trip] : Opts.DriverOpts.Targets) { for (const HeaderType Type : {HeaderType::Public, HeaderType::Private, HeaderType::Project}) { - Ctx.Slice = std::make_shared(Trip); + Ctx.Slice = std::make_shared(Trip); Ctx.Type = Type; if (!runFrontend(ProgName, Opts.DriverOpts.Verbose, Ctx, InMemoryFileSystem.get(), Opts.getClangFrontendArgs()))