| //===--- APINotesManager.cpp - Manage API Notes Files ---------------------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file implements the APINotesManager class. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/APINotes/APINotesManager.h" |
| #include "clang/APINotes/APINotesOptions.h" |
| #include "clang/APINotes/APINotesReader.h" |
| #include "clang/APINotes/APINotesYAMLCompiler.h" |
| #include "clang/Basic/DiagnosticIDs.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/Statistic.h" |
| #include "llvm/ADT/SetVector.h" |
| #include "llvm/ADT/SmallPtrSet.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/PrettyStackTrace.h" |
| #include <sys/stat.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; |
| } |
| }; |
| } |
| |
| APINotesManager::APINotesManager(SourceManager &sourceMgr, |
| const LangOptions &langOpts) |
| : SourceMgr(sourceMgr), 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(const FileEntry *apiNotesFile) { |
| PrettyStackTraceDoubleString trace("Loading API notes from ", |
| apiNotesFile->getName()); |
| |
| // If the API notes file is already in the binary form, load it directly. |
| StringRef apiNotesFileName = apiNotesFile->getName(); |
| StringRef apiNotesFileExt = llvm::sys::path::extension(apiNotesFileName); |
| if (!apiNotesFileExt.empty() && |
| apiNotesFileExt.substr(1) == BINARY_APINOTES_EXTENSION) { |
| auto compiledFileID = SourceMgr.createFileID(apiNotesFile, SourceLocation(), SrcMgr::C_User); |
| |
| // Load the file. |
| auto buffer = SourceMgr.getBuffer(compiledFileID, SourceLocation()); |
| if (!buffer) return nullptr; |
| |
| // Load the binary form. |
| return APINotesReader::getUnmanaged(buffer, SwiftVersion); |
| } |
| |
| // Open the source file. |
| auto sourceFileID = SourceMgr.createFileID(apiNotesFile, SourceLocation(), SrcMgr::C_User); |
| auto sourceBuffer = SourceMgr.getBuffer(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 srcMgrAdapter(SourceMgr, SourceMgr.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(), |
| SourceMgr.getFileEntryForID(sourceFileID), |
| OS, |
| api_notes::OSType::Absent, |
| srcMgrAdapter.getDiagHandler(), |
| srcMgrAdapter.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::get(std::move(compiledBuffer), SwiftVersion); |
| assert(reader && "Could not load the API notes we just generated?"); |
| return reader; |
| } |
| |
| bool APINotesManager::loadAPINotes(const DirectoryEntry *HeaderDir, |
| const FileEntry *APINotesFile) { |
| assert(Readers.find(HeaderDir) == Readers.end()); |
| if (auto reader = loadAPINotes(APINotesFile)) { |
| Readers[HeaderDir] = reader.release(); |
| return false; |
| } |
| |
| Readers[HeaderDir] = nullptr; |
| return true; |
| } |
| |
| const FileEntry *APINotesManager::findAPINotesFile(const DirectoryEntry *directory, |
| StringRef basename, |
| bool wantPublic) { |
| FileManager &fileMgr = SourceMgr.getFileManager(); |
| |
| llvm::SmallString<128> path; |
| path += directory->getName(); |
| |
| unsigned pathLen = path.size(); |
| |
| StringRef basenameSuffix = ""; |
| if (!wantPublic) basenameSuffix = "_private"; |
| |
| // Look for a binary API notes file. |
| llvm::sys::path::append(path, |
| llvm::Twine(basename) + basenameSuffix + "." + BINARY_APINOTES_EXTENSION); |
| if (const FileEntry *binaryFile = fileMgr.getFile(path)) |
| return binaryFile; |
| |
| // Go back to the original path. |
| path.resize(pathLen); |
| |
| // Look for the source API notes file. |
| llvm::sys::path::append(path, |
| llvm::Twine(basename) + basenameSuffix + "." + SOURCE_APINOTES_EXTENSION); |
| return fileMgr.getFile(path); |
| } |
| |
| const DirectoryEntry *APINotesManager::loadFrameworkAPINotes( |
| llvm::StringRef FrameworkPath, |
| llvm::StringRef FrameworkName, |
| bool Public) { |
| FileManager &FileMgr = SourceMgr.getFileManager(); |
| |
| llvm::SmallString<128> Path; |
| 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. |
| const FileEntry *APINotesFile = FileMgr.getFile(Path); |
| if (!APINotesFile) |
| return nullptr; |
| |
| // 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. |
| const DirectoryEntry *HeaderDir = FileMgr.getDirectory(Path); |
| if (!HeaderDir) |
| return nullptr; |
| |
| // Try to load the API notes. |
| if (loadAPINotes(HeaderDir, APINotesFile)) |
| return nullptr; |
| |
| // Success: return the header directory. |
| if (Public) |
| ++NumPublicFrameworkAPINotes; |
| else |
| ++NumPrivateFrameworkAPINotes; |
| return HeaderDir; |
| } |
| |
| bool APINotesManager::loadCurrentModuleAPINotes( |
| const Module *module, |
| bool lookInModule, |
| ArrayRef<std::string> searchPaths) { |
| assert(!CurrentModuleReaders[0] && |
| "Already loaded API notes for the current module?"); |
| |
| FileManager &fileMgr = SourceMgr.getFileManager(); |
| auto moduleName = module->getTopLevelModuleName(); |
| |
| // First, look relative to the module itself. |
| if (lookInModule) { |
| bool foundAny = false; |
| unsigned numReaders = 0; |
| |
| // Local function to try loading an API notes file in the given directory. |
| auto tryAPINotes = [&](const DirectoryEntry *dir, bool wantPublic) { |
| if (auto file = findAPINotesFile(dir, moduleName, wantPublic)) { |
| foundAny = true; |
| |
| // Try to load the API notes file. |
| CurrentModuleReaders[numReaders] = loadAPINotes(file).release(); |
| if (CurrentModuleReaders[numReaders]) |
| ++numReaders; |
| } |
| }; |
| |
| if (module->IsFramework) { |
| // For frameworks, we search in the "Headers" or "PrivateHeaders" |
| // subdirectory. |
| llvm::SmallString<128> path; |
| path += module->Directory->getName(); |
| unsigned pathLen = path.size(); |
| |
| llvm::sys::path::append(path, "Headers"); |
| if (auto apinotesDir = fileMgr.getDirectory(path)) |
| tryAPINotes(apinotesDir, /*wantPublic=*/true); |
| |
| path.resize(pathLen); |
| llvm::sys::path::append(path, "PrivateHeaders"); |
| if (auto privateAPINotesDir = fileMgr.getDirectory(path)) |
| tryAPINotes(privateAPINotesDir, /*wantPublic=*/false); |
| } else { |
| tryAPINotes(module->Directory, /*wantPublic=*/true); |
| tryAPINotes(module->Directory, /*wantPublic=*/false); |
| } |
| |
| if (foundAny) |
| return numReaders > 0; |
| } |
| |
| // Second, look for API notes for this module in the module API |
| // notes search paths. |
| for (const auto &searchPath : searchPaths) { |
| if (auto searchDir = fileMgr.getDirectory(searchPath)) { |
| if (auto file = findAPINotesFile(searchDir, moduleName)) { |
| CurrentModuleReaders[0] = loadAPINotes(file).release(); |
| return !getCurrentModuleReaders().empty(); |
| } |
| } |
| } |
| |
| // Didn't find any API notes. |
| return false; |
| } |
| |
| 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 = SourceMgr.getExpansionLoc(Loc); |
| FileID ID = SourceMgr.getFileID(ExpansionLoc); |
| if (ID.isInvalid()) return Results; |
| const FileEntry *File = SourceMgr.getFileEntryForID(ID); |
| if (!File) return Results; |
| |
| // Look for API notes in the directory corresponding to this file, or one of |
| // its its parent directories. |
| const DirectoryEntry *Dir = File->getDir(); |
| FileManager &FileMgr = SourceMgr.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 (auto OtherDir = Known->second.dyn_cast<const DirectoryEntry *>()) { |
| DirsVisited.insert(Dir); |
| Dir = OtherDir; |
| 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. |
| const DirectoryEntry *PublicDir |
| = loadFrameworkAPINotes(Path, FrameworkName, /*Public=*/true); |
| const DirectoryEntry *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 (DirsVisited.back() == PublicDir) { |
| DirsVisited.pop_back(); |
| Dir = PublicDir; |
| } else if (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; |
| 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 (const FileEntry *APINotesFile = FileMgr.getFile(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 = 0; |
| 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 = nullptr; |
| } else { |
| Dir = FileMgr.getDirectory(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; |
| } |
| |
| return Results; |
| } |