Skip to content

Commit

Permalink
[InstallAPI] Collect frontend attributes & ObjCInterface decls (#83378)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
cyndyishida committed Mar 1, 2024
1 parent f31ac3c commit 17ede03
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 22 deletions.
30 changes: 27 additions & 3 deletions clang/include/clang/InstallAPI/Context.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -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<HeaderType> findAndRecordFile(const FileEntry *FE,
const Preprocessor &PP);

private:
using HeaderMap = llvm::DenseMap<const FileEntry *, HeaderType>;

// 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<StringRef, HeaderType> KnownIncludes;
};

} // namespace installapi
Expand Down
68 changes: 62 additions & 6 deletions clang/include/clang/InstallAPI/Frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down
17 changes: 13 additions & 4 deletions clang/include/clang/InstallAPI/Visitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -27,20 +27,29 @@ 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;

/// 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;
};
Expand Down
71 changes: 70 additions & 1 deletion clang/lib/InstallAPI/Frontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<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()) {
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:
Expand All @@ -31,7 +98,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;
Expand All @@ -47,6 +114,8 @@ std::unique_ptr<MemoryBuffer> createInputBuffer(const InstallAPIContext &Ctx) {
OS << "<" << H.getIncludeName() << ">";
else
OS << "\"" << H.getPath() << "\"";

Ctx.addKnownHeader(H);
}
if (Contents.empty())
return nullptr;
Expand Down
74 changes: 69 additions & 5 deletions clang/lib/InstallAPI/Visitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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))
Expand All @@ -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;
}

Expand Down

0 comments on commit 17ede03

Please sign in to comment.