https://github.com/egorzhdan created https://github.com/llvm/llvm-project/pull/72389
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 >From 2550d96acc9ce134b4f7359af0c75e3bd3146484 Mon Sep 17 00:00:00 2001 From: Egor Zhdan <e_zh...@apple.com> Date: Wed, 15 Nov 2023 13:46:54 +0000 Subject: [PATCH] [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 --- .../include/clang/APINotes/APINotesManager.h | 163 ++++++ clang/include/clang/APINotes/Types.h | 3 + .../clang/Basic/DiagnosticCommonKinds.td | 13 + clang/include/clang/Basic/LangOptions.def | 2 + clang/include/clang/Basic/Module.h | 3 + clang/include/clang/Basic/SourceMgrAdapter.h | 85 ++++ clang/lib/APINotes/APINotesManager.cpp | 469 ++++++++++++++++++ clang/lib/APINotes/CMakeLists.txt | 1 + clang/lib/Basic/CMakeLists.txt | 1 + clang/lib/Basic/SourceMgrAdapter.cpp | 136 +++++ 10 files changed, 876 insertions(+) create mode 100644 clang/include/clang/APINotes/APINotesManager.h create mode 100644 clang/include/clang/Basic/SourceMgrAdapter.h create mode 100644 clang/lib/APINotes/APINotesManager.cpp create mode 100644 clang/lib/Basic/SourceMgrAdapter.cpp diff --git a/clang/include/clang/APINotes/APINotesManager.h b/clang/include/clang/APINotes/APINotesManager.h new file mode 100644 index 000000000000000..d82d1fa2d544a1d --- /dev/null +++ b/clang/include/clang/APINotes/APINotesManager.h @@ -0,0 +1,163 @@ +//===--- 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; + + /// API notes readers for the current module. + /// + /// There can be up to two of these, one for public headers and one + /// for private headers. + 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. + /// + /// \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 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 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 { + unsigned numReaders = + static_cast<unsigned>(CurrentModuleReaders[0] != nullptr) + + static_cast<unsigned>(CurrentModuleReaders[1] != nullptr); + return ArrayRef(CurrentModuleReaders).slice(0, numReaders); + } + + /// 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 diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h index 79595abcf7d02d9..d9c88adbf768e37 100644 --- a/clang/include/clang/APINotes/Types.h +++ b/clang/include/clang/APINotes/Types.h @@ -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 char SOURCE_APINOTES_EXTENSION[] = "apinotes"; + /// Opaque context ID used to refer to an Objective-C class or protocol or a C++ /// namespace. class ContextID { diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td b/clang/include/clang/Basic/DiagnosticCommonKinds.td index 906ca6b8e47c50e..55a237f40af459f 100644 --- a/clang/include/clang/Basic/DiagnosticCommonKinds.td +++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td @@ -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">; diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index c541ccefdd5fbe1..0f7e4783bd4f519 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -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)") diff --git a/clang/include/clang/Basic/Module.h b/clang/include/clang/Basic/Module.h index 08b153e8c1c9d33..d29cc0b45d583e0 100644 --- a/clang/include/clang/Basic/Module.h +++ b/clang/include/clang/Basic/Module.h @@ -178,6 +178,9 @@ class alignas(8) Module { /// 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) { diff --git a/clang/include/clang/Basic/SourceMgrAdapter.h b/clang/include/clang/Basic/SourceMgrAdapter.h new file mode 100644 index 000000000000000..be7f9d5051fbf4c --- /dev/null +++ b/clang/include/clang/Basic/SourceMgrAdapter.h @@ -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 diff --git a/clang/lib/APINotes/APINotesManager.cpp b/clang/lib/APINotes/APINotesManager.cpp new file mode 100644 index 000000000000000..1e1ef66a3b78e59 --- /dev/null +++ b/clang/lib/APINotes/APINotesManager.cpp @@ -0,0 +1,469 @@ +//===--- 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[0]; + delete CurrentModuleReaders[1]; +} + +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 BasenameSuffix = ""; + if (!WantPublic) + BasenameSuffix = "_private"; + + // Look for the source API notes file. + llvm::sys::path::append(Path, llvm::Twine(Basename) + BasenameSuffix + "." + + 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(); + + // Form the path to the APINotes file. + llvm::sys::path::append(Path, "APINotes"); + if (Public) + llvm::sys::path::append( + Path, (llvm::Twine(FrameworkName) + "." + SOURCE_APINOTES_EXTENSION)); + else + llvm::sys::path::append(Path, (llvm::Twine(FrameworkName) + "_private." + + 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); + if (Public) + llvm::sys::path::append(Path, "Headers"); + else + llvm::sys::path::append(Path, "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[0] && + "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); + } + if (ParentPath.empty()) + Dir = std::nullopt; + else + Dir = 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; +} diff --git a/clang/lib/APINotes/CMakeLists.txt b/clang/lib/APINotes/CMakeLists.txt index dec596ea160c68f..dc83edd911ce202 100644 --- a/clang/lib/APINotes/CMakeLists.txt +++ b/clang/lib/APINotes/CMakeLists.txt @@ -3,6 +3,7 @@ set(LLVM_LINK_COMPONENTS BitstreamReader Support) add_clang_library(clangAPINotes + APINotesManager.cpp APINotesReader.cpp APINotesTypes.cpp APINotesWriter.cpp diff --git a/clang/lib/Basic/CMakeLists.txt b/clang/lib/Basic/CMakeLists.txt index 36ccf7d0809453e..57115fb45722344 100644 --- a/clang/lib/Basic/CMakeLists.txt +++ b/clang/lib/Basic/CMakeLists.txt @@ -85,6 +85,7 @@ add_clang_library(clangBasic Sarif.cpp SourceLocation.cpp SourceManager.cpp + SourceMgrAdapter.cpp Stack.cpp TargetID.cpp TargetInfo.cpp diff --git a/clang/lib/Basic/SourceMgrAdapter.cpp b/clang/lib/Basic/SourceMgrAdapter.cpp new file mode 100644 index 000000000000000..e39e4de9d42debc --- /dev/null +++ b/clang/lib/Basic/SourceMgrAdapter.cpp @@ -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()); + } + } +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits