| //===--- 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/Support/Path.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"); |
| STATISTIC(NumBinaryCacheHits, |
| "binary form cache hits"); |
| STATISTIC(NumBinaryCacheMisses, |
| "binary form cache misses"); |
| STATISTIC(NumBinaryCacheRebuilds, |
| "binary form cache rebuilds"); |
| |
| APINotesManager::APINotesManager(SourceManager &sourceMgr, |
| const LangOptions &langOpts) |
| : SourceMgr(sourceMgr), ImplicitAPINotes(langOpts.APINotes), |
| PrunedCache(false) { } |
| |
| 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]; |
| } |
| |
| /// \brief Write a new timestamp file with the given path. |
| static void writeTimestampFile(StringRef TimestampFile) { |
| std::error_code EC; |
| llvm::raw_fd_ostream Out(TimestampFile.str(), EC, llvm::sys::fs::F_None); |
| } |
| |
| /// \brief Prune the API notes cache of API notes that haven't been accessed in |
| /// a long time. |
| static void pruneAPINotesCache(StringRef APINotesCachePath) { |
| struct stat StatBuf; |
| llvm::SmallString<128> TimestampFile; |
| TimestampFile = APINotesCachePath; |
| llvm::sys::path::append(TimestampFile, "APINotes.timestamp"); |
| |
| // Try to stat() the timestamp file. |
| if (::stat(TimestampFile.c_str(), &StatBuf)) { |
| // If the timestamp file wasn't there, create one now. |
| if (errno == ENOENT) { |
| llvm::sys::fs::create_directories(APINotesCachePath); |
| writeTimestampFile(TimestampFile); |
| } |
| return; |
| } |
| |
| const unsigned APINotesCachePruneInterval = 7 * 24 * 60 * 60; |
| const unsigned APINotesCachePruneAfter = 31 * 24 * 60 * 60; |
| |
| // Check whether the time stamp is older than our pruning interval. |
| // If not, do nothing. |
| time_t TimeStampModTime = StatBuf.st_mtime; |
| time_t CurrentTime = time(nullptr); |
| if (CurrentTime - TimeStampModTime <= time_t(APINotesCachePruneInterval)) |
| return; |
| |
| // Write a new timestamp file so that nobody else attempts to prune. |
| // There is a benign race condition here, if two Clang instances happen to |
| // notice at the same time that the timestamp is out-of-date. |
| writeTimestampFile(TimestampFile); |
| |
| // Walk the entire API notes cache, looking for unused compiled API notes. |
| std::error_code EC; |
| SmallString<128> APINotesCachePathNative; |
| llvm::sys::path::native(APINotesCachePath, APINotesCachePathNative); |
| for (llvm::sys::fs::directory_iterator |
| File(APINotesCachePathNative.str(), EC), DirEnd; |
| File != DirEnd && !EC; File.increment(EC)) { |
| StringRef Extension = llvm::sys::path::extension(File->path()); |
| if (Extension.empty()) |
| continue; |
| |
| if (Extension.substr(1) != BINARY_APINOTES_EXTENSION) |
| continue; |
| |
| // Look at this file. If we can't stat it, there's nothing interesting |
| // there. |
| if (::stat(File->path().c_str(), &StatBuf)) |
| continue; |
| |
| // If the file has been used recently enough, leave it there. |
| time_t FileAccessTime = StatBuf.st_atime; |
| if (CurrentTime - FileAccessTime <= time_t(APINotesCachePruneAfter)) { |
| continue; |
| } |
| |
| // Remove the file. |
| llvm::sys::fs::remove(File->path()); |
| } |
| } |
| |
| std::unique_ptr<APINotesReader> |
| APINotesManager::loadAPINotes(const FileEntry *apiNotesFile) { |
| FileManager &fileMgr = SourceMgr.getFileManager(); |
| |
| // 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); |
| } |
| |
| // If we haven't pruned the API notes cache yet during this execution, do |
| // so now. |
| if (!PrunedCache) { |
| pruneAPINotesCache(fileMgr.getFileSystemOpts().APINotesCachePath); |
| PrunedCache = true; |
| } |
| |
| // Compute a hash of the API notes file's directory and the Clang version, |
| // to be used as part of the filename for the cached binary copy. |
| auto code = llvm::hash_value(StringRef(apiNotesFile->getDir()->getName())); |
| code = hash_combine(code, getClangFullRepositoryVersion()); |
| |
| // Determine the file name for the cached binary form. |
| SmallString<128> compiledFileName; |
| compiledFileName += fileMgr.getFileSystemOpts().APINotesCachePath; |
| assert(!compiledFileName.empty() && "No API notes cache path provided?"); |
| llvm::sys::path::append(compiledFileName, |
| (llvm::Twine(llvm::sys::path::stem(apiNotesFileName)) + "-" |
| + llvm::APInt(64, code).toString(36, /*Signed=*/false) + "." |
| + BINARY_APINOTES_EXTENSION)); |
| |
| // Try to open the cached binary form. |
| if (const FileEntry *compiledFile = fileMgr.getFile(compiledFileName, |
| /*openFile=*/true, |
| /*cacheFailure=*/false)) { |
| // Load the file contents. |
| if (auto buffer = fileMgr.getBufferForFile(compiledFile)) { |
| // Load the file. |
| if (auto reader = APINotesReader::get(std::move(buffer.get()))) { |
| bool outOfDate = false; |
| if (auto sizeAndModTime = reader->getSourceFileSizeAndModTime()) { |
| if (sizeAndModTime->first != apiNotesFile->getSize() || |
| sizeAndModTime->second != apiNotesFile->getModificationTime()) |
| outOfDate = true; |
| } |
| |
| if (!outOfDate) { |
| // Success. |
| ++NumBinaryCacheHits; |
| return reader; |
| } |
| } |
| } |
| |
| // The cache entry was somehow broken; delete this one so we can build a |
| // new one below. |
| llvm::sys::fs::remove(compiledFileName.str()); |
| ++NumBinaryCacheRebuilds; |
| } else { |
| ++NumBinaryCacheMisses; |
| } |
| |
| // 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. |
| 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())); |
| } |
| |
| // Save the binary form into the cache. Perform this operation |
| // atomically. |
| SmallString<64> temporaryBinaryFileName = compiledFileName.str(); |
| temporaryBinaryFileName.erase( |
| temporaryBinaryFileName.end() |
| - llvm::sys::path::extension(temporaryBinaryFileName).size(), |
| temporaryBinaryFileName.end()); |
| temporaryBinaryFileName += "-%%%%%%."; |
| temporaryBinaryFileName += BINARY_APINOTES_EXTENSION; |
| |
| int temporaryFD; |
| llvm::sys::fs::create_directories( |
| fileMgr.getFileSystemOpts().APINotesCachePath); |
| if (!llvm::sys::fs::createUniqueFile(temporaryBinaryFileName.str(), |
| temporaryFD, temporaryBinaryFileName)) { |
| // Write the contents of the buffer. |
| bool hadError; |
| { |
| llvm::raw_fd_ostream out(temporaryFD, /*shouldClose=*/true); |
| out.write(compiledBuffer.get()->getBufferStart(), |
| compiledBuffer.get()->getBufferSize()); |
| out.flush(); |
| |
| hadError = out.has_error(); |
| } |
| |
| if (!hadError) { |
| // Rename the temporary file to the actual compiled file. |
| llvm::sys::fs::rename(temporaryBinaryFileName.str(), |
| compiledFileName.str()); |
| } |
| } |
| |
| // Load the binary form we just compiled. |
| auto reader = APINotesReader::get(std::move(compiledBuffer)); |
| 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 "APINotes" subdirectory. |
| llvm::SmallString<128> path; |
| path += module->Directory->getName(); |
| llvm::sys::path::append(path, "APINotes"); |
| if (auto apinotesDir = fileMgr.getDirectory(path)) { |
| tryAPINotes(apinotesDir, /*wantPublic=*/true); |
| tryAPINotes(apinotesDir, /*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; |
| } |