[clang] [InstallAPI] Collect frontend attributes & ObjCInterface decls (PR #83378)
Cyndy Ishida via cfe-commits
cfe-commits at lists.llvm.org
Wed Feb 28 21:36:17 PST 2024
https://github.com/cyndyishida created https://github.com/llvm/llvm-project/pull/83378
* This patch introduces a container class, for holding records and attributes only collectible from the clang frontend, which is a subclass of `llvm::MachO::RecordsSlice`
* This also prunes out collecting declarations from headers that aren't considered input to installapi.
* Uses these constructs for collecting global objective-c interfaces.
>From 908e41485a148539b3d0699c55291f4f4e819858 Mon Sep 17 00:00:00 2001
From: Cyndy Ishida <cyndy_ishida at apple.com>
Date: Wed, 28 Feb 2024 14:15:48 -0800
Subject: [PATCH] [InstallAPI] Collect frontend attributes during &
ObjCInterface Records.
* This patch introduces a inherited container class for holding
records and attributes only collectable from the clang frontend.
* This also prunes out collecting declarations from headers that aren't
considered input to installapi.
* Use these constructs for collecting global objective-c interfaces.
---
clang/include/clang/InstallAPI/Context.h | 27 +++++-
clang/include/clang/InstallAPI/Frontend.h | 68 +++++++++++++--
clang/include/clang/InstallAPI/Visitor.h | 17 +++-
clang/lib/InstallAPI/Frontend.cpp | 72 +++++++++++++++-
clang/lib/InstallAPI/Visitor.cpp | 74 ++++++++++++++--
clang/test/InstallAPI/objcclasses.test | 86 +++++++++++++++++++
.../clang-installapi/ClangInstallAPI.cpp | 8 +-
7 files changed, 330 insertions(+), 22 deletions(-)
create mode 100644 clang/test/InstallAPI/objcclasses.test
diff --git a/clang/include/clang/InstallAPI/Context.h b/clang/include/clang/InstallAPI/Context.h
index 3e2046642c7fe8..3f8167b12dcd23 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<llvm::MachO::RecordsSlice> Slice;
+ std::shared_ptr<FrontendRecordsSlice> Slice;
/// FileManager for all I/O operations.
FileManager *FM = nullptr;
@@ -50,6 +50,27 @@ 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<HeaderType> findAndRecordFile(const FileEntry *FE,
+ const Preprocessor &PP);
+
+private:
+ using HeaderMap = llvm::DenseMap<const FileEntry *, HeaderType>;
+
+ HeaderMap KnownFiles;
+ llvm::DenseMap<StringRef, HeaderType> KnownIncludes;
+ std::set<const FileEntry *> UnusedFiles;
};
} // namespace installapi
diff --git a/clang/include/clang/InstallAPI/Frontend.h b/clang/include/clang/InstallAPI/Frontend.h
index 7ee87ae028d079..d72b4680fde400 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<llvm::MachO::Record *, FrontendAttrs> FrontendRecords;
+};
+
/// Create a buffer that contains all headers to scan
/// for global symbols with.
-std::unique_ptr<llvm::MemoryBuffer>
-createInputBuffer(const InstallAPIContext &Ctx);
+std::unique_ptr<llvm::MemoryBuffer> 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<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
StringRef InFile) override {
- return std::make_unique<InstallAPIVisitor>(CI.getASTContext(), Records);
+ return std::make_unique<InstallAPIVisitor>(
+ 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 95d669688e4f9c..60a05005df841a 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<InstallAPIVisitor> {
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<HeaderType> getAccessForDecl(const NamedDecl *D) const;
- llvm::MachO::RecordsSlice &Slice;
+ InstallAPIContext &Ctx;
+ SourceManager &SrcMgr;
+ Preprocessor &PP;
std::unique_ptr<clang::ItaniumMangleContext> MC;
StringRef Layout;
};
diff --git a/clang/lib/InstallAPI/Frontend.cpp b/clang/lib/InstallAPI/Frontend.cpp
index 9f675ef7d1bd22..a299d05e90d14b 100644
--- a/clang/lib/InstallAPI/Frontend.cpp
+++ b/clang/lib/InstallAPI/Frontend.cpp
@@ -16,6 +16,74 @@ 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);
+ if (!FrontendRecords.contains(GR))
+ 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);
+ if (!FrontendRecords.contains(ObjCR))
+ FrontendRecords.insert({ObjCR, FrontendAttrs{Avail, D, Access}});
+ return ObjCR;
+}
+
+std::optional<HeaderType>
+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())
+ return It->second;
+ auto UnusedIt = UnusedFiles.find(FE);
+ if (UnusedIt != UnusedFiles.end())
+ 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.
+ UnusedFiles.insert(FE);
+ return std::nullopt;
+}
+
+void InstallAPIContext::addKnownHeader(const HeaderFile &H) {
+ auto FE = FM->getFile(H.getPath());
+ if (!FE)
+ return; // File do 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 +99,7 @@ static StringRef getFileExtension(clang::Language Lang) {
}
}
-std::unique_ptr<MemoryBuffer> createInputBuffer(const InstallAPIContext &Ctx) {
+std::unique_ptr<MemoryBuffer> createInputBuffer(InstallAPIContext &Ctx) {
assert(Ctx.Type != HeaderType::Unknown &&
"unexpected access level for parsing");
SmallString<4096> Contents;
@@ -47,6 +115,8 @@ std::unique_ptr<MemoryBuffer> 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 3806a69b52399b..355a092520c3cd 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<HeaderType>
+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<ObjCExceptionAttr>())
+ 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<ParmVarDecl>(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<WeakAttr>();
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 00000000000000..4215f67ebc9346
--- /dev/null
+++ b/clang/test/InstallAPI/objcclasses.test
@@ -0,0 +1,86 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: sed -e "s|DSTROOT|%/t|g" %t/inputs.json.in > %t/inputs.json
+
+/// Check multiple targets are captured.
+// 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 <Foo/Foo.h>
+// CHECK-NOT: error:
+// CHECK-NOT: warning:
+
+//--- Foo.framework/Headers/Foo.h
+// Ignore forward declaration.
+ at class NSObject;
+
+ at interface Visible
+ at end
+
+__attribute__((visibility("hidden")))
+ at interface Hidden
+ at end
+
+__attribute__((objc_exception))
+ at interface Exception
+ at 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 43c9fca0a82eec..cc15b72bacf12f 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<std::string> 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<InstallAPIAction>(*Ctx.Slice), Ctx.FM);
+ std::move(Args), std::make_unique<InstallAPIAction>(Ctx), Ctx.FM);
return Invocation.run();
}
@@ -124,11 +124,13 @@ static bool run(ArrayRef<const char *> 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<RecordsSlice>(Trip);
+ Ctx.Slice = std::make_shared<FrontendRecordsSlice>(Trip);
Ctx.Type = Type;
if (!runFrontend(ProgName, Opts.DriverOpts.Verbose, Ctx,
InMemoryFileSystem.get(), Opts.getClangFrontendArgs()))
More information about the cfe-commits
mailing list