[APINotes] Upstream APINotesManager

This upstreams more of the Clang API Notes functionality that is
currently implemented in the Apple fork:
https://github.com/apple/llvm-project/tree/next/clang/lib/APINotes
This commit is contained in:
Egor Zhdan 2023-11-17 14:28:51 +01:00 committed by GitHub
parent ec42d547eb
commit f049395fc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 877 additions and 0 deletions

View File

@ -0,0 +1,175 @@
//===--- APINotesManager.h - Manage API Notes Files -------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_APINOTES_APINOTESMANAGER_H
#define LLVM_CLANG_APINOTES_APINOTESMANAGER_H
#include "clang/Basic/Module.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/PointerUnion.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/VersionTuple.h"
#include <memory>
#include <string>
namespace clang {
class DirectoryEntry;
class FileEntry;
class LangOptions;
class SourceManager;
namespace api_notes {
class APINotesReader;
/// The API notes manager helps find API notes associated with declarations.
///
/// API notes are externally-provided annotations for declarations that can
/// introduce new attributes (covering availability, nullability of
/// parameters/results, and so on) for specific declarations without directly
/// modifying the headers that contain those declarations.
///
/// The API notes manager is responsible for finding and loading the
/// external API notes files that correspond to a given header. Its primary
/// operation is \c findAPINotes(), which finds the API notes reader that
/// provides information about the declarations at that location.
class APINotesManager {
using ReaderEntry = llvm::PointerUnion<DirectoryEntryRef, APINotesReader *>;
SourceManager &SM;
/// Whether to implicitly search for API notes files based on the
/// source file from which an entity was declared.
bool ImplicitAPINotes;
/// The Swift version to use when interpreting versioned API notes.
llvm::VersionTuple SwiftVersion;
enum ReaderKind : unsigned { Public = 0, Private = 1 };
/// API notes readers for the current module.
///
/// There can be up to two of these, one for public headers and one
/// for private headers.
///
/// Not using std::unique_ptr to store these, since the reader pointers are
/// also stored in llvm::PointerUnion below.
APINotesReader *CurrentModuleReaders[2] = {nullptr, nullptr};
/// A mapping from header file directories to the API notes reader for
/// that directory, or a redirection to another directory entry that may
/// have more information, or NULL to indicate that there is no API notes
/// reader for this directory.
llvm::DenseMap<const DirectoryEntry *, ReaderEntry> Readers;
/// Load the API notes associated with the given file, whether it is
/// the binary or source form of API notes.
///
/// \returns the API notes reader for this file, or null if there is
/// a failure.
std::unique_ptr<APINotesReader> loadAPINotes(FileEntryRef APINotesFile);
/// Load the API notes associated with the given buffer, whether it is
/// the binary or source form of API notes.
///
/// \returns the API notes reader for this file, or null if there is
/// a failure.
std::unique_ptr<APINotesReader> loadAPINotes(StringRef Buffer);
/// Load the given API notes file for the given header directory.
///
/// \param HeaderDir The directory at which we
///
/// \returns true if an error occurred.
bool loadAPINotes(const DirectoryEntry *HeaderDir, FileEntryRef APINotesFile);
/// Look for API notes in the given directory.
///
/// This might find either a binary or source API notes.
OptionalFileEntryRef findAPINotesFile(DirectoryEntryRef Directory,
StringRef FileName,
bool WantPublic = true);
/// Attempt to load API notes for the given framework. A framework will have
/// the API notes file under either {FrameworkPath}/APINotes,
/// {FrameworkPath}/Headers or {FrameworkPath}/PrivateHeaders, while a
/// library will have the API notes simply in its directory.
///
/// \param FrameworkPath The path to the framework.
/// \param Public Whether to load the public API notes. Otherwise, attempt
/// to load the private API notes.
///
/// \returns the header directory entry (e.g., for Headers or PrivateHeaders)
/// for which the API notes were successfully loaded, or NULL if API notes
/// could not be loaded for any reason.
OptionalDirectoryEntryRef loadFrameworkAPINotes(llvm::StringRef FrameworkPath,
llvm::StringRef FrameworkName,
bool Public);
public:
APINotesManager(SourceManager &SM, const LangOptions &LangOpts);
~APINotesManager();
/// Set the Swift version to use when filtering API notes.
void setSwiftVersion(llvm::VersionTuple Version) {
this->SwiftVersion = Version;
}
/// Load the API notes for the current module.
///
/// \param M The current module.
/// \param LookInModule Whether to look inside the module itself.
/// \param SearchPaths The paths in which we should search for API notes
/// for the current module.
///
/// \returns true if API notes were successfully loaded, \c false otherwise.
bool loadCurrentModuleAPINotes(Module *M, bool LookInModule,
ArrayRef<std::string> SearchPaths);
/// Get FileEntry for the APINotes of the module that is currently being
/// compiled.
///
/// \param M The current module.
/// \param LookInModule Whether to look inside the directory of the current
/// module.
/// \param SearchPaths The paths in which we should search for API
/// notes for the current module.
///
/// \returns a vector of FileEntry where APINotes files are.
llvm::SmallVector<FileEntryRef, 2>
getCurrentModuleAPINotes(Module *M, bool LookInModule,
ArrayRef<std::string> SearchPaths);
/// Load Compiled API notes for current module.
///
/// \param Buffers Array of compiled API notes.
///
/// \returns true if API notes were successfully loaded, \c false otherwise.
bool loadCurrentModuleAPINotesFromBuffer(ArrayRef<StringRef> Buffers);
/// Retrieve the set of API notes readers for the current module.
ArrayRef<APINotesReader *> getCurrentModuleReaders() const {
bool HasPublic = CurrentModuleReaders[ReaderKind::Public];
bool HasPrivate = CurrentModuleReaders[ReaderKind::Private];
assert(!HasPrivate || HasPublic && "private module requires public module");
if (!HasPrivate && !HasPublic)
return {};
return ArrayRef(CurrentModuleReaders).slice(0, HasPrivate ? 2 : 1);
}
/// Find the API notes readers that correspond to the given source location.
llvm::SmallVector<APINotesReader *, 2> findAPINotes(SourceLocation Loc);
};
} // end namespace api_notes
} // end namespace clang
#endif

