fe32089a67
This is the first results from profiling callsites of StringData::initLiteral. This diff converts a handful more string literals to StaticString, removes overloaded Variant comparison operators (operator==, etc), and avoids constructing new strings in a few cases in tvCastToString and tvCastToStringInPlace.
948 linhas
28 KiB
C++
948 linhas
28 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. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#define __STDC_LIMIT_MACROS
|
|
|
|
#include "hphp/runtime/base/execution_context.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "hphp/util/logger.h"
|
|
#include "hphp/util/process.h"
|
|
#include "hphp/util/text_color.h"
|
|
#include "hphp/runtime/base/array/array_init.h"
|
|
#include "hphp/runtime/base/array/array_iterator.h"
|
|
#include "hphp/runtime/base/memory/memory_manager.h"
|
|
#include "hphp/runtime/base/memory/sweepable.h"
|
|
#include "hphp/runtime/base/server/server_stats.h"
|
|
#include "hphp/runtime/base/util/request_local.h"
|
|
#include "hphp/runtime/base/builtin_functions.h"
|
|
#include "hphp/runtime/base/comparisons.h"
|
|
#include "hphp/runtime/base/complex_types.h"
|
|
#include "hphp/runtime/base/externals.h"
|
|
#include "hphp/runtime/base/resource_data.h"
|
|
#include "hphp/runtime/base/runtime_option.h"
|
|
#include "hphp/runtime/base/type_conversions.h"
|
|
#include "hphp/runtime/debugger/debugger.h"
|
|
#include "hphp/runtime/base/file_repository.h"
|
|
#include "hphp/runtime/ext/ext_string.h"
|
|
#include "hphp/runtime/vm/jit/translator-inline.h"
|
|
#include "hphp/runtime/vm/jit/translator.h"
|
|
#include "hphp/runtime/vm/debugger_hook.h"
|
|
#include "hphp/runtime/vm/event_hook.h"
|
|
|
|
namespace HPHP {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
IMPLEMENT_THREAD_LOCAL_NO_CHECK_HOT(ExecutionContext, g_context);
|
|
|
|
int64_t VMExecutionContext::s_threadIdxCounter = 0;
|
|
Mutex VMExecutionContext::s_threadIdxLock;
|
|
hphp_hash_map<pid_t, int64_t> VMExecutionContext::s_threadIdxMap;
|
|
|
|
const StaticString BaseExecutionContext::s_amp("&");
|
|
|
|
BaseExecutionContext::BaseExecutionContext() :
|
|
m_fp(nullptr), m_pc(nullptr),
|
|
m_transport(nullptr),
|
|
m_maxTime(RuntimeOption::RequestTimeoutSeconds),
|
|
m_cwd(Process::CurrentWorkingDirectory),
|
|
m_out(nullptr), m_implicitFlush(false), m_protectedLevel(0),
|
|
m_stdout(nullptr), m_stdoutData(nullptr),
|
|
m_errorState(ExecutionContext::NoError),
|
|
m_errorReportingLevel(RuntimeOption::RuntimeErrorReportingLevel),
|
|
m_lastErrorNum(0), m_logErrors(false), m_throwAllErrors(false),
|
|
m_vhost(nullptr) {
|
|
|
|
setRequestMemoryMaxBytes(RuntimeOption::RequestMemoryMaxBytes);
|
|
m_include_paths = Array::Create();
|
|
for (unsigned int i = 0; i < RuntimeOption::IncludeSearchPaths.size(); ++i) {
|
|
m_include_paths.append(String(RuntimeOption::IncludeSearchPaths[i]));
|
|
}
|
|
}
|
|
|
|
VMExecutionContext::VMExecutionContext() :
|
|
m_preg_backtrace_limit(RuntimeOption::PregBacktraceLimit),
|
|
m_preg_recursion_limit(RuntimeOption::PregRecursionLimit),
|
|
m_lambdaCounter(0), m_nesting(0),
|
|
m_injTables(nullptr), m_breakPointFilter(nullptr), m_lastLocFilter(nullptr),
|
|
m_interpreting(false), m_dbgNoBreak(false),
|
|
m_coverPrevLine(-1), m_coverPrevUnit(nullptr),
|
|
m_executingSetprofileCallback(false) {
|
|
|
|
// Make sure any fields accessed from the TC are within a byte of
|
|
// ExecutionContext's beginning.
|
|
static_assert(offsetof(ExecutionContext, m_stack) <= 0xff,
|
|
"m_stack offset too large");
|
|
static_assert(offsetof(ExecutionContext, m_fp) <= 0xff,
|
|
"m_fp offset too large");
|
|
static_assert(offsetof(ExecutionContext, m_pc) <= 0xff,
|
|
"m_pc offset too large");
|
|
static_assert(offsetof(ExecutionContext, m_currentThreadIdx) <= 0xff,
|
|
"m_currentThreadIdx offset too large");
|
|
|
|
{
|
|
Lock lock(s_threadIdxLock);
|
|
pid_t tid = Process::GetThreadPid();
|
|
if (!mapGet(s_threadIdxMap, tid, &m_currentThreadIdx)) {
|
|
m_currentThreadIdx = s_threadIdxCounter++;
|
|
s_threadIdxMap[tid] = m_currentThreadIdx;
|
|
}
|
|
}
|
|
}
|
|
|
|
BaseExecutionContext::~BaseExecutionContext() {
|
|
obFlushAll();
|
|
for (std::list<OutputBuffer*>::const_iterator iter = m_buffers.begin();
|
|
iter != m_buffers.end(); ++iter) {
|
|
delete *iter;
|
|
}
|
|
}
|
|
|
|
VMExecutionContext::~VMExecutionContext() {
|
|
// Discard any ConstInfo objects that were created to support reflection.
|
|
for (ConstInfoMap::const_iterator it = m_constInfo.begin();
|
|
it != m_constInfo.end(); ++it) {
|
|
delete it->second;
|
|
}
|
|
// decRef all of the PhpFiles in m_evaledFiles. Any PhpFile whose refcount
|
|
// reaches zero will be destroyed. Currently each PhpFile "owns" its Unit,
|
|
// so when a PhpFile is destroyed it will free its Unit as well.
|
|
for (EvaledFilesMap::iterator it = m_evaledFiles.begin();
|
|
it != m_evaledFiles.end();) {
|
|
EvaledFilesMap::iterator current = it;
|
|
++it;
|
|
StringData* sd = current->first;
|
|
Eval::PhpFile* efile = current->second;
|
|
efile->decRefAndDelete();
|
|
m_evaledFiles.erase(current);
|
|
decRefStr(sd);
|
|
}
|
|
// Discard all units that were created via create_function().
|
|
for (EvaledUnitsVec::iterator it = m_createdFuncs.begin();
|
|
it != m_createdFuncs.end(); ++it) {
|
|
delete *it;
|
|
}
|
|
|
|
delete m_injTables;
|
|
delete m_breakPointFilter;
|
|
delete m_lastLocFilter;
|
|
}
|
|
|
|
void BaseExecutionContext::backupSession() {
|
|
m_shutdownsBackup = m_shutdowns;
|
|
m_userErrorHandlersBackup = m_userErrorHandlers;
|
|
m_userExceptionHandlersBackup = m_userExceptionHandlers;
|
|
}
|
|
|
|
void BaseExecutionContext::restoreSession() {
|
|
m_shutdowns = m_shutdownsBackup;
|
|
m_userErrorHandlers = m_userErrorHandlersBackup;
|
|
m_userExceptionHandlers = m_userExceptionHandlersBackup;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// system functions
|
|
|
|
String BaseExecutionContext::getMimeType() const {
|
|
String mimetype;
|
|
if (m_transport) {
|
|
mimetype = m_transport->getMimeType();
|
|
}
|
|
|
|
if (strncasecmp(mimetype.data(), "text/", 5) == 0) {
|
|
int pos = mimetype.find(';');
|
|
if (pos != String::npos) {
|
|
mimetype = mimetype.substr(0, pos);
|
|
}
|
|
} else if (m_transport && m_transport->sendDefaultContentType()) {
|
|
mimetype = m_transport->getDefaultContentType();
|
|
}
|
|
return mimetype;
|
|
}
|
|
|
|
std::string BaseExecutionContext::getRequestUrl(size_t szLimit) {
|
|
Transport* t = getTransport();
|
|
std::string ret = t ? t->getUrl() : "";
|
|
if (szLimit != std::string::npos) {
|
|
ret = ret.substr(0, szLimit);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void BaseExecutionContext::setContentType(CStrRef mimetype, CStrRef charset) {
|
|
if (m_transport) {
|
|
String contentType = mimetype;
|
|
contentType += "; ";
|
|
contentType += "charset=";
|
|
contentType += charset;
|
|
m_transport->addHeader("Content-Type", contentType.c_str());
|
|
m_transport->setDefaultContentType(false);
|
|
}
|
|
}
|
|
|
|
void BaseExecutionContext::setRequestMemoryMaxBytes(int64_t max) {
|
|
if (max <= 0) {
|
|
max = INT64_MAX;
|
|
}
|
|
m_maxMemory = max;
|
|
MemoryManager::TheMemoryManager()->getStats().maxBytes = m_maxMemory;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// write()
|
|
|
|
void BaseExecutionContext::write(CStrRef s) {
|
|
write(s.data(), s.size());
|
|
}
|
|
|
|
void BaseExecutionContext::setStdout(PFUNC_STDOUT func, void *data) {
|
|
m_stdout = func;
|
|
m_stdoutData = data;
|
|
}
|
|
|
|
static void safe_stdout(const void *ptr, size_t size) {
|
|
write(fileno(stdout), ptr, size);
|
|
}
|
|
|
|
void BaseExecutionContext::writeStdout(const char *s, int len) {
|
|
if (m_stdout == nullptr) {
|
|
if (s_stdout_color) {
|
|
safe_stdout(s_stdout_color, strlen(s_stdout_color));
|
|
safe_stdout(s, len);
|
|
safe_stdout(ANSI_COLOR_END, strlen(ANSI_COLOR_END));
|
|
} else {
|
|
safe_stdout(s, len);
|
|
}
|
|
} else {
|
|
m_stdout(s, len, m_stdoutData);
|
|
}
|
|
}
|
|
|
|
void BaseExecutionContext::write(const char *s, int len) {
|
|
if (m_out) {
|
|
m_out->append(s, len);
|
|
} else {
|
|
writeStdout(s, len);
|
|
}
|
|
if (m_implicitFlush) flush();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// output buffers
|
|
|
|
void BaseExecutionContext::obProtect(bool on) {
|
|
m_protectedLevel = on ? m_buffers.size() : 0;
|
|
}
|
|
|
|
void BaseExecutionContext::obStart(CVarRef handler /* = null */) {
|
|
OutputBuffer *ob = new OutputBuffer();
|
|
ob->handler = handler;
|
|
m_buffers.push_back(ob);
|
|
resetCurrentBuffer();
|
|
}
|
|
|
|
String BaseExecutionContext::obCopyContents() {
|
|
if (!m_buffers.empty()) {
|
|
StringBuffer &oss = m_buffers.back()->oss;
|
|
if (!oss.empty()) {
|
|
return oss.copy();
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
String BaseExecutionContext::obDetachContents() {
|
|
if (!m_buffers.empty()) {
|
|
StringBuffer &oss = m_buffers.back()->oss;
|
|
if (!oss.empty()) {
|
|
return oss.detach();
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
int BaseExecutionContext::obGetContentLength() {
|
|
if (m_buffers.empty()) {
|
|
return 0;
|
|
}
|
|
return m_buffers.back()->oss.size();
|
|
}
|
|
|
|
void BaseExecutionContext::obClean() {
|
|
if (!m_buffers.empty()) {
|
|
m_buffers.back()->oss.reset();
|
|
}
|
|
}
|
|
|
|
bool BaseExecutionContext::obFlush() {
|
|
assert(m_protectedLevel >= 0);
|
|
if ((int)m_buffers.size() > m_protectedLevel) {
|
|
std::list<OutputBuffer*>::const_iterator iter = m_buffers.end();
|
|
OutputBuffer *last = *(--iter);
|
|
const int flag = PHP_OUTPUT_HANDLER_START | PHP_OUTPUT_HANDLER_END;
|
|
if (iter != m_buffers.begin()) {
|
|
OutputBuffer *prev = *(--iter);
|
|
if (last->handler.isNull()) {
|
|
prev->oss.absorb(last->oss);
|
|
} else {
|
|
try {
|
|
Variant tout =
|
|
vm_call_user_func(last->handler,
|
|
CREATE_VECTOR2(last->oss.detach(), flag));
|
|
prev->oss.append(tout.toString());
|
|
last->oss.reset();
|
|
} catch (...) {
|
|
prev->oss.absorb(last->oss);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (!last->handler.isNull()) {
|
|
try {
|
|
Variant tout =
|
|
vm_call_user_func(last->handler,
|
|
CREATE_VECTOR2(last->oss.detach(), flag));
|
|
String sout = tout.toString();
|
|
writeStdout(sout.data(), sout.size());
|
|
last->oss.reset();
|
|
return true;
|
|
} catch (...) {}
|
|
}
|
|
|
|
writeStdout(last->oss.data(), last->oss.size());
|
|
last->oss.reset();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void BaseExecutionContext::obFlushAll() {
|
|
while (obFlush()) { obEnd();}
|
|
}
|
|
|
|
bool BaseExecutionContext::obEnd() {
|
|
assert(m_protectedLevel >= 0);
|
|
if ((int)m_buffers.size() > m_protectedLevel) {
|
|
delete m_buffers.back();
|
|
m_buffers.pop_back();
|
|
resetCurrentBuffer();
|
|
if (m_implicitFlush) flush();
|
|
return true;
|
|
}
|
|
if (m_implicitFlush) flush();
|
|
return false;
|
|
}
|
|
|
|
void BaseExecutionContext::obEndAll() {
|
|
while (obEnd()) {}
|
|
}
|
|
|
|
int BaseExecutionContext::obGetLevel() {
|
|
assert((int)m_buffers.size() >= m_protectedLevel);
|
|
return m_buffers.size() - m_protectedLevel;
|
|
}
|
|
|
|
static const StaticString s_level("level");
|
|
static const StaticString s_type("type");
|
|
static const StaticString s_name("name");
|
|
static const StaticString s_args("args");
|
|
static const StaticString s_default_output_handler("default output handler");
|
|
|
|
Array BaseExecutionContext::obGetStatus(bool full) {
|
|
Array ret = Array::Create();
|
|
std::list<OutputBuffer*>::const_iterator iter = m_buffers.begin();
|
|
++iter; // skip over the fake outermost buffer
|
|
int level = 0;
|
|
for (; iter != m_buffers.end(); ++iter, ++level) {
|
|
Array status;
|
|
status.set(s_level, level);
|
|
if (level < m_protectedLevel) {
|
|
status.set(s_type, 1);
|
|
status.set(s_name, s_default_output_handler);
|
|
} else {
|
|
status.set(s_type, 0);
|
|
status.set(s_name, (*iter)->handler);
|
|
}
|
|
|
|
if (full) {
|
|
ret.append(status);
|
|
} else {
|
|
ret = std::move(status);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void BaseExecutionContext::obSetImplicitFlush(bool on) {
|
|
m_implicitFlush = on;
|
|
}
|
|
|
|
Array BaseExecutionContext::obGetHandlers() {
|
|
Array ret;
|
|
for (std::list<OutputBuffer*>::const_iterator iter = m_buffers.begin();
|
|
iter != m_buffers.end(); ++iter) {
|
|
ret.append((*iter)->handler);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void BaseExecutionContext::flush() {
|
|
if (m_buffers.empty()) {
|
|
fflush(stdout);
|
|
} else if (RuntimeOption::EnableEarlyFlush && m_protectedLevel &&
|
|
(m_transport == nullptr ||
|
|
(m_transport->getHTTPVersion() == "1.1" &&
|
|
m_transport->getMethod() != Transport::HEAD))) {
|
|
StringBuffer &oss = m_buffers.front()->oss;
|
|
if (!oss.empty()) {
|
|
if (m_transport) {
|
|
m_transport->sendRaw((void*)oss.data(), oss.size(), 200, false, true);
|
|
} else {
|
|
writeStdout(oss.data(), oss.size());
|
|
fflush(stdout);
|
|
}
|
|
oss.reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
void BaseExecutionContext::resetCurrentBuffer() {
|
|
if (m_buffers.empty()) {
|
|
m_out = nullptr;
|
|
} else {
|
|
m_out = &m_buffers.back()->oss;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// program executions
|
|
|
|
void BaseExecutionContext::registerShutdownFunction(CVarRef function,
|
|
Array arguments,
|
|
ShutdownType type) {
|
|
Array callback = CREATE_MAP2(s_name, function, s_args, arguments);
|
|
Variant &funcs = m_shutdowns.lvalAt(type);
|
|
funcs.append(callback);
|
|
}
|
|
|
|
Variant BaseExecutionContext::pushUserErrorHandler(CVarRef function,
|
|
int error_types) {
|
|
Variant ret;
|
|
if (!m_userErrorHandlers.empty()) {
|
|
ret = m_userErrorHandlers.back().first;
|
|
}
|
|
m_userErrorHandlers.push_back(std::pair<Variant,int>(function, error_types));
|
|
return ret;
|
|
}
|
|
|
|
Variant BaseExecutionContext::pushUserExceptionHandler(CVarRef function) {
|
|
Variant ret;
|
|
if (!m_userExceptionHandlers.empty()) {
|
|
ret = m_userExceptionHandlers.back();
|
|
}
|
|
m_userExceptionHandlers.push_back(function);
|
|
return ret;
|
|
}
|
|
|
|
void BaseExecutionContext::popUserErrorHandler() {
|
|
if (!m_userErrorHandlers.empty()) {
|
|
m_userErrorHandlers.pop_back();
|
|
}
|
|
}
|
|
|
|
void BaseExecutionContext::popUserExceptionHandler() {
|
|
if (!m_userExceptionHandlers.empty()) {
|
|
m_userExceptionHandlers.pop_back();
|
|
}
|
|
}
|
|
|
|
void BaseExecutionContext::registerRequestEventHandler
|
|
(RequestEventHandler *handler) {
|
|
assert(handler);
|
|
if (m_requestEventHandlerSet.find(handler) ==
|
|
m_requestEventHandlerSet.end()) {
|
|
m_requestEventHandlerSet.insert(handler);
|
|
m_requestEventHandlers.push_back(handler);
|
|
} else {
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
static bool requestEventHandlerPriorityComp(RequestEventHandler *a,
|
|
RequestEventHandler *b) {
|
|
return a->priority() < b->priority();
|
|
}
|
|
|
|
void BaseExecutionContext::onRequestShutdown() {
|
|
// Sort handlers by priority so that lower priority values get shutdown
|
|
// first
|
|
sort(m_requestEventHandlers.begin(), m_requestEventHandlers.end(),
|
|
requestEventHandlerPriorityComp);
|
|
for (unsigned int i = 0; i < m_requestEventHandlers.size(); i++) {
|
|
RequestEventHandler *handler = m_requestEventHandlers[i];
|
|
assert(handler->getInited());
|
|
if (handler->getInited()) {
|
|
handler->requestShutdown();
|
|
handler->setInited(false);
|
|
}
|
|
}
|
|
m_requestEventHandlers.clear();
|
|
m_requestEventHandlerSet.clear();
|
|
}
|
|
|
|
void BaseExecutionContext::executeFunctions(CArrRef funcs) {
|
|
for (ArrayIter iter(funcs); iter; ++iter) {
|
|
Array callback = iter.second();
|
|
vm_call_user_func(callback[s_name], callback[s_args]);
|
|
}
|
|
}
|
|
|
|
void BaseExecutionContext::onShutdownPreSend() {
|
|
if (!m_shutdowns.isNull() && m_shutdowns.exists(ShutDown)) {
|
|
executeFunctions(m_shutdowns[ShutDown]);
|
|
m_shutdowns.remove(ShutDown);
|
|
}
|
|
obFlushAll(); // in case obStart was called without obFlush
|
|
}
|
|
|
|
void BaseExecutionContext::onShutdownPostSend() {
|
|
ServerStats::SetThreadMode(ServerStats::PostProcessing);
|
|
try {
|
|
try {
|
|
ServerStatsHelper ssh("psp", ServerStatsHelper::TRACK_HWINST);
|
|
if (!m_shutdowns.isNull()) {
|
|
if (m_shutdowns.exists(PostSend)) {
|
|
executeFunctions(m_shutdowns[PostSend]);
|
|
m_shutdowns.remove(PostSend);
|
|
}
|
|
if (m_shutdowns.exists(CleanUp)) {
|
|
executeFunctions(m_shutdowns[CleanUp]);
|
|
m_shutdowns.remove(CleanUp);
|
|
}
|
|
}
|
|
} catch (const ExitException &e) {
|
|
// do nothing
|
|
} catch (const Exception &e) {
|
|
onFatalError(e);
|
|
} catch (const Object &e) {
|
|
onUnhandledException(e);
|
|
}
|
|
} catch (...) {
|
|
Logger::Error("unknown exception was thrown from psp");
|
|
}
|
|
ServerStats::SetThreadMode(ServerStats::Idling);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// error handling
|
|
|
|
bool BaseExecutionContext::errorNeedsHandling(int errnum,
|
|
bool callUserHandler,
|
|
ErrorThrowMode mode) {
|
|
if (m_throwAllErrors) throw errnum;
|
|
if (mode != NeverThrow || (getErrorReportingLevel() & errnum) != 0 ||
|
|
RuntimeOption::NoSilencer) {
|
|
return true;
|
|
}
|
|
if (callUserHandler) {
|
|
if (!m_userErrorHandlers.empty() &&
|
|
(m_userErrorHandlers.back().second & errnum) != 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
class ErrorStateHelper {
|
|
public:
|
|
ErrorStateHelper(BaseExecutionContext *context, int state) {
|
|
m_context = context;
|
|
m_originalState = m_context->getErrorState();
|
|
m_context->setErrorState(state);
|
|
}
|
|
~ErrorStateHelper() {
|
|
m_context->setErrorState(m_originalState);
|
|
}
|
|
private:
|
|
BaseExecutionContext *m_context;
|
|
int m_originalState;
|
|
};
|
|
|
|
static StaticString s_file("file");
|
|
static StaticString s_line("line");
|
|
|
|
void BaseExecutionContext::handleError(const std::string &msg,
|
|
int errnum,
|
|
bool callUserHandler,
|
|
ErrorThrowMode mode,
|
|
const std::string &prefix,
|
|
bool skipFrame /* = false */) {
|
|
SYNC_VM_REGS_SCOPED();
|
|
|
|
int newErrorState = ErrorRaised;
|
|
switch (getErrorState()) {
|
|
case ErrorRaised:
|
|
case ErrorRaisedByUserHandler:
|
|
return;
|
|
case ExecutingUserHandler:
|
|
newErrorState = ErrorRaisedByUserHandler;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
ErrorStateHelper esh(this, newErrorState);
|
|
ExtendedException ee = skipFrame ?
|
|
ExtendedException(ExtendedException::skipFrame, msg) :
|
|
ExtendedException(msg);
|
|
Array bt = ee.getBackTrace();
|
|
|
|
recordLastError(ee, errnum);
|
|
bool handled = false;
|
|
if (callUserHandler) {
|
|
handled = callUserErrorHandler(ee, errnum, false);
|
|
}
|
|
if (mode == AlwaysThrow || (mode == ThrowIfUnhandled && !handled)) {
|
|
try {
|
|
if (!Eval::Debugger::InterruptException(String(msg))) return;
|
|
} catch (const Eval::DebuggerClientExitException &e) {}
|
|
throw FatalErrorException(msg, bt);
|
|
}
|
|
if (!handled &&
|
|
(RuntimeOption::NoSilencer ||
|
|
(getErrorReportingLevel() & errnum) != 0)) {
|
|
try {
|
|
if (!Eval::Debugger::InterruptException(String(msg))) return;
|
|
} catch (const Eval::DebuggerClientExitException &e) {}
|
|
|
|
String file = empty_string;
|
|
int line = 0;
|
|
if (RuntimeOption::InjectedStackTrace) {
|
|
if (!bt.empty()) {
|
|
Array top = bt.rvalAt(0).toArray();
|
|
if (top.exists(s_file)) file = top.rvalAt(s_file).toString();
|
|
if (top.exists(s_line)) line = top.rvalAt(s_line);
|
|
}
|
|
}
|
|
|
|
Logger::Log(Logger::LogError, prefix.c_str(), ee, file.c_str(), line);
|
|
}
|
|
}
|
|
|
|
bool BaseExecutionContext::callUserErrorHandler(const Exception &e, int errnum,
|
|
bool swallowExceptions) {
|
|
switch (getErrorState()) {
|
|
case ExecutingUserHandler:
|
|
case ErrorRaisedByUserHandler:
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
if (!m_userErrorHandlers.empty() &&
|
|
(m_userErrorHandlers.back().second & errnum) != 0) {
|
|
int errline = 0;
|
|
String errfile;
|
|
Array backtrace;
|
|
const ExtendedException *ee = dynamic_cast<const ExtendedException*>(&e);
|
|
if (ee) {
|
|
Array arr = ee->getBackTrace();
|
|
if (!arr.isNull()) {
|
|
backtrace = arr;
|
|
Array top = backtrace.rvalAt(0);
|
|
if (!top.isNull()) {
|
|
errfile = top.rvalAt(s_file);
|
|
errline = top.rvalAt(s_line).toInt64();
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
ErrorStateHelper esh(this, ExecutingUserHandler);
|
|
if (!same(vm_call_user_func
|
|
(m_userErrorHandlers.back().first,
|
|
CREATE_VECTOR6(errnum, String(e.getMessage()), errfile,
|
|
errline, "", backtrace)),
|
|
false)) {
|
|
return true;
|
|
}
|
|
} catch (...) {
|
|
if (!swallowExceptions) throw;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void BaseExecutionContext::recordLastError(const Exception &e,
|
|
int errnum /* = 0 */) {
|
|
m_lastError = String(e.getMessage());
|
|
m_lastErrorNum = errnum;
|
|
}
|
|
|
|
bool BaseExecutionContext::onFatalError(const Exception &e) {
|
|
recordLastError(e);
|
|
String file = empty_string;
|
|
int line = 0;
|
|
if (RuntimeOption::InjectedStackTrace) {
|
|
const ExtendedException *ee = dynamic_cast<const ExtendedException *>(&e);
|
|
if (ee) {
|
|
Array bt = ee->getBackTrace();
|
|
if (!bt.empty()) {
|
|
Array top = bt.rvalAt(0).toArray();
|
|
if (top.exists(s_file)) file = top.rvalAt(s_file).toString();
|
|
if (top.exists(s_line)) line = top.rvalAt(s_line);
|
|
}
|
|
}
|
|
}
|
|
if (RuntimeOption::AlwaysLogUnhandledExceptions) {
|
|
Logger::Log(Logger::LogError, "HipHop Fatal error: ", e,
|
|
file.c_str(), line);
|
|
}
|
|
bool handled = false;
|
|
if (RuntimeOption::CallUserHandlerOnFatals) {
|
|
int errnum = ErrorConstants::FATAL_ERROR;
|
|
handled = callUserErrorHandler(e, errnum, true);
|
|
}
|
|
if (!handled && !RuntimeOption::AlwaysLogUnhandledExceptions) {
|
|
Logger::Log(Logger::LogError, "HipHop Fatal error: ", e,
|
|
file.c_str(), line);
|
|
}
|
|
return handled;
|
|
}
|
|
|
|
bool BaseExecutionContext::onUnhandledException(Object e) {
|
|
String err = e.toString();
|
|
if (RuntimeOption::AlwaysLogUnhandledExceptions) {
|
|
Logger::Error("HipHop Fatal error: Uncaught %s", err.data());
|
|
}
|
|
|
|
if (e.instanceof(SystemLib::s_ExceptionClass)) {
|
|
// user thrown exception
|
|
if (!m_userExceptionHandlers.empty()) {
|
|
if (!same(vm_call_user_func
|
|
(m_userExceptionHandlers.back(),
|
|
CREATE_VECTOR1(e)),
|
|
false)) {
|
|
return true;
|
|
}
|
|
}
|
|
} else {
|
|
assert(false);
|
|
}
|
|
m_lastError = err;
|
|
|
|
if (!RuntimeOption::AlwaysLogUnhandledExceptions) {
|
|
Logger::Error("HipHop Fatal error: Uncaught %s", err.data());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void BaseExecutionContext::setLogErrors(bool on) {
|
|
if (m_logErrors != on) {
|
|
m_logErrors = on;
|
|
if (m_logErrors) {
|
|
if (!m_errorLog.empty()) {
|
|
FILE *output = fopen(m_errorLog.data(), "a");
|
|
if (output) {
|
|
Logger::SetNewOutput(output);
|
|
}
|
|
}
|
|
} else {
|
|
Logger::SetNewOutput(nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void BaseExecutionContext::setErrorLog(CStrRef filename) {
|
|
m_errorLog = filename;
|
|
if (m_logErrors && !m_errorLog.empty()) {
|
|
FILE *output = fopen(m_errorLog.data(), "a");
|
|
if (output) {
|
|
Logger::SetNewOutput(output);
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// IDebuggable
|
|
|
|
void BaseExecutionContext::debuggerInfo(InfoVec &info) {
|
|
if (m_maxMemory <= 0) {
|
|
Add(info, "Max Memory", "(unlimited)");
|
|
} else {
|
|
Add(info, "Max Memory", FormatSize(m_maxMemory));
|
|
}
|
|
Add(info, "Max Time", FormatTime(m_maxTime * 1000));
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void BaseExecutionContext::setenv(CStrRef name, CStrRef value) {
|
|
m_envs.set(name, value);
|
|
}
|
|
|
|
String BaseExecutionContext::getenv(CStrRef name) const {
|
|
if (m_envs.exists(name)) {
|
|
return m_envs[name];
|
|
}
|
|
char *value = ::getenv(name.data());
|
|
if (value) {
|
|
return String(value, CopyString);
|
|
}
|
|
if (RuntimeOption::EnvVariables.find(name.c_str()) != RuntimeOption::EnvVariables.end()) {
|
|
return String(RuntimeOption::EnvVariables[name.c_str()].data(), CopyString);
|
|
}
|
|
return String();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void BaseExecutionContext::setIncludePath(CStrRef path) {
|
|
m_include_paths = f_explode(":", path);
|
|
}
|
|
|
|
String BaseExecutionContext::getIncludePath() const {
|
|
StringBuffer sb;
|
|
bool first = true;
|
|
for (ArrayIter iter(m_include_paths); iter; ++iter) {
|
|
if (first) {
|
|
first = false;
|
|
} else {
|
|
sb.append(':');
|
|
}
|
|
sb.append(iter.second().toString());
|
|
}
|
|
return sb.detach();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// persistent objects
|
|
|
|
IMPLEMENT_THREAD_LOCAL_NO_CHECK(PersistentObjectStore, g_persistentObjects);
|
|
|
|
void PersistentObjectStore::removeObject(ResourceData *data) {
|
|
if (data) {
|
|
if (data->decRefCount() == 0) {
|
|
data->release();
|
|
} else {
|
|
SweepableResourceData *sw = dynamic_cast<SweepableResourceData*>(data);
|
|
if (sw) {
|
|
sw->decPersistent();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PersistentObjectStore::~PersistentObjectStore() {
|
|
for (ResourceMapMap::const_iterator iter = m_objects.begin();
|
|
iter != m_objects.end(); ++iter) {
|
|
const ResourceMap &resources = iter->second;
|
|
for (ResourceMap::const_iterator iterInner = resources.begin();
|
|
iterInner != resources.end(); ++iterInner) {
|
|
removeObject(iterInner->second);
|
|
}
|
|
}
|
|
}
|
|
|
|
int PersistentObjectStore::size() const {
|
|
int total = 0;
|
|
for (ResourceMapMap::const_iterator iter = m_objects.begin();
|
|
iter != m_objects.end(); ++iter) {
|
|
total += iter->second.size();
|
|
}
|
|
return total;
|
|
}
|
|
|
|
void PersistentObjectStore::set(const char *type, const char *name,
|
|
ResourceData *obj) {
|
|
assert(type && *type);
|
|
assert(name);
|
|
{
|
|
ResourceMap &resources = m_objects[type];
|
|
ResourceMap::iterator iter = resources.find(name);
|
|
if (iter != resources.end()) {
|
|
if (iter->second == obj) {
|
|
return; // we are setting the same object
|
|
}
|
|
removeObject(iter->second);
|
|
resources.erase(iter);
|
|
}
|
|
}
|
|
if (obj) {
|
|
obj->incRefCount();
|
|
SweepableResourceData *sw = dynamic_cast<SweepableResourceData*>(obj);
|
|
if (sw) {
|
|
sw->incPersistent();
|
|
}
|
|
m_objects[type][name] = obj;
|
|
}
|
|
}
|
|
|
|
ResourceData *PersistentObjectStore::get(const char *type, const char *name) {
|
|
assert(type && *type);
|
|
assert(name);
|
|
ResourceMap &resources = m_objects[type];
|
|
ResourceMap::const_iterator iter = resources.find(name);
|
|
if (iter == resources.end()) {
|
|
return nullptr;
|
|
}
|
|
return iter->second;
|
|
}
|
|
|
|
void PersistentObjectStore::remove(const char *type, const char *name) {
|
|
assert(type && *type);
|
|
assert(name);
|
|
ResourceMap &resources = m_objects[type];
|
|
ResourceMap::iterator iter = resources.find(name);
|
|
if (iter != resources.end()) {
|
|
removeObject(iter->second);
|
|
resources.erase(iter);
|
|
}
|
|
}
|
|
|
|
const ResourceMap &PersistentObjectStore::getMap(const char *type) {
|
|
assert(type && *type);
|
|
return m_objects[type];
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// silencer
|
|
|
|
|
|
Silencer::Silencer(bool e) : m_active(false) {
|
|
if (e) enable();
|
|
}
|
|
|
|
void Silencer::enable() {
|
|
m_errorReportingValue = g_context->getErrorReportingLevel();
|
|
g_context->setErrorReportingLevel(0);
|
|
m_active = true;
|
|
}
|
|
|
|
void Silencer::disableHelper() {
|
|
if (m_active) {
|
|
if (g_context->getErrorReportingLevel() == 0)
|
|
g_context->setErrorReportingLevel(m_errorReportingValue);
|
|
}
|
|
}
|
|
|
|
Variant Silencer::disable(CVarRef v) {
|
|
disable();
|
|
return v;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|