b7d2212723
We were maintaining a flag that said if we were interpreting on the EC. It was only used in the implementation of hphp_break(), for pretty minimal benefit. I yanked it… it seems fine to me to throw the switch exception if someone steps out of the hard break even if we're already interpreting. Also yank out a vestige of the previous stepping and breakpoint logic from the dispatch loop I noticed today. I'd previously missed it, but I made a change a while ago to put the state of the last location filter into the hands of the flow control commands. This was unnecessary and should have been removed then.
942 linhas
27 KiB
C++
942 linhas
27 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_breakPointFilter(nullptr), m_lastLocFilter(nullptr),
|
|
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_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;
|
|
}
|
|
|
|
const StaticString
|
|
s_level("level"),
|
|
s_type("type"),
|
|
s_name("name"),
|
|
s_args("args"),
|
|
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)) {
|
|
DEBUGGER_ATTACHED_ONLY(phpDebuggerErrorHook(msg));
|
|
throw FatalErrorException(msg, bt);
|
|
}
|
|
if (!handled &&
|
|
(RuntimeOption::NoSilencer ||
|
|
(getErrorReportingLevel() & errnum) != 0)) {
|
|
DEBUGGER_ATTACHED_ONLY(phpDebuggerErrorHook(msg));
|
|
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;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|