View File

@ -737,6 +737,9 @@ inline bool operator!=(const TypedefInfo &LHS, const TypedefInfo &RHS) {
return !(LHS == RHS);
}
/// The file extension used for the source representation of API notes.
static const constexpr char SOURCE_APINOTES_EXTENSION[] = "apinotes";
/// Opaque context ID used to refer to an Objective-C class or protocol or a C++
/// namespace.
class ContextID {

View File

@ -390,6 +390,19 @@ def note_mt_message : Note<"[rewriter] %0">;
def warn_arcmt_nsalloc_realloc : Warning<"[rewriter] call returns pointer to GC managed memory; it will become unmanaged in ARC">;
def err_arcmt_nsinvocation_ownership : Error<"NSInvocation's %0 is not safe to be used with an object with ownership other than __unsafe_unretained">;
// API notes
def err_apinotes_message : Error<"%0">;
def warn_apinotes_message : Warning<"%0">, InGroup<DiagGroup<"apinotes">>;
def note_apinotes_message : Note<"%0">;
class NonportablePrivateAPINotesPath : Warning<
"private API notes file for module '%0' should be named "
"'%0_private.apinotes', not '%1'">;
def warn_apinotes_private_case : NonportablePrivateAPINotesPath,
InGroup<DiagGroup<"nonportable-private-apinotes-path">>;
def warn_apinotes_private_case_system : NonportablePrivateAPINotesPath,
DefaultIgnore, InGroup<DiagGroup<"nonportable-private-system-apinotes-path">>;
// C++ for OpenCL.
def err_openclcxx_not_supported : Error<
"'%0' is not supported in C++ for OpenCL">;

View File

@ -402,6 +402,8 @@ LANGOPT(XLPragmaPack, 1, 0, "IBM XL #pragma pack handling")
LANGOPT(RetainCommentsFromSystemHeaders, 1, 0, "retain documentation comments from system headers in the AST")
LANGOPT(APINotes, 1, 0, "use external API notes")
LANGOPT(SanitizeAddressFieldPadding, 2, 0, "controls how aggressive is ASan "
"field padding (0: none, 1:least "
"aggressive, 2: more aggressive)")

View File

@ -178,6 +178,9 @@ public:
/// eventually be exposed, for use in "private" modules.
std::string ExportAsModule;
/// For the debug info, the path to this module's .apinotes file, if any.
std::string APINotesFile;
/// Does this Module is a named module of a standard named module?
bool isNamedModule() const {
switch (Kind) {

View File

@ -0,0 +1,85 @@
//=== SourceMgrAdapter.h - SourceMgr to SourceManager Adapter ---*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file provides an adapter that maps diagnostics from llvm::SourceMgr
// to Clang's SourceManager.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_SOURCEMGRADAPTER_H
#define LLVM_CLANG_SOURCEMGRADAPTER_H
#include "clang/Basic/SourceManager.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/SourceMgr.h"
#include <string>
#include <utility>
namespace clang {
class DiagnosticsEngine;
class FileEntry;
/// An adapter that can be used to translate diagnostics from one or more
/// llvm::SourceMgr instances to a ,
class SourceMgrAdapter {
/// Clang source manager.
SourceManager &SrcMgr;
/// Clang diagnostics engine.
DiagnosticsEngine &Diagnostics;
/// Diagnostic IDs for errors, warnings, and notes.
unsigned ErrorDiagID, WarningDiagID, NoteDiagID;
/// The default file to use when mapping buffers.
OptionalFileEntryRef DefaultFile;
/// A mapping from (LLVM source manager, buffer ID) pairs to the
/// corresponding file ID within the Clang source manager.
llvm::DenseMap<std::pair<const llvm::SourceMgr *, unsigned>, FileID>
FileIDMapping;
/// Diagnostic handler.
static void handleDiag(const llvm::SMDiagnostic &Diag, void *Context);
public:
/// Create a new \c SourceMgr adaptor that maps to the given source
/// manager and diagnostics engine.
SourceMgrAdapter(SourceManager &SM, DiagnosticsEngine &Diagnostics,
unsigned ErrorDiagID, unsigned WarningDiagID,
unsigned NoteDiagID,
OptionalFileEntryRef DefaultFile = std::nullopt);
~SourceMgrAdapter();
/// Map a source location in the given LLVM source manager to its
/// corresponding location in the Clang source manager.
SourceLocation mapLocation(const llvm::SourceMgr &LLVMSrcMgr,
llvm::SMLoc Loc);
/// Map a source range in the given LLVM source manager to its corresponding
/// range in the Clang source manager.
SourceRange mapRange(const llvm::SourceMgr &LLVMSrcMgr, llvm::SMRange Range);
/// Handle the given diagnostic from an LLVM source manager.
void handleDiag(const llvm::SMDiagnostic &Diag);
/// Retrieve the diagnostic handler to use with the underlying SourceMgr.
llvm::SourceMgr::DiagHandlerTy getDiagHandler() {
return &SourceMgrAdapter::handleDiag;
}
/// Retrieve the context to use with the diagnostic handler produced by
/// \c getDiagHandler().
void *getDiagContext() { return this; }
};
} // end namespace clang
#endif

View File

@ -0,0 +1,458 @@
//===--- APINotesManager.cpp - Manage API Notes Files ---------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "clang/APINotes/APINotesManager.h"
#include "clang/APINotes/APINotesReader.h"
#include "clang/APINotes/APINotesYAMLCompiler.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/SourceMgrAdapter.h"
#include "clang/Basic/Version.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/PrettyStackTrace.h"
using namespace clang;
using namespace api_notes;
#define DEBUG_TYPE "API Notes"
STATISTIC(NumHeaderAPINotes, "non-framework API notes files loaded");
STATISTIC(NumPublicFrameworkAPINotes, "framework public API notes loaded");
STATISTIC(NumPrivateFrameworkAPINotes, "framework private API notes loaded");
STATISTIC(NumFrameworksSearched, "frameworks searched");
STATISTIC(NumDirectoriesSearched, "header directories searched");
STATISTIC(NumDirectoryCacheHits, "directory cache hits");
namespace {
/// Prints two successive strings, which much be kept alive as long as the
/// PrettyStackTrace entry.
class PrettyStackTraceDoubleString : public llvm::PrettyStackTraceEntry {
StringRef First, Second;
public:
PrettyStackTraceDoubleString(StringRef First, StringRef Second)
: First(First), Second(Second) {}
void print(raw_ostream &OS) const override { OS << First << Second; }
};
} // namespace
APINotesManager::APINotesManager(SourceManager &SM, const LangOptions &LangOpts)
: SM(SM), ImplicitAPINotes(LangOpts.APINotes) {}
APINotesManager::~APINotesManager() {
// Free the API notes readers.
for (const auto &Entry : Readers) {
if (auto Reader = Entry.second.dyn_cast<APINotesReader *>())
delete Reader;
}
delete CurrentModuleReaders[ReaderKind::Public];
delete CurrentModuleReaders[ReaderKind::Private];
}
std::unique_ptr<APINotesReader>
APINotesManager::loadAPINotes(FileEntryRef APINotesFile) {
PrettyStackTraceDoubleString Trace("Loading API notes from ",
APINotesFile.getName());
// Open the source file.
auto SourceFileID = SM.getOrCreateFileID(APINotesFile, SrcMgr::C_User);
auto SourceBuffer = SM.getBufferOrNone(SourceFileID, SourceLocation());
if (!SourceBuffer)
return nullptr;
// Compile the API notes source into a buffer.
// FIXME: Either propagate OSType through or, better yet, improve the binary
// APINotes format to maintain complete availability information.
// FIXME: We don't even really need to go through the binary format at all;
// we're just going to immediately deserialize it again.
llvm::SmallVector<char, 1024> APINotesBuffer;
std::unique_ptr<llvm::MemoryBuffer> CompiledBuffer;
{
SourceMgrAdapter SMAdapter(
SM, SM.getDiagnostics(), diag::err_apinotes_message,
diag::warn_apinotes_message, diag::note_apinotes_message, APINotesFile);
llvm::raw_svector_ostream OS(APINotesBuffer);
if (api_notes::compileAPINotes(
SourceBuffer->getBuffer(), SM.getFileEntryForID(SourceFileID), OS,
SMAdapter.getDiagHandler(), SMAdapter.getDiagContext()))
return nullptr;
// Make a copy of the compiled form into the buffer.
CompiledBuffer = llvm::MemoryBuffer::getMemBufferCopy(
StringRef(APINotesBuffer.data(), APINotesBuffer.size()));
}
// Load the binary form we just compiled.
auto Reader = APINotesReader::Create(std::move(CompiledBuffer), SwiftVersion);
assert(Reader && "Could not load the API notes we just generated?");
return Reader;
}
std::unique_ptr<APINotesReader>
APINotesManager::loadAPINotes(StringRef Buffer) {
llvm::SmallVector<char, 1024> APINotesBuffer;
std::unique_ptr<llvm::MemoryBuffer> CompiledBuffer;
SourceMgrAdapter SMAdapter(
SM, SM.getDiagnostics(), diag::err_apinotes_message,
diag::warn_apinotes_message, diag::note_apinotes_message, std::nullopt);
llvm::raw_svector_ostream OS(APINotesBuffer);
if (api_notes::compileAPINotes(Buffer, nullptr, OS,
SMAdapter.getDiagHandler(),
SMAdapter.getDiagContext()))
return nullptr;
CompiledBuffer = llvm::MemoryBuffer::getMemBufferCopy(
StringRef(APINotesBuffer.data(), APINotesBuffer.size()));
auto Reader = APINotesReader::Create(std::move(CompiledBuffer), SwiftVersion);
assert(Reader && "Could not load the API notes we just generated?");
return Reader;
}
bool APINotesManager::loadAPINotes(const DirectoryEntry *HeaderDir,
FileEntryRef APINotesFile) {
assert(Readers.find(HeaderDir) == Readers.end());
if (auto Reader = loadAPINotes(APINotesFile)) {
Readers[HeaderDir] = Reader.release();
return false;
}
Readers[HeaderDir] = nullptr;
return true;
}
OptionalFileEntryRef
APINotesManager::findAPINotesFile(DirectoryEntryRef Directory,
StringRef Basename, bool WantPublic) {
FileManager &FM = SM.getFileManager();
llvm::SmallString<128> Path(Directory.getName());
StringRef Suffix = WantPublic ? "" : "_private";
// Look for the source API notes file.
llvm::sys::path::append(Path, llvm::Twine(Basename) + Suffix + "." +
SOURCE_APINOTES_EXTENSION);
return FM.getOptionalFileRef(Path, /*Open*/ true);
}
OptionalDirectoryEntryRef APINotesManager::loadFrameworkAPINotes(
llvm::StringRef FrameworkPath, llvm::StringRef FrameworkName, bool Public) {
FileManager &FM = SM.getFileManager();
llvm::SmallString<128> Path(FrameworkPath);
unsigned FrameworkNameLength = Path.size();
StringRef Suffix = Public ? "" : "_private";
// Form the path to the APINotes file.
llvm::sys::path::append(Path, "APINotes");
llvm::sys::path::append(Path, (llvm::Twine(FrameworkName) + Suffix + "." +
SOURCE_APINOTES_EXTENSION));
// Try to open the APINotes file.
auto APINotesFile = FM.getOptionalFileRef(Path);
if (!APINotesFile)
return std::nullopt;
// Form the path to the corresponding header directory.
Path.resize(FrameworkNameLength);
llvm::sys::path::append(Path, Public ? "Headers" : "PrivateHeaders");
// Try to access the header directory.
auto HeaderDir = FM.getOptionalDirectoryRef(Path);
if (!HeaderDir)
return std::nullopt;
// Try to load the API notes.
if (loadAPINotes(*HeaderDir, *APINotesFile))
return std::nullopt;
// Success: return the header directory.
if (Public)
++NumPublicFrameworkAPINotes;
else
++NumPrivateFrameworkAPINotes;
return *HeaderDir;
}
static void checkPrivateAPINotesName(DiagnosticsEngine &Diags,
const FileEntry *File, const Module *M) {
if (File->tryGetRealPathName().empty())
return;
StringRef RealFileName =
llvm::sys::path::filename(File->tryGetRealPathName());
StringRef RealStem = llvm::sys::path::stem(RealFileName);
if (RealStem.endswith("_private"))
return;
unsigned DiagID = diag::warn_apinotes_private_case;
if (M->IsSystem)
DiagID = diag::warn_apinotes_private_case_system;
Diags.Report(SourceLocation(), DiagID) << M->Name << RealFileName;
}
/// \returns true if any of \p module's immediate submodules are defined in a
/// private module map
static bool hasPrivateSubmodules(const Module *M) {
return llvm::any_of(M->submodules(), [](const Module *Submodule) {
return Submodule->ModuleMapIsPrivate;
});
}
llvm::SmallVector<FileEntryRef, 2>
APINotesManager::getCurrentModuleAPINotes(Module *M, bool LookInModule,
ArrayRef<std::string> SearchPaths) {
FileManager &FM = SM.getFileManager();
auto ModuleName = M->getTopLevelModuleName();
llvm::SmallVector<FileEntryRef, 2> APINotes;
// First, look relative to the module itself.
if (LookInModule) {
// Local function to try loading an API notes file in the given directory.
auto tryAPINotes = [&](DirectoryEntryRef Dir, bool WantPublic) {
if (auto File = findAPINotesFile(Dir, ModuleName, WantPublic)) {
if (!WantPublic)
checkPrivateAPINotesName(SM.getDiagnostics(), *File, M);
APINotes.push_back(*File);
}
};
if (M->IsFramework) {
// For frameworks, we search in the "Headers" or "PrivateHeaders"
// subdirectory.
//
// Public modules:
// - Headers/Foo.apinotes
// - PrivateHeaders/Foo_private.apinotes (if there are private submodules)
// Private modules:
// - PrivateHeaders/Bar.apinotes (except that 'Bar' probably already has
// the word "Private" in it in practice)
llvm::SmallString<128> Path(M->Directory->getName());
if (!M->ModuleMapIsPrivate) {
unsigned PathLen = Path.size();
llvm::sys::path::append(Path, "Headers");
if (auto APINotesDir = FM.getOptionalDirectoryRef(Path))
tryAPINotes(*APINotesDir, /*wantPublic=*/true);
Path.resize(PathLen);
}
if (M->ModuleMapIsPrivate || hasPrivateSubmodules(M)) {
llvm::sys::path::append(Path, "PrivateHeaders");
if (auto PrivateAPINotesDir = FM.getOptionalDirectoryRef(Path))
tryAPINotes(*PrivateAPINotesDir,
/*wantPublic=*/M->ModuleMapIsPrivate);
}
} else {
// Public modules:
// - Foo.apinotes
// - Foo_private.apinotes (if there are private submodules)
// Private modules:
// - Bar.apinotes (except that 'Bar' probably already has the word
// "Private" in it in practice)
tryAPINotes(*M->Directory, /*wantPublic=*/true);
if (!M->ModuleMapIsPrivate && hasPrivateSubmodules(M))
tryAPINotes(*M->Directory, /*wantPublic=*/false);
}
if (!APINotes.empty())
return APINotes;
}
// Second, look for API notes for this module in the module API
// notes search paths.
for (const auto &SearchPath : SearchPaths) {
if (auto SearchDir = FM.getOptionalDirectoryRef(SearchPath)) {
if (auto File = findAPINotesFile(*SearchDir, ModuleName)) {
APINotes.push_back(*File);
return APINotes;
}
}
}
// Didn't find any API notes.
return APINotes;
}
bool APINotesManager::loadCurrentModuleAPINotes(
Module *M, bool LookInModule, ArrayRef<std::string> SearchPaths) {
assert(!CurrentModuleReaders[ReaderKind::Public] &&
"Already loaded API notes for the current module?");
auto APINotes = getCurrentModuleAPINotes(M, LookInModule, SearchPaths);
unsigned NumReaders = 0;
for (auto File : APINotes) {
CurrentModuleReaders[NumReaders++] = loadAPINotes(File).release();
if (!getCurrentModuleReaders().empty())
M->APINotesFile = File.getName().str();
}
return NumReaders > 0;
}
bool APINotesManager::loadCurrentModuleAPINotesFromBuffer(
ArrayRef<StringRef> Buffers) {
unsigned NumReader = 0;
for (auto Buf : Buffers) {
auto Reader = loadAPINotes(Buf);
assert(Reader && "Could not load the API notes we just generated?");
CurrentModuleReaders[NumReader++] = Reader.release();
}
return NumReader;
}
llvm::SmallVector<APINotesReader *, 2>
APINotesManager::findAPINotes(SourceLocation Loc) {
llvm::SmallVector<APINotesReader *, 2> Results;
// If there are readers for the current module, return them.
if (!getCurrentModuleReaders().empty()) {
Results.append(getCurrentModuleReaders().begin(),
getCurrentModuleReaders().end());
return Results;
}
// If we're not allowed to implicitly load API notes files, we're done.
if (!ImplicitAPINotes)
return Results;
// If we don't have source location information, we're done.
if (Loc.isInvalid())
return Results;
// API notes are associated with the expansion location. Retrieve the
// file for this location.
SourceLocation ExpansionLoc = SM.getExpansionLoc(Loc);
FileID ID = SM.getFileID(ExpansionLoc);
if (ID.isInvalid())
return Results;
OptionalFileEntryRef File = SM.getFileEntryRefForID(ID);
if (!File)
return Results;
// Look for API notes in the directory corresponding to this file, or one of
// its its parent directories.
OptionalDirectoryEntryRef Dir = File->getDir();
FileManager &FileMgr = SM.getFileManager();
llvm::SetVector<const DirectoryEntry *,
SmallVector<const DirectoryEntry *, 4>,
llvm::SmallPtrSet<const DirectoryEntry *, 4>>
DirsVisited;
do {
// Look for an API notes reader for this header search directory.
auto Known = Readers.find(*Dir);
// If we already know the answer, chase it.
if (Known != Readers.end()) {
++NumDirectoryCacheHits;
// We've been redirected to another directory for answers. Follow it.
if (Known->second && Known->second.is<DirectoryEntryRef>()) {
DirsVisited.insert(*Dir);
Dir = Known->second.get<DirectoryEntryRef>();
continue;
}
// We have the answer.
if (auto Reader = Known->second.dyn_cast<APINotesReader *>())
Results.push_back(Reader);
break;
}
// Look for API notes corresponding to this directory.
StringRef Path = Dir->getName();
if (llvm::sys::path::extension(Path) == ".framework") {
// If this is a framework directory, check whether there are API notes
// in the APINotes subdirectory.
auto FrameworkName = llvm::sys::path::stem(Path);
++NumFrameworksSearched;
// Look for API notes for both the public and private headers.
OptionalDirectoryEntryRef PublicDir =
loadFrameworkAPINotes(Path, FrameworkName, /*Public=*/true);
OptionalDirectoryEntryRef PrivateDir =
loadFrameworkAPINotes(Path, FrameworkName, /*Public=*/false);
if (PublicDir || PrivateDir) {
// We found API notes: don't ever look past the framework directory.
Readers[*Dir] = nullptr;
// Pretend we found the result in the public or private directory,
// as appropriate. All headers should be in one of those two places,
// but be defensive here.
if (!DirsVisited.empty()) {
if (PublicDir && DirsVisited.back() == *PublicDir) {
DirsVisited.pop_back();
Dir = *PublicDir;
} else if (PrivateDir && DirsVisited.back() == *PrivateDir) {
DirsVisited.pop_back();
Dir = *PrivateDir;
}
}
// Grab the result.
if (auto Reader = Readers[*Dir].dyn_cast<APINotesReader *>())
Results.push_back(Reader);
break;
}
} else {
// Look for an APINotes file in this directory.
llvm::SmallString<128> APINotesPath(Dir->getName());
llvm::sys::path::append(
APINotesPath, (llvm::Twine("APINotes.") + SOURCE_APINOTES_EXTENSION));
// If there is an API notes file here, try to load it.
++NumDirectoriesSearched;
if (auto APINotesFile = FileMgr.getOptionalFileRef(APINotesPath)) {
if (!loadAPINotes(*Dir, *APINotesFile)) {
++NumHeaderAPINotes;
if (auto Reader = Readers[*Dir].dyn_cast<APINotesReader *>())
Results.push_back(Reader);
break;
}
}
}
// We didn't find anything. Look at the parent directory.
if (!DirsVisited.insert(*Dir)) {
Dir = std::nullopt;
break;
}
StringRef ParentPath = llvm::sys::path::parent_path(Path);
while (llvm::sys::path::stem(ParentPath) == "..")
ParentPath = llvm::sys::path::parent_path(ParentPath);
Dir = ParentPath.empty() ? std::nullopt
: FileMgr.getOptionalDirectoryRef(ParentPath);
} while (Dir);
// Path compression for all of the directories we visited, redirecting
// them to the directory we ended on. If no API notes were found, the
// resulting directory will be NULL, indicating no API notes.
for (const auto Visited : DirsVisited)
Readers[Visited] = Dir ? ReaderEntry(*Dir) : ReaderEntry();
return Results;
}

View File

@ -3,6 +3,7 @@ set(LLVM_LINK_COMPONENTS
BitstreamReader
Support)
add_clang_library(clangAPINotes
APINotesManager.cpp
APINotesReader.cpp
APINotesTypes.cpp
APINotesWriter.cpp

View File

@ -85,6 +85,7 @@ add_clang_library(clangBasic
Sarif.cpp
SourceLocation.cpp
SourceManager.cpp
SourceMgrAdapter.cpp
Stack.cpp
TargetID.cpp
TargetInfo.cpp

View File

@ -0,0 +1,136 @@
//=== SourceMgrAdapter.cpp - SourceMgr to SourceManager Adapter -----------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements the adapter that maps diagnostics from llvm::SourceMgr
// to Clang's SourceManager.
//
//===----------------------------------------------------------------------===//
#include "clang/Basic/SourceMgrAdapter.h"
#include "clang/Basic/Diagnostic.h"
using namespace clang;
void SourceMgrAdapter::handleDiag(const llvm::SMDiagnostic &Diag,
void *Context) {
static_cast<SourceMgrAdapter *>(Context)->handleDiag(Diag);
}
SourceMgrAdapter::SourceMgrAdapter(SourceManager &SM,
DiagnosticsEngine &Diagnostics,
unsigned ErrorDiagID, unsigned WarningDiagID,
unsigned NoteDiagID,
OptionalFileEntryRef DefaultFile)
: SrcMgr(SM), Diagnostics(Diagnostics), ErrorDiagID(ErrorDiagID),
WarningDiagID(WarningDiagID), NoteDiagID(NoteDiagID),
DefaultFile(DefaultFile) {}
SourceMgrAdapter::~SourceMgrAdapter() {}
SourceLocation SourceMgrAdapter::mapLocation(const llvm::SourceMgr &LLVMSrcMgr,
llvm::SMLoc Loc) {
// Map invalid locations.
if (!Loc.isValid())
return SourceLocation();
// Find the buffer containing the location.
unsigned BufferID = LLVMSrcMgr.FindBufferContainingLoc(Loc);
if (!BufferID)
return SourceLocation();
// If we haven't seen this buffer before, copy it over.
auto Buffer = LLVMSrcMgr.getMemoryBuffer(BufferID);
auto KnownBuffer = FileIDMapping.find(std::make_pair(&LLVMSrcMgr, BufferID));
if (KnownBuffer == FileIDMapping.end()) {
FileID FileID;
if (DefaultFile) {
// Map to the default file.
FileID = SrcMgr.getOrCreateFileID(*DefaultFile, SrcMgr::C_User);
// Only do this once.
DefaultFile = std::nullopt;
} else {
// Make a copy of the memory buffer.
StringRef bufferName = Buffer->getBufferIdentifier();
auto bufferCopy = std::unique_ptr<llvm::MemoryBuffer>(
llvm::MemoryBuffer::getMemBufferCopy(Buffer->getBuffer(),
bufferName));
// Add this memory buffer to the Clang source manager.
FileID = SrcMgr.createFileID(std::move(bufferCopy));
}
// Save the mapping.
KnownBuffer = FileIDMapping
.insert(std::make_pair(
std::make_pair(&LLVMSrcMgr, BufferID), FileID))
.first;
}
// Translate the offset into the file.
unsigned Offset = Loc.getPointer() - Buffer->getBufferStart();
return SrcMgr.getLocForStartOfFile(KnownBuffer->second)
.getLocWithOffset(Offset);
}
SourceRange SourceMgrAdapter::mapRange(const llvm::SourceMgr &LLVMSrcMgr,
llvm::SMRange Range) {
if (!Range.isValid())
return SourceRange();
SourceLocation Start = mapLocation(LLVMSrcMgr, Range.Start);
SourceLocation End = mapLocation(LLVMSrcMgr, Range.End);
return SourceRange(Start, End);
}
void SourceMgrAdapter::handleDiag(const llvm::SMDiagnostic &Diag) {
// Map the location.
SourceLocation Loc;
if (auto *LLVMSrcMgr = Diag.getSourceMgr())
Loc = mapLocation(*LLVMSrcMgr, Diag.getLoc());
// Extract the message.
StringRef Message = Diag.getMessage();
// Map the diagnostic kind.
unsigned DiagID;
switch (Diag.getKind()) {
case llvm::SourceMgr::DK_Error:
DiagID = ErrorDiagID;
break;
case llvm::SourceMgr::DK_Warning:
DiagID = WarningDiagID;
break;
case llvm::SourceMgr::DK_Remark:
llvm_unreachable("remarks not implemented");
case llvm::SourceMgr::DK_Note:
DiagID = NoteDiagID;
break;
}
// Report the diagnostic.
DiagnosticBuilder Builder = Diagnostics.Report(Loc, DiagID) << Message;
if (auto *LLVMSrcMgr = Diag.getSourceMgr()) {
// Translate ranges.
SourceLocation StartOfLine = Loc.getLocWithOffset(-Diag.getColumnNo());
for (auto Range : Diag.getRanges()) {
Builder << SourceRange(StartOfLine.getLocWithOffset(Range.first),
StartOfLine.getLocWithOffset(Range.second));
}
// Translate Fix-Its.
for (const llvm::SMFixIt &FixIt : Diag.getFixIts()) {
CharSourceRange Range(mapRange(*LLVMSrcMgr, FixIt.getRange()), false);
Builder << FixItHint::CreateReplacement(Range, FixIt.getText());
}
}
}