APINotesManager.cpp [plain text]
#include "clang/APINotes/APINotesManager.h"
#include "clang/APINotes/APINotesReader.h"
#include "clang/APINotes/APINotesYAMLCompiler.h"
#include "clang/Basic/DiagnosticIDs.h"
#include "clang/Basic/FileManager.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)
: SourceMgr(SourceMgr), PrunedCache(false) { }
APINotesManager::~APINotesManager() {
for (const auto &entry : Readers) {
if (auto reader = entry.second.dyn_cast<APINotesReader *>()) {
delete reader;
}
}
}
static void writeTimestampFile(StringRef TimestampFile) {
std::string ErrorString;
llvm::raw_fd_ostream Out(TimestampFile.str().c_str(), ErrorString,
llvm::sys::fs::F_None);
}
static void pruneAPINotesCache(StringRef APINotesCachePath) {
struct stat StatBuf;
llvm::SmallString<128> TimestampFile;
TimestampFile = APINotesCachePath;
llvm::sys::path::append(TimestampFile, "APINotes.timestamp");
if (::stat(TimestampFile.c_str(), &StatBuf)) {
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;
time_t TimeStampModTime = StatBuf.st_mtime;
time_t CurrentTime = time(nullptr);
if (CurrentTime - TimeStampModTime <= time_t(APINotesCachePruneInterval))
return;
writeTimestampFile(TimestampFile);
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;
if (::stat(File->path().c_str(), &StatBuf))
continue;
time_t FileAccessTime = StatBuf.st_atime;
if (CurrentTime - FileAccessTime <= time_t(APINotesCachePruneAfter)) {
continue;
}
llvm::sys::fs::remove(File->path());
}
}
bool APINotesManager::loadAPINotes(const DirectoryEntry *HeaderDir,
const FileEntry *APINotesFile) {
assert(Readers.find(HeaderDir) == Readers.end());
FileManager &FileMgr = SourceMgr.getFileManager();
StringRef APINotesFileName = APINotesFile->getName();
StringRef APINotesFileExt = llvm::sys::path::extension(APINotesFileName);
if (!APINotesFileExt.empty() &&
APINotesFileExt.substr(1) == BINARY_APINOTES_EXTENSION) {
std::unique_ptr<llvm::MemoryBuffer>
Buffer(FileMgr.getBufferForFile(APINotesFile));
if (!Buffer) {
Readers[HeaderDir] = nullptr;
return true;
}
auto Reader = APINotesReader::get(std::move(Buffer));
if (!Reader) {
Readers[HeaderDir] = nullptr;
return true;
}
Readers[HeaderDir] = Reader.release();
return false;
}
if (!PrunedCache) {
pruneAPINotesCache(FileMgr.getFileSystemOptions().APINotesCachePath);
PrunedCache = true;
}
auto code = llvm::hash_value(StringRef(APINotesFile->getDir()->getName()));
code = hash_combine(code, getClangFullRepositoryVersion());
SmallString<128> CompiledFileName;
CompiledFileName += FileMgr.getFileSystemOptions().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, false) + "."
+ BINARY_APINOTES_EXTENSION));
if (const FileEntry *CompiledFile = FileMgr.getFile(CompiledFileName,
true,
false)) {
if (auto Buffer = std::unique_ptr<llvm::MemoryBuffer>(
FileMgr.getBufferForFile(CompiledFile))) {
if (CompiledFile->getModificationTime()
>= APINotesFile->getModificationTime()) {
if (auto Reader = APINotesReader::get(std::move(Buffer))) {
++NumBinaryCacheHits;
Readers[HeaderDir] = Reader.release();
return false;
}
}
}
llvm::sys::fs::remove(CompiledFileName.str());
++NumBinaryCacheRebuilds;
} else {
++NumBinaryCacheMisses;
}
std::unique_ptr<llvm::MemoryBuffer>
Buffer(FileMgr.getBufferForFile(APINotesFile));
if (!Buffer) {
Readers[HeaderDir] = nullptr;
return true;
}
llvm::SmallVector<char, 1024> APINotesBuffer;
{
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(Buffer->getBuffer(),
OS,
api_notes::OSType::Absent,
srcMgrAdapter.getDiagHandler(),
srcMgrAdapter.getDiagContext())) {
Readers[HeaderDir] = nullptr;
return true;
}
OS.flush();
Buffer = std::unique_ptr<llvm::MemoryBuffer>(
llvm::MemoryBuffer::getMemBufferCopy(
StringRef(APINotesBuffer.data(), APINotesBuffer.size())));
}
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.getFileSystemOptions().APINotesCachePath);
if (!llvm::sys::fs::createUniqueFile(TemporaryBinaryFileName.str(),
TemporaryFD, TemporaryBinaryFileName)) {
bool hadError;
{
llvm::raw_fd_ostream Out(TemporaryFD, true);
Out.write(Buffer->getBufferStart(), Buffer->getBufferSize());
Out.flush();
hadError = Out.has_error();
}
if (!hadError) {
llvm::sys::fs::rename(TemporaryBinaryFileName.str(),
CompiledFileName.str());
}
}
auto Reader = APINotesReader::get(std::move(Buffer));
assert(Reader && "Could not load the API notes we just generated?");
Readers[HeaderDir] = Reader.release();
return false;
}
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();
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));
const FileEntry *APINotesFile = FileMgr.getFile(Path);
if (!APINotesFile)
return nullptr;
Path.resize(FrameworkNameLength);
if (Public)
llvm::sys::path::append(Path, "Headers");
else
llvm::sys::path::append(Path, "PrivateHeaders");
const DirectoryEntry *HeaderDir = FileMgr.getDirectory(Path);
if (!HeaderDir)
return nullptr;
if (loadAPINotes(HeaderDir, APINotesFile))
return nullptr;
if (Public)
++NumPublicFrameworkAPINotes;
else
++NumPrivateFrameworkAPINotes;
return HeaderDir;
}
APINotesReader *APINotesManager::findAPINotes(SourceLocation Loc) {
SourceLocation ExpansionLoc = SourceMgr.getExpansionLoc(Loc);
FileID ID = SourceMgr.getFileID(ExpansionLoc);
if (ID.isInvalid())
return nullptr;
const FileEntry *File = SourceMgr.getFileEntryForID(ID);
if (!File)
return nullptr;
const DirectoryEntry *Dir = File->getDir();
FileManager &FileMgr = SourceMgr.getFileManager();
llvm::SetVector<const DirectoryEntry *,
SmallVector<const DirectoryEntry *, 4>,
llvm::SmallPtrSet<const DirectoryEntry *, 4>> DirsVisited;
APINotesReader *Result = nullptr;
do {
auto Known = Readers.find(Dir);
if (Known != Readers.end()) {
++NumDirectoryCacheHits;
if (auto OtherDir = Known->second.dyn_cast<const DirectoryEntry *>()) {
DirsVisited.insert(Dir);
Dir = OtherDir;
continue;
}
Result = Known->second.dyn_cast<APINotesReader *>();
break;
}
StringRef Path = Dir->getName();
if (llvm::sys::path::extension(Path) == ".framework") {
auto FrameworkName = llvm::sys::path::stem(Path);
++NumFrameworksSearched;
const DirectoryEntry *PublicDir
= loadFrameworkAPINotes(Path, FrameworkName, true);
const DirectoryEntry *PrivateDir
= loadFrameworkAPINotes(Path, FrameworkName, false);
if (PublicDir || PrivateDir) {
Readers[Dir] = nullptr;
if (!DirsVisited.empty()) {
if (DirsVisited.back() == PublicDir) {
DirsVisited.pop_back();
Dir = PublicDir;
} else if (DirsVisited.back() == PrivateDir) {
DirsVisited.pop_back();
Dir = PrivateDir;
}
}
Result = Readers[Dir].dyn_cast<APINotesReader *>();;
break;
}
} else {
llvm::SmallString<128> APINotesPath;
APINotesPath += Dir->getName();
llvm::sys::path::append(APINotesPath,
(llvm::Twine("APINotes.")
+ SOURCE_APINOTES_EXTENSION));
++NumDirectoriesSearched;
if (const FileEntry *APINotesFile = FileMgr.getFile(APINotesPath)) {
if (!loadAPINotes(Dir, APINotesFile)) {
++NumHeaderAPINotes;
Result = Readers[Dir].dyn_cast<APINotesReader *>();
break;
}
}
}
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);
for (const auto Visited : DirsVisited) {
Readers[Visited] = Dir;
}
return Result;
}