Path: blob/main/contrib/llvm-project/clang/lib/APINotes/APINotesManager.cpp
35259 views
//===--- APINotesManager.cpp - Manage API Notes Files ---------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "clang/APINotes/APINotesManager.h"9#include "clang/APINotes/APINotesReader.h"10#include "clang/APINotes/APINotesYAMLCompiler.h"11#include "clang/Basic/Diagnostic.h"12#include "clang/Basic/FileManager.h"13#include "clang/Basic/LangOptions.h"14#include "clang/Basic/Module.h"15#include "clang/Basic/SourceManager.h"16#include "clang/Basic/SourceMgrAdapter.h"17#include "clang/Basic/Version.h"18#include "llvm/ADT/APInt.h"19#include "llvm/ADT/Hashing.h"20#include "llvm/ADT/SetVector.h"21#include "llvm/ADT/SmallPtrSet.h"22#include "llvm/ADT/SmallVector.h"23#include "llvm/ADT/Statistic.h"24#include "llvm/Support/MemoryBuffer.h"25#include "llvm/Support/Path.h"26#include "llvm/Support/PrettyStackTrace.h"2728using namespace clang;29using namespace api_notes;3031#define DEBUG_TYPE "API Notes"32STATISTIC(NumHeaderAPINotes, "non-framework API notes files loaded");33STATISTIC(NumPublicFrameworkAPINotes, "framework public API notes loaded");34STATISTIC(NumPrivateFrameworkAPINotes, "framework private API notes loaded");35STATISTIC(NumFrameworksSearched, "frameworks searched");36STATISTIC(NumDirectoriesSearched, "header directories searched");37STATISTIC(NumDirectoryCacheHits, "directory cache hits");3839namespace {40/// Prints two successive strings, which much be kept alive as long as the41/// PrettyStackTrace entry.42class PrettyStackTraceDoubleString : public llvm::PrettyStackTraceEntry {43StringRef First, Second;4445public:46PrettyStackTraceDoubleString(StringRef First, StringRef Second)47: First(First), Second(Second) {}48void print(raw_ostream &OS) const override { OS << First << Second; }49};50} // namespace5152APINotesManager::APINotesManager(SourceManager &SM, const LangOptions &LangOpts)53: SM(SM), ImplicitAPINotes(LangOpts.APINotes) {}5455APINotesManager::~APINotesManager() {56// Free the API notes readers.57for (const auto &Entry : Readers) {58if (auto Reader = Entry.second.dyn_cast<APINotesReader *>())59delete Reader;60}6162delete CurrentModuleReaders[ReaderKind::Public];63delete CurrentModuleReaders[ReaderKind::Private];64}6566std::unique_ptr<APINotesReader>67APINotesManager::loadAPINotes(FileEntryRef APINotesFile) {68PrettyStackTraceDoubleString Trace("Loading API notes from ",69APINotesFile.getName());7071// Open the source file.72auto SourceFileID = SM.getOrCreateFileID(APINotesFile, SrcMgr::C_User);73auto SourceBuffer = SM.getBufferOrNone(SourceFileID, SourceLocation());74if (!SourceBuffer)75return nullptr;7677// Compile the API notes source into a buffer.78// FIXME: Either propagate OSType through or, better yet, improve the binary79// APINotes format to maintain complete availability information.80// FIXME: We don't even really need to go through the binary format at all;81// we're just going to immediately deserialize it again.82llvm::SmallVector<char, 1024> APINotesBuffer;83std::unique_ptr<llvm::MemoryBuffer> CompiledBuffer;84{85SourceMgrAdapter SMAdapter(86SM, SM.getDiagnostics(), diag::err_apinotes_message,87diag::warn_apinotes_message, diag::note_apinotes_message, APINotesFile);88llvm::raw_svector_ostream OS(APINotesBuffer);89if (api_notes::compileAPINotes(90SourceBuffer->getBuffer(), SM.getFileEntryForID(SourceFileID), OS,91SMAdapter.getDiagHandler(), SMAdapter.getDiagContext()))92return nullptr;9394// Make a copy of the compiled form into the buffer.95CompiledBuffer = llvm::MemoryBuffer::getMemBufferCopy(96StringRef(APINotesBuffer.data(), APINotesBuffer.size()));97}9899// Load the binary form we just compiled.100auto Reader = APINotesReader::Create(std::move(CompiledBuffer), SwiftVersion);101assert(Reader && "Could not load the API notes we just generated?");102return Reader;103}104105std::unique_ptr<APINotesReader>106APINotesManager::loadAPINotes(StringRef Buffer) {107llvm::SmallVector<char, 1024> APINotesBuffer;108std::unique_ptr<llvm::MemoryBuffer> CompiledBuffer;109SourceMgrAdapter SMAdapter(110SM, SM.getDiagnostics(), diag::err_apinotes_message,111diag::warn_apinotes_message, diag::note_apinotes_message, std::nullopt);112llvm::raw_svector_ostream OS(APINotesBuffer);113114if (api_notes::compileAPINotes(Buffer, nullptr, OS,115SMAdapter.getDiagHandler(),116SMAdapter.getDiagContext()))117return nullptr;118119CompiledBuffer = llvm::MemoryBuffer::getMemBufferCopy(120StringRef(APINotesBuffer.data(), APINotesBuffer.size()));121auto Reader = APINotesReader::Create(std::move(CompiledBuffer), SwiftVersion);122assert(Reader && "Could not load the API notes we just generated?");123return Reader;124}125126bool APINotesManager::loadAPINotes(const DirectoryEntry *HeaderDir,127FileEntryRef APINotesFile) {128assert(!Readers.contains(HeaderDir));129if (auto Reader = loadAPINotes(APINotesFile)) {130Readers[HeaderDir] = Reader.release();131return false;132}133134Readers[HeaderDir] = nullptr;135return true;136}137138OptionalFileEntryRef139APINotesManager::findAPINotesFile(DirectoryEntryRef Directory,140StringRef Basename, bool WantPublic) {141FileManager &FM = SM.getFileManager();142143llvm::SmallString<128> Path(Directory.getName());144145StringRef Suffix = WantPublic ? "" : "_private";146147// Look for the source API notes file.148llvm::sys::path::append(Path, llvm::Twine(Basename) + Suffix + "." +149SOURCE_APINOTES_EXTENSION);150return FM.getOptionalFileRef(Path, /*Open*/ true);151}152153OptionalDirectoryEntryRef APINotesManager::loadFrameworkAPINotes(154llvm::StringRef FrameworkPath, llvm::StringRef FrameworkName, bool Public) {155FileManager &FM = SM.getFileManager();156157llvm::SmallString<128> Path(FrameworkPath);158unsigned FrameworkNameLength = Path.size();159160StringRef Suffix = Public ? "" : "_private";161162// Form the path to the APINotes file.163llvm::sys::path::append(Path, "APINotes");164llvm::sys::path::append(Path, (llvm::Twine(FrameworkName) + Suffix + "." +165SOURCE_APINOTES_EXTENSION));166167// Try to open the APINotes file.168auto APINotesFile = FM.getOptionalFileRef(Path);169if (!APINotesFile)170return std::nullopt;171172// Form the path to the corresponding header directory.173Path.resize(FrameworkNameLength);174llvm::sys::path::append(Path, Public ? "Headers" : "PrivateHeaders");175176// Try to access the header directory.177auto HeaderDir = FM.getOptionalDirectoryRef(Path);178if (!HeaderDir)179return std::nullopt;180181// Try to load the API notes.182if (loadAPINotes(*HeaderDir, *APINotesFile))183return std::nullopt;184185// Success: return the header directory.186if (Public)187++NumPublicFrameworkAPINotes;188else189++NumPrivateFrameworkAPINotes;190return *HeaderDir;191}192193static void checkPrivateAPINotesName(DiagnosticsEngine &Diags,194const FileEntry *File, const Module *M) {195if (File->tryGetRealPathName().empty())196return;197198StringRef RealFileName =199llvm::sys::path::filename(File->tryGetRealPathName());200StringRef RealStem = llvm::sys::path::stem(RealFileName);201if (RealStem.ends_with("_private"))202return;203204unsigned DiagID = diag::warn_apinotes_private_case;205if (M->IsSystem)206DiagID = diag::warn_apinotes_private_case_system;207208Diags.Report(SourceLocation(), DiagID) << M->Name << RealFileName;209}210211/// \returns true if any of \p module's immediate submodules are defined in a212/// private module map213static bool hasPrivateSubmodules(const Module *M) {214return llvm::any_of(M->submodules(), [](const Module *Submodule) {215return Submodule->ModuleMapIsPrivate;216});217}218219llvm::SmallVector<FileEntryRef, 2>220APINotesManager::getCurrentModuleAPINotes(Module *M, bool LookInModule,221ArrayRef<std::string> SearchPaths) {222FileManager &FM = SM.getFileManager();223auto ModuleName = M->getTopLevelModuleName();224auto ExportedModuleName = M->getTopLevelModule()->ExportAsModule;225llvm::SmallVector<FileEntryRef, 2> APINotes;226227// First, look relative to the module itself.228if (LookInModule && M->Directory) {229// Local function to try loading an API notes file in the given directory.230auto tryAPINotes = [&](DirectoryEntryRef Dir, bool WantPublic) {231if (auto File = findAPINotesFile(Dir, ModuleName, WantPublic)) {232if (!WantPublic)233checkPrivateAPINotesName(SM.getDiagnostics(), *File, M);234235APINotes.push_back(*File);236}237// If module FooCore is re-exported through module Foo, try Foo.apinotes.238if (!ExportedModuleName.empty())239if (auto File = findAPINotesFile(Dir, ExportedModuleName, WantPublic))240APINotes.push_back(*File);241};242243if (M->IsFramework) {244// For frameworks, we search in the "Headers" or "PrivateHeaders"245// subdirectory.246//247// Public modules:248// - Headers/Foo.apinotes249// - PrivateHeaders/Foo_private.apinotes (if there are private submodules)250// Private modules:251// - PrivateHeaders/Bar.apinotes (except that 'Bar' probably already has252// the word "Private" in it in practice)253llvm::SmallString<128> Path(M->Directory->getName());254255if (!M->ModuleMapIsPrivate) {256unsigned PathLen = Path.size();257258llvm::sys::path::append(Path, "Headers");259if (auto APINotesDir = FM.getOptionalDirectoryRef(Path))260tryAPINotes(*APINotesDir, /*wantPublic=*/true);261262Path.resize(PathLen);263}264265if (M->ModuleMapIsPrivate || hasPrivateSubmodules(M)) {266llvm::sys::path::append(Path, "PrivateHeaders");267if (auto PrivateAPINotesDir = FM.getOptionalDirectoryRef(Path))268tryAPINotes(*PrivateAPINotesDir,269/*wantPublic=*/M->ModuleMapIsPrivate);270}271} else {272// Public modules:273// - Foo.apinotes274// - Foo_private.apinotes (if there are private submodules)275// Private modules:276// - Bar.apinotes (except that 'Bar' probably already has the word277// "Private" in it in practice)278tryAPINotes(*M->Directory, /*wantPublic=*/true);279if (!M->ModuleMapIsPrivate && hasPrivateSubmodules(M))280tryAPINotes(*M->Directory, /*wantPublic=*/false);281}282283if (!APINotes.empty())284return APINotes;285}286287// Second, look for API notes for this module in the module API288// notes search paths.289for (const auto &SearchPath : SearchPaths) {290if (auto SearchDir = FM.getOptionalDirectoryRef(SearchPath)) {291if (auto File = findAPINotesFile(*SearchDir, ModuleName)) {292APINotes.push_back(*File);293return APINotes;294}295}296}297298// Didn't find any API notes.299return APINotes;300}301302bool APINotesManager::loadCurrentModuleAPINotes(303Module *M, bool LookInModule, ArrayRef<std::string> SearchPaths) {304assert(!CurrentModuleReaders[ReaderKind::Public] &&305"Already loaded API notes for the current module?");306307auto APINotes = getCurrentModuleAPINotes(M, LookInModule, SearchPaths);308unsigned NumReaders = 0;309for (auto File : APINotes) {310CurrentModuleReaders[NumReaders++] = loadAPINotes(File).release();311if (!getCurrentModuleReaders().empty())312M->APINotesFile = File.getName().str();313}314315return NumReaders > 0;316}317318bool APINotesManager::loadCurrentModuleAPINotesFromBuffer(319ArrayRef<StringRef> Buffers) {320unsigned NumReader = 0;321for (auto Buf : Buffers) {322auto Reader = loadAPINotes(Buf);323assert(Reader && "Could not load the API notes we just generated?");324325CurrentModuleReaders[NumReader++] = Reader.release();326}327return NumReader;328}329330llvm::SmallVector<APINotesReader *, 2>331APINotesManager::findAPINotes(SourceLocation Loc) {332llvm::SmallVector<APINotesReader *, 2> Results;333334// If there are readers for the current module, return them.335if (!getCurrentModuleReaders().empty()) {336Results.append(getCurrentModuleReaders().begin(),337getCurrentModuleReaders().end());338return Results;339}340341// If we're not allowed to implicitly load API notes files, we're done.342if (!ImplicitAPINotes)343return Results;344345// If we don't have source location information, we're done.346if (Loc.isInvalid())347return Results;348349// API notes are associated with the expansion location. Retrieve the350// file for this location.351SourceLocation ExpansionLoc = SM.getExpansionLoc(Loc);352FileID ID = SM.getFileID(ExpansionLoc);353if (ID.isInvalid())354return Results;355OptionalFileEntryRef File = SM.getFileEntryRefForID(ID);356if (!File)357return Results;358359// Look for API notes in the directory corresponding to this file, or one of360// its its parent directories.361OptionalDirectoryEntryRef Dir = File->getDir();362FileManager &FileMgr = SM.getFileManager();363llvm::SetVector<const DirectoryEntry *,364SmallVector<const DirectoryEntry *, 4>,365llvm::SmallPtrSet<const DirectoryEntry *, 4>>366DirsVisited;367do {368// Look for an API notes reader for this header search directory.369auto Known = Readers.find(*Dir);370371// If we already know the answer, chase it.372if (Known != Readers.end()) {373++NumDirectoryCacheHits;374375// We've been redirected to another directory for answers. Follow it.376if (Known->second && Known->second.is<DirectoryEntryRef>()) {377DirsVisited.insert(*Dir);378Dir = Known->second.get<DirectoryEntryRef>();379continue;380}381382// We have the answer.383if (auto Reader = Known->second.dyn_cast<APINotesReader *>())384Results.push_back(Reader);385break;386}387388// Look for API notes corresponding to this directory.389StringRef Path = Dir->getName();390if (llvm::sys::path::extension(Path) == ".framework") {391// If this is a framework directory, check whether there are API notes392// in the APINotes subdirectory.393auto FrameworkName = llvm::sys::path::stem(Path);394++NumFrameworksSearched;395396// Look for API notes for both the public and private headers.397OptionalDirectoryEntryRef PublicDir =398loadFrameworkAPINotes(Path, FrameworkName, /*Public=*/true);399OptionalDirectoryEntryRef PrivateDir =400loadFrameworkAPINotes(Path, FrameworkName, /*Public=*/false);401402if (PublicDir || PrivateDir) {403// We found API notes: don't ever look past the framework directory.404Readers[*Dir] = nullptr;405406// Pretend we found the result in the public or private directory,407// as appropriate. All headers should be in one of those two places,408// but be defensive here.409if (!DirsVisited.empty()) {410if (PublicDir && DirsVisited.back() == *PublicDir) {411DirsVisited.pop_back();412Dir = *PublicDir;413} else if (PrivateDir && DirsVisited.back() == *PrivateDir) {414DirsVisited.pop_back();415Dir = *PrivateDir;416}417}418419// Grab the result.420if (auto Reader = Readers[*Dir].dyn_cast<APINotesReader *>())421Results.push_back(Reader);422break;423}424} else {425// Look for an APINotes file in this directory.426llvm::SmallString<128> APINotesPath(Dir->getName());427llvm::sys::path::append(428APINotesPath, (llvm::Twine("APINotes.") + SOURCE_APINOTES_EXTENSION));429430// If there is an API notes file here, try to load it.431++NumDirectoriesSearched;432if (auto APINotesFile = FileMgr.getOptionalFileRef(APINotesPath)) {433if (!loadAPINotes(*Dir, *APINotesFile)) {434++NumHeaderAPINotes;435if (auto Reader = Readers[*Dir].dyn_cast<APINotesReader *>())436Results.push_back(Reader);437break;438}439}440}441442// We didn't find anything. Look at the parent directory.443if (!DirsVisited.insert(*Dir)) {444Dir = std::nullopt;445break;446}447448StringRef ParentPath = llvm::sys::path::parent_path(Path);449while (llvm::sys::path::stem(ParentPath) == "..")450ParentPath = llvm::sys::path::parent_path(ParentPath);451452Dir = ParentPath.empty() ? std::nullopt453: FileMgr.getOptionalDirectoryRef(ParentPath);454} while (Dir);455456// Path compression for all of the directories we visited, redirecting457// them to the directory we ended on. If no API notes were found, the458// resulting directory will be NULL, indicating no API notes.459for (const auto Visited : DirsVisited)460Readers[Visited] = Dir ? ReaderEntry(*Dir) : ReaderEntry();461462return Results;463}464465466