795aa1309c
For PhpFiles, there was a mixed management system, where references from translated code were held until the code was made unreachable, while references from interpreted code were held for the duration of the current request. Under the new scheme, PhpFiles are always treadmilled. They are owned by the FileRepository, and so need to be ref-counted because the FileRepository can have the same PhpFile under multiple paths. But we don't ref-count the *uses* of PhpFiles anymore. Classes still need to be ref-counted, but as soon as their Unit goes away, most of their internals can be freed. We just need to hold onto them if derived classes are referencing them. Even in that case, the next time we try to instantiate the derived class, we can kill any that reference this Class. There were also lots of holes where references were not dropped, or owned data structures were not destroyed. eg a Class's methods were not destroyed when the Class was destroyed; there were several paths where an entry was erased from the file map, but the corresponding PhpFile was not decRef'd - or worse, a null entry was left in the map (something we had asserts to check for). This tries to make the handling more consistent.
510 linhas
16 KiB
C++
510 linhas
16 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "hphp/runtime/base/file_repository.h"
|
|
#include "hphp/runtime/base/runtime_option.h"
|
|
#include "hphp/runtime/base/zend/zend_string.h"
|
|
#include "hphp/util/process.h"
|
|
#include "hphp/util/trace.h"
|
|
#include "hphp/runtime/base/stat_cache.h"
|
|
#include "hphp/runtime/base/server/source_root_info.h"
|
|
|
|
#include "hphp/runtime/vm/jit/targetcache.h"
|
|
#include "hphp/runtime/vm/jit/translator.h"
|
|
#include "hphp/runtime/vm/bytecode.h"
|
|
#include "hphp/runtime/vm/pendq.h"
|
|
#include "hphp/runtime/vm/repo.h"
|
|
#include "hphp/runtime/vm/runtime.h"
|
|
#include "hphp/runtime/vm/treadmill.h"
|
|
|
|
#include "folly/ScopeGuard.h"
|
|
|
|
using std::endl;
|
|
|
|
namespace HPHP {
|
|
|
|
TRACE_SET_MOD(fr);
|
|
extern bool (*file_dump)(const char *filename);
|
|
|
|
namespace Eval {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
std::set<string> FileRepository::s_names;
|
|
|
|
PhpFile::PhpFile(const string &fileName, const string &srcRoot,
|
|
const string &relPath, const string &md5,
|
|
HPHP::Unit* unit)
|
|
: m_refCount(0), m_id(0),
|
|
m_profName(string("run_init::") + string(fileName)),
|
|
m_fileName(fileName), m_srcRoot(srcRoot), m_relPath(relPath), m_md5(md5),
|
|
m_unit(unit) {
|
|
}
|
|
|
|
PhpFile::~PhpFile() {
|
|
always_assert(getRef() == 0);
|
|
if (m_unit != nullptr) {
|
|
// Deleting a Unit can grab a low-ranked lock and we're probably
|
|
// at a high rank right now
|
|
PendQ::defer(new DeferredDeleter<Unit>(m_unit));
|
|
m_unit = nullptr;
|
|
}
|
|
}
|
|
|
|
void PhpFile::incRef() {
|
|
UNUSED int ret = m_refCount.fetch_add(1, std::memory_order_acq_rel);
|
|
TRACE(4, "PhpFile: %s incRef() %d -> %d %p called by %p\n",
|
|
m_fileName.c_str(), ret - 1, ret, this, __builtin_return_address(0));
|
|
}
|
|
|
|
int PhpFile::decRef(int n) {
|
|
int ret = m_refCount.fetch_sub(n, std::memory_order_acq_rel);
|
|
TRACE(4, "PhpFile: %s decRef() %d -> %d %p called by %p\n",
|
|
m_fileName.c_str(), ret, ret - n, this, __builtin_return_address(0));
|
|
assert(ret >= n); // fetch_sub returns the old value
|
|
return ret - n;
|
|
}
|
|
|
|
void PhpFile::decRefAndDelete() {
|
|
class FileInvalidationTrigger : public Treadmill::WorkItem {
|
|
Eval::PhpFile* m_f;
|
|
public:
|
|
FileInvalidationTrigger(Eval::PhpFile* f) : m_f(f) { }
|
|
virtual void operator()() {
|
|
FileRepository::onDelete(m_f);
|
|
}
|
|
};
|
|
|
|
if (decRef() == 0) {
|
|
if (RuntimeOption::EvalJit) Transl::Translator::Get()->invalidateFile(this);
|
|
Treadmill::WorkItem::enqueue(new FileInvalidationTrigger(this));
|
|
}
|
|
}
|
|
|
|
void PhpFile::setId(int id) {
|
|
m_id = id;
|
|
m_unit->setCacheId(id);
|
|
}
|
|
|
|
ReadWriteMutex FileRepository::s_md5Lock(RankFileMd5);
|
|
ParsedFilesMap FileRepository::s_files;
|
|
Md5FileMap FileRepository::s_md5Files;
|
|
UnitMd5Map FileRepository::s_unitMd5Map;
|
|
|
|
static class FileDumpInitializer {
|
|
public: FileDumpInitializer() {
|
|
file_dump = FileRepository::fileDump;
|
|
}
|
|
} s_fileDumpInitializer;
|
|
|
|
bool FileRepository::fileDump(const char *filename) {
|
|
std::ofstream out(filename);
|
|
if (out.fail()) return false;
|
|
out << "s_files: " << s_files.size() << endl;
|
|
for (ParsedFilesMap::const_iterator it =
|
|
s_files.begin(); it != s_files.end(); it++) {
|
|
out << it->first->data() << endl;
|
|
}
|
|
{
|
|
ReadLock lock(s_md5Lock);
|
|
out << "s_md5Files: " << s_md5Files.size() << endl;
|
|
for (Md5FileMap::const_iterator it = s_md5Files.begin();
|
|
it != s_md5Files.end(); it++) {
|
|
out << it->second->getMd5().c_str() << " "
|
|
<< it->second->getFileName().c_str() << endl;
|
|
}
|
|
}
|
|
out.close();
|
|
return true;
|
|
}
|
|
|
|
void FileRepository::onDelete(PhpFile* f) {
|
|
assert(f->getRef() == 0);
|
|
if (md5Enabled()) {
|
|
WriteLock lock(s_md5Lock);
|
|
s_md5Files.erase(f->getMd5());
|
|
}
|
|
delete f;
|
|
}
|
|
|
|
void FileRepository::forEachUnit(UnitVisitor& uit) {
|
|
ReadLock lock(s_md5Lock);
|
|
for (Md5FileMap::const_iterator it = s_md5Files.begin();
|
|
it != s_md5Files.end(); ++it) {
|
|
uit(it->second->unit());
|
|
}
|
|
}
|
|
|
|
size_t FileRepository::getLoadedFiles() {
|
|
ReadLock lock(s_md5Lock);
|
|
return s_md5Files.size();
|
|
}
|
|
|
|
PhpFile *FileRepository::checkoutFile(StringData *rname,
|
|
const struct stat &s) {
|
|
FileInfo fileInfo;
|
|
PhpFile *ret = nullptr;
|
|
String name(rname);
|
|
if (rname->data()[0] != '/') {
|
|
name = String(SourceRootInfo::GetCurrentSourceRoot()) + name;
|
|
}
|
|
|
|
{
|
|
// Get the common fast path out of the way with as little locking
|
|
// as possible: it's in the map and has not changed on disk
|
|
ParsedFilesMap::const_accessor acc;
|
|
if (s_files.find(acc, name.get()) && !acc->second->isChanged(s)) {
|
|
TRACE(1, "FR fast path hit %s\n", rname->data());
|
|
ret = acc->second->getPhpFile();
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
TRACE(1, "FR fast path miss: %s\n", rname->data());
|
|
const StringData *n = StringData::GetStaticString(name.get());
|
|
|
|
PhpFile* toKill = nullptr;
|
|
SCOPE_EXIT {
|
|
// run this after acc is destroyed (and its lock released)
|
|
if (toKill) toKill->decRefAndDelete();
|
|
};
|
|
ParsedFilesMap::accessor acc;
|
|
bool isNew = s_files.insert(acc, n);
|
|
PhpFileWrapper* old = acc->second;
|
|
SCOPE_EXIT {
|
|
// run this just before acc is released
|
|
if (old && old != acc->second) {
|
|
if (old->getPhpFile() != acc->second->getPhpFile()) {
|
|
toKill = old->getPhpFile();
|
|
}
|
|
delete old;
|
|
}
|
|
if (!acc->second) s_files.erase(acc);
|
|
};
|
|
|
|
assert(isNew || old); // We don't leave null entries around.
|
|
bool isChanged = !isNew && old->isChanged(s);
|
|
|
|
if (isNew || isChanged) {
|
|
if (!readFile(n, s, fileInfo)) {
|
|
TRACE(1, "File disappeared between stat and FR::readNewFile: %s\n",
|
|
rname->data());
|
|
return nullptr;
|
|
}
|
|
ret = fileInfo.m_phpFile;
|
|
if (isChanged && ret == old->getPhpFile()) {
|
|
// The file changed but had the same contents.
|
|
if (debug && md5Enabled()) {
|
|
ReadLock lock(s_md5Lock);
|
|
assert(s_md5Files.find(ret->getMd5())->second == ret);
|
|
}
|
|
return ret;
|
|
}
|
|
} else if (!isNew) {
|
|
// Somebody might have loaded the file between when we dropped
|
|
// our read lock and got the write lock
|
|
ret = old->getPhpFile();
|
|
return ret;
|
|
}
|
|
|
|
// If we get here the file was not in s_files or has changed on disk
|
|
if (!ret) {
|
|
// Try to read Unit from .hhbc repo.
|
|
ret = readHhbc(n->data(), fileInfo);
|
|
}
|
|
if (!ret) {
|
|
if (isAuthoritativeRepo()) {
|
|
raise_error("Tried to parse %s in repo authoritative mode", n->data());
|
|
}
|
|
ret = parseFile(n->data(), fileInfo);
|
|
if (!ret) return nullptr;
|
|
}
|
|
assert(ret != nullptr);
|
|
|
|
if (isNew) {
|
|
acc->second = new PhpFileWrapper(s, ret);
|
|
ret->incRef();
|
|
ret->setId(Transl::TargetCache::allocBit());
|
|
} else {
|
|
PhpFile *f = old->getPhpFile();
|
|
if (f != ret) {
|
|
ret->setId(f->getId());
|
|
ret->incRef();
|
|
}
|
|
acc->second = new PhpFileWrapper(s, ret);
|
|
}
|
|
|
|
if (md5Enabled()) {
|
|
WriteLock lock(s_md5Lock);
|
|
s_md5Files[ret->getMd5()] = ret;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool FileRepository::findFile(const StringData *path, struct stat *s) {
|
|
if (isAuthoritativeRepo()) {
|
|
{
|
|
UnitMd5Map::const_accessor acc;
|
|
if (s_unitMd5Map.find(acc, path)) {
|
|
return acc->second.m_present;
|
|
}
|
|
}
|
|
MD5 md5;
|
|
const StringData* spath = StringData::GetStaticString(path);
|
|
UnitMd5Map::accessor acc;
|
|
if (s_unitMd5Map.insert(acc, spath)) {
|
|
bool present = Repo::get().findFile(
|
|
path->data(), SourceRootInfo::GetCurrentSourceRoot(), md5);
|
|
acc->second.m_present = present;
|
|
acc->second.m_unitMd5 = md5;
|
|
}
|
|
return acc->second.m_present;
|
|
}
|
|
return fileStat(path->data(), s) && !S_ISDIR(s->st_mode);
|
|
}
|
|
|
|
String FileRepository::translateFileName(StringData *file) {
|
|
ParsedFilesMap::const_accessor acc;
|
|
if (!s_files.find(acc, file)) return file;
|
|
string srcRoot(SourceRootInfo::GetCurrentSourceRoot());
|
|
if (srcRoot.empty()) return file;
|
|
PhpFile *f = acc->second->getPhpFile();
|
|
const string &parsedSrcRoot = f->getSrcRoot();
|
|
if (srcRoot == parsedSrcRoot) return file;
|
|
int len = parsedSrcRoot.size();
|
|
if (len > 0 && file->size() > len &&
|
|
strncmp(file->data(), parsedSrcRoot.c_str(), len) == 0) {
|
|
return srcRoot + (file->data() + len);
|
|
}
|
|
return file;
|
|
}
|
|
|
|
string FileRepository::unitMd5(const string& fileMd5) {
|
|
// Incorporate relevant options into the unit md5 (there will be more)
|
|
char* md5str;
|
|
int md5len;
|
|
std::ostringstream opts;
|
|
string t = fileMd5 + '\0'
|
|
+ (RuntimeOption::EnableHipHopSyntax ? '1' : '0')
|
|
+ (RuntimeOption::EnableEmitSwitch ? '1' : '0')
|
|
+ (RuntimeOption::EvalJitEnableRenameFunction ? '1' : '0')
|
|
+ (RuntimeOption::EvalAllowHhas ? '1' : '0');
|
|
md5str = string_md5(t.c_str(), t.size(), false, md5len);
|
|
string s = string(md5str, md5len);
|
|
free(md5str);
|
|
return s;
|
|
}
|
|
|
|
void FileRepository::setFileInfo(const StringData *name,
|
|
const string& md5,
|
|
FileInfo &fileInfo,
|
|
bool fromRepo) {
|
|
int md5len;
|
|
char* md5str;
|
|
// Incorporate the path into the md5 that is used as the key for file
|
|
// repository lookups. This assures that even if two PHP files have
|
|
// identical content, separate units exist for them (so that
|
|
// Unit::filepath() and Unit::dirpath() work correctly).
|
|
string s = md5 + '\0' + name->data();
|
|
md5str = string_md5(s.c_str(), s.size(), false, md5len);
|
|
fileInfo.m_md5 = string(md5str, md5len);
|
|
free(md5str);
|
|
|
|
if (fromRepo) {
|
|
fileInfo.m_unitMd5 = md5;
|
|
} else {
|
|
fileInfo.m_unitMd5 = unitMd5(md5);
|
|
}
|
|
|
|
fileInfo.m_srcRoot = SourceRootInfo::GetCurrentSourceRoot();
|
|
int srcRootLen = fileInfo.m_srcRoot.size();
|
|
if (srcRootLen) {
|
|
if (!strncmp(name->data(), fileInfo.m_srcRoot.c_str(), srcRootLen)) {
|
|
fileInfo.m_relPath = string(name->data() + srcRootLen);
|
|
}
|
|
}
|
|
|
|
ReadLock lock(s_md5Lock);
|
|
Md5FileMap::iterator it = s_md5Files.find(fileInfo.m_md5);
|
|
if (it != s_md5Files.end()) {
|
|
PhpFile *f = it->second;
|
|
if (!fileInfo.m_relPath.empty() &&
|
|
fileInfo.m_relPath == f->getRelPath()) {
|
|
assert(fileInfo.m_md5 == f->getMd5());
|
|
fileInfo.m_phpFile = f;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FileRepository::readActualFile(const StringData *name,
|
|
const struct stat &s,
|
|
FileInfo &fileInfo) {
|
|
if (s.st_size > StringData::MaxSize) {
|
|
throw FatalErrorException(0, "file %s is too big", name->data());
|
|
}
|
|
int fileSize = s.st_size;
|
|
if (!fileSize) return false;
|
|
int fd = open(name->data(), O_RDONLY);
|
|
if (!fd) return false; // ignore file open exception
|
|
String str = String(fileSize, ReserveString);
|
|
char *input = str.mutableSlice().ptr;
|
|
if (!input) return false;
|
|
int nbytes = read(fd, input, fileSize);
|
|
close(fd);
|
|
str.setSize(fileSize);
|
|
fileInfo.m_inputString = str;
|
|
if (nbytes != fileSize) return false;
|
|
|
|
if (md5Enabled()) {
|
|
string md5 = StringUtil::MD5(fileInfo.m_inputString).c_str();
|
|
setFileInfo(name, md5, fileInfo, false);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FileRepository::readRepoMd5(const StringData *path,
|
|
FileInfo& fileInfo) {
|
|
MD5 md5;
|
|
bool found;
|
|
{
|
|
UnitMd5Map::const_accessor acc;
|
|
found = s_unitMd5Map.find(acc, path);
|
|
if (found) {
|
|
if (!acc->second.m_present) {
|
|
return false;
|
|
}
|
|
md5 = acc->second.m_unitMd5;
|
|
path = acc->first;
|
|
}
|
|
}
|
|
if (!found) {
|
|
UnitMd5Map::accessor acc;
|
|
path = StringData::GetStaticString(path);
|
|
if (s_unitMd5Map.insert(acc, path)) {
|
|
if (!Repo::get().findFile(path->data(),
|
|
SourceRootInfo::GetCurrentSourceRoot(),
|
|
md5)) {
|
|
acc->second.m_present = false;
|
|
return false;
|
|
}
|
|
acc->second.m_present = true;
|
|
acc->second.m_unitMd5 = md5;
|
|
} else {
|
|
md5 = acc->second.m_unitMd5;
|
|
path = acc->first;
|
|
}
|
|
}
|
|
setFileInfo(path, md5.toString(), fileInfo, true);
|
|
return true;
|
|
}
|
|
|
|
bool FileRepository::readFile(const StringData *name,
|
|
const struct stat &s,
|
|
FileInfo &fileInfo) {
|
|
if (!isAuthoritativeRepo()) {
|
|
TRACE(1, "read initial \"%s\"\n", name->data());
|
|
return readActualFile(name, s, fileInfo);
|
|
}
|
|
|
|
if (!readRepoMd5(name, fileInfo)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
PhpFile *FileRepository::readHhbc(const std::string &name,
|
|
const FileInfo &fileInfo) {
|
|
MD5 md5 = MD5(fileInfo.m_unitMd5.c_str());
|
|
Unit* u = Repo::get().loadUnit(name, md5);
|
|
if (u != nullptr) {
|
|
PhpFile *p = new PhpFile(name, fileInfo.m_srcRoot, fileInfo.m_relPath,
|
|
fileInfo.m_md5, u);
|
|
return p;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
PhpFile *FileRepository::parseFile(const std::string &name,
|
|
const FileInfo &fileInfo) {
|
|
MD5 md5 = MD5(fileInfo.m_unitMd5.c_str());
|
|
Unit* unit = compile_file(fileInfo.m_inputString->data(),
|
|
fileInfo.m_inputString->size(),
|
|
md5, name.c_str());
|
|
PhpFile *p = new PhpFile(name, fileInfo.m_srcRoot, fileInfo.m_relPath,
|
|
fileInfo.m_md5, unit);
|
|
return p;
|
|
}
|
|
|
|
bool FileRepository::fileStat(const string &name, struct stat *s) {
|
|
return StatCache::stat(name, s) == 0;
|
|
}
|
|
|
|
struct ResolveIncludeContext {
|
|
String path; // translated path of the file
|
|
struct stat* s; // stat for the file
|
|
};
|
|
|
|
static bool findFileWrapper(CStrRef file, void* ctx) {
|
|
ResolveIncludeContext* context = (ResolveIncludeContext*)ctx;
|
|
assert(context->path.isNull());
|
|
// TranslatePath() will canonicalize the path and also check
|
|
// whether the file is in an allowed directory.
|
|
String translatedPath = File::TranslatePath(file, false, true);
|
|
if (file[0] != '/') {
|
|
if (HPHP::Eval::FileRepository::findFile(translatedPath.get(),
|
|
context->s)) {
|
|
context->path = translatedPath;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
if (RuntimeOption::SandboxMode || !RuntimeOption::AlwaysUseRelativePath) {
|
|
if (HPHP::Eval::FileRepository::findFile(translatedPath.get(),
|
|
context->s)) {
|
|
context->path = translatedPath;
|
|
return true;
|
|
}
|
|
}
|
|
string server_root(SourceRootInfo::GetCurrentSourceRoot());
|
|
if (server_root.empty()) {
|
|
server_root = string(g_vmContext->getCwd()->data());
|
|
if (server_root.empty() || server_root[server_root.size() - 1] != '/') {
|
|
server_root += "/";
|
|
}
|
|
}
|
|
String rel_path(Util::relativePath(server_root, translatedPath.data()));
|
|
if (HPHP::Eval::FileRepository::findFile(rel_path.get(),
|
|
context->s)) {
|
|
context->path = rel_path;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
String resolveVmInclude(StringData* path, const char* currentDir,
|
|
struct stat *s) {
|
|
ResolveIncludeContext ctx;
|
|
ctx.s = s;
|
|
resolve_include(path, currentDir, findFileWrapper,
|
|
(void*)&ctx);
|
|
// If resolve_include() could not find the file, return NULL
|
|
return ctx.path;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|
|
}
|