27e29de6f4
This allows us to swap TypedValue.m_type and m_aux, which puts m_type at offset 8 and simplifies the necessary code for passing TypedValue by value. It also makes the m_type and m_data fields of TypedValue contiguous.
7248 linhas
216 KiB
C++
7248 linhas
216 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/vm/bytecode.h"
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <sstream>
|
|
|
|
#include "folly/String.h"
|
|
|
|
#include "hphp/runtime/base/tv_comparisons.h"
|
|
#include "hphp/runtime/base/tv_conversions.h"
|
|
#include "hphp/runtime/base/tv_arith.h"
|
|
#include "hphp/compiler/builtin_symbols.h"
|
|
#include "hphp/runtime/vm/event_hook.h"
|
|
#include "hphp/runtime/vm/jit/translator.h"
|
|
#include "hphp/runtime/vm/srckey.h"
|
|
#include "hphp/runtime/vm/member_operations.h"
|
|
#include "hphp/runtime/base/class_info.h"
|
|
#include "hphp/runtime/base/code_coverage.h"
|
|
#include "hphp/runtime/base/file_repository.h"
|
|
#include "hphp/runtime/base/base_includes.h"
|
|
#include "hphp/runtime/base/execution_context.h"
|
|
#include "hphp/runtime/base/runtime_option.h"
|
|
#include "hphp/runtime/base/hphp_array.h"
|
|
#include "hphp/runtime/base/strings.h"
|
|
#include "hphp/util/util.h"
|
|
#include "hphp/util/trace.h"
|
|
#include "hphp/util/debug.h"
|
|
#include "hphp/runtime/base/stat_cache.h"
|
|
#include "hphp/runtime/vm/debug/debug.h"
|
|
#include "hphp/runtime/vm/hhbc.h"
|
|
#include "hphp/runtime/vm/php_debug.h"
|
|
#include "hphp/runtime/vm/debugger_hook.h"
|
|
#include "hphp/runtime/vm/runtime.h"
|
|
#include "hphp/runtime/vm/jit/target-cache.h"
|
|
#include "hphp/runtime/vm/type_constraint.h"
|
|
#include "hphp/runtime/vm/unwind.h"
|
|
#include "hphp/runtime/vm/jit/translator-inline.h"
|
|
#include "hphp/runtime/ext/ext_string.h"
|
|
#include "hphp/runtime/ext/ext_error.h"
|
|
#include "hphp/runtime/ext/ext_closure.h"
|
|
#include "hphp/runtime/ext/ext_continuation.h"
|
|
#include "hphp/runtime/ext/ext_function.h"
|
|
#include "hphp/runtime/ext/ext_variable.h"
|
|
#include "hphp/runtime/ext/ext_array.h"
|
|
#include "hphp/runtime/base/stats.h"
|
|
#include "hphp/runtime/vm/type_profile.h"
|
|
#include "hphp/runtime/server/source_root_info.h"
|
|
#include "hphp/runtime/base/extended_logger.h"
|
|
#include "hphp/runtime/base/tracer.h"
|
|
|
|
#include "hphp/system/systemlib.h"
|
|
#include "hphp/runtime/ext/ext_collections.h"
|
|
|
|
#include "hphp/runtime/vm/name_value_table_wrapper.h"
|
|
#include "hphp/runtime/vm/request_arena.h"
|
|
#include "hphp/util/arena.h"
|
|
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
#include <algorithm>
|
|
#include <boost/format.hpp>
|
|
#include <boost/utility/typed_in_place_factory.hpp>
|
|
|
|
#include <cinttypes>
|
|
|
|
#include <libgen.h>
|
|
#include <sys/mman.h>
|
|
|
|
namespace HPHP {
|
|
|
|
// TODO: #1746957, #1756122
|
|
// we should skip the call in call_user_func_array, if
|
|
// by reference params are passed by value, or if its
|
|
// argument is not an array, but currently lots of tests
|
|
// depend on actually making the call.
|
|
const bool skipCufOnInvalidParams = false;
|
|
|
|
// RepoAuthoritative has been raptured out of runtime_option.cpp. It needs
|
|
// to be closer to other bytecode.cpp data.
|
|
bool RuntimeOption::RepoAuthoritative = false;
|
|
|
|
using std::string;
|
|
|
|
using Transl::VMRegAnchor;
|
|
using Transl::EagerVMRegAnchor;
|
|
|
|
#if DEBUG
|
|
#define OPTBLD_INLINE
|
|
#else
|
|
#define OPTBLD_INLINE ALWAYS_INLINE
|
|
#endif
|
|
TRACE_SET_MOD(bcinterp);
|
|
|
|
ActRec* ActRec::arGetSfp() const {
|
|
ActRec* prevFrame = (ActRec*)m_savedRbp;
|
|
if (LIKELY(((uintptr_t)prevFrame - Util::s_stackLimit) >=
|
|
Util::s_stackSize)) {
|
|
if (LIKELY(prevFrame != nullptr)) return prevFrame;
|
|
}
|
|
|
|
return const_cast<ActRec*>(this);
|
|
}
|
|
|
|
bool
|
|
ActRec::skipFrame() const {
|
|
return m_func && m_func->skipFrame();
|
|
}
|
|
|
|
template <>
|
|
Class* arGetContextClassImpl<false>(const ActRec* ar) {
|
|
if (ar == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return ar->m_func->cls();
|
|
}
|
|
|
|
template <>
|
|
Class* arGetContextClassImpl<true>(const ActRec* ar) {
|
|
if (ar == nullptr) {
|
|
return nullptr;
|
|
}
|
|
if (ar->m_func->isPseudoMain() || ar->m_func->isBuiltin()) {
|
|
// Pseudomains inherit the context of their caller
|
|
VMExecutionContext* context = g_vmContext;
|
|
ar = context->getPrevVMState(ar);
|
|
while (ar != nullptr &&
|
|
(ar->m_func->isPseudoMain() || ar->m_func->isBuiltin())) {
|
|
ar = context->getPrevVMState(ar);
|
|
}
|
|
if (ar == nullptr) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
return ar->m_func->cls();
|
|
}
|
|
|
|
const StaticString s_call_user_func("call_user_func");
|
|
const StaticString s_call_user_func_array("call_user_func_array");
|
|
const StaticString s_stdclass("stdclass");
|
|
const StaticString s___call("__call");
|
|
const StaticString s___callStatic("__callStatic");
|
|
const StaticString s_file("file");
|
|
const StaticString s_line("line");
|
|
const StaticString s_function("function");
|
|
const StaticString s_args("args");
|
|
const StaticString s_class("class");
|
|
const StaticString s_object("object");
|
|
const StaticString s_type("type");
|
|
const StaticString s_include("include");
|
|
|
|
static inline
|
|
Transl::Translator* tx() {
|
|
return Transl::Translator::Get();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//=============================================================================
|
|
// Miscellaneous macros.
|
|
|
|
#define NEXT() pc++
|
|
#define DECODE_JMP(type, var) \
|
|
type var __attribute__((unused)) = *(type*)pc; \
|
|
ONTRACE(2, \
|
|
Trace::trace("decode: Immediate %s %" PRIi64"\n", #type, \
|
|
(int64_t)var));
|
|
#define ITER_SKIP(offset) pc = origPc + (offset);
|
|
|
|
#define DECODE(type, var) \
|
|
DECODE_JMP(type, var); \
|
|
pc += sizeof(type)
|
|
#define DECODE_IVA(var) \
|
|
int32_t var UNUSED = decodeVariableSizeImm(&pc); \
|
|
ONTRACE(2, \
|
|
Trace::trace("decode: Immediate int32 %" PRIi64"\n", \
|
|
(int64_t)var));
|
|
#define DECODE_LITSTR(var) \
|
|
StringData* var; \
|
|
do { \
|
|
DECODE(Id, id); \
|
|
var = m_fp->m_func->unit()->lookupLitstrId(id); \
|
|
} while (false)
|
|
|
|
#define DECODE_HA(var) DECODE_IVA(var)
|
|
#define DECODE_IA(var) DECODE_IVA(var)
|
|
|
|
#define DECODE_ITER_LIST(typeList, idList, vecLen) \
|
|
DECODE(int32_t, vecLen); \
|
|
assert(vecLen > 0); \
|
|
Id* typeList = (Id*)pc; \
|
|
Id* idList = (Id*)pc + 1; \
|
|
pc += 2 * vecLen * sizeof(Id);
|
|
|
|
#define SYNC() m_pc = pc
|
|
|
|
//=============================================================================
|
|
// Miscellaneous helpers.
|
|
|
|
static inline Class* frameStaticClass(ActRec* fp) {
|
|
if (fp->hasThis()) {
|
|
return fp->getThis()->getVMClass();
|
|
} else if (fp->hasClass()) {
|
|
return fp->getClass();
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
// VarEnv.
|
|
|
|
VarEnv::VarEnv()
|
|
: m_depth(0)
|
|
, m_malloced(false)
|
|
, m_global(false)
|
|
, m_cfp(0)
|
|
, m_nvTable(boost::in_place<NameValueTable>(
|
|
RuntimeOption::EvalVMInitialGlobalTableSize))
|
|
{
|
|
TypedValue globalArray;
|
|
globalArray.m_type = KindOfArray;
|
|
globalArray.m_data.parr =
|
|
new (request_arena()) GlobalNameValueTableWrapper(&*m_nvTable);
|
|
globalArray.m_data.parr->incRefCount();
|
|
m_nvTable->set(StringData::GetStaticString("GLOBALS"), &globalArray);
|
|
tvRefcountedDecRef(&globalArray);
|
|
}
|
|
|
|
VarEnv::VarEnv(ActRec* fp, ExtraArgs* eArgs)
|
|
: m_extraArgs(eArgs)
|
|
, m_depth(1)
|
|
, m_malloced(false)
|
|
, m_global(false)
|
|
, m_cfp(fp)
|
|
{
|
|
const Func* func = fp->m_func;
|
|
const Id numNames = func->numNamedLocals();
|
|
|
|
if (!numNames) return;
|
|
|
|
m_nvTable = boost::in_place<NameValueTable>(numNames);
|
|
|
|
TypedValue** origLocs =
|
|
reinterpret_cast<TypedValue**>(uintptr_t(this) + sizeof(VarEnv));
|
|
TypedValue* loc = frame_local(fp, 0);
|
|
for (Id i = 0; i < numNames; ++i, --loc) {
|
|
assert(func->lookupVarId(func->localVarName(i)) == (int)i);
|
|
origLocs[i] = m_nvTable->migrateSet(func->localVarName(i), loc);
|
|
}
|
|
}
|
|
|
|
VarEnv::~VarEnv() {
|
|
TRACE(3, "Destroying VarEnv %p [%s]\n",
|
|
this,
|
|
isGlobalScope() ? "global scope" : "local scope");
|
|
assert(m_restoreLocations.empty());
|
|
|
|
if (!isGlobalScope()) {
|
|
if (LIKELY(!m_malloced)) {
|
|
varenv_arena().endFrame();
|
|
return;
|
|
}
|
|
} else {
|
|
/*
|
|
* When detaching the global scope, we leak any live objects (and
|
|
* let the smart allocator clean them up). This is because we're
|
|
* not supposed to run destructors for objects that are live at
|
|
* the end of a request.
|
|
*/
|
|
m_nvTable->leak();
|
|
}
|
|
}
|
|
|
|
size_t VarEnv::getObjectSz(ActRec* fp) {
|
|
return sizeof(VarEnv) + sizeof(TypedValue*) * fp->m_func->numNamedLocals();
|
|
}
|
|
|
|
VarEnv* VarEnv::createLocalOnStack(ActRec* fp) {
|
|
auto& va = varenv_arena();
|
|
va.beginFrame();
|
|
void* mem = va.alloc(getObjectSz(fp));
|
|
VarEnv* ret = new (mem) VarEnv(fp, fp->getExtraArgs());
|
|
TRACE(3, "Creating lazily attached VarEnv %p on stack\n", mem);
|
|
return ret;
|
|
}
|
|
|
|
VarEnv* VarEnv::createLocalOnHeap(ActRec* fp) {
|
|
void* mem = malloc(getObjectSz(fp));
|
|
VarEnv* ret = new (mem) VarEnv(fp, fp->getExtraArgs());
|
|
TRACE(3, "Creating lazily attached VarEnv %p on heap\n", mem);
|
|
ret->m_malloced = true;
|
|
return ret;
|
|
}
|
|
|
|
VarEnv* VarEnv::createGlobal() {
|
|
assert(!g_vmContext->m_globalVarEnv);
|
|
|
|
VarEnv* ret = new (request_arena()) VarEnv();
|
|
TRACE(3, "Creating VarEnv %p [global scope]\n", ret);
|
|
ret->m_global = true;
|
|
g_vmContext->m_globalVarEnv = ret;
|
|
return ret;
|
|
}
|
|
|
|
void VarEnv::destroy(VarEnv* ve) {
|
|
bool malloced = ve->m_malloced;
|
|
ve->~VarEnv();
|
|
if (UNLIKELY(malloced)) free(ve);
|
|
}
|
|
|
|
void VarEnv::attach(ActRec* fp) {
|
|
TRACE(3, "Attaching VarEnv %p [%s] %d fp @%p\n",
|
|
this,
|
|
isGlobalScope() ? "global scope" : "local scope",
|
|
int(fp->m_func->numNamedLocals()), fp);
|
|
assert(m_depth == 0 || fp->arGetSfp() == m_cfp ||
|
|
(fp->arGetSfp() == fp && g_vmContext->isNested()));
|
|
m_cfp = fp;
|
|
m_depth++;
|
|
|
|
// Overlay fp's locals, if it has any.
|
|
|
|
const Func* func = fp->m_func;
|
|
const Id numNames = func->numNamedLocals();
|
|
if (!numNames) {
|
|
return;
|
|
}
|
|
if (!m_nvTable) {
|
|
m_nvTable = boost::in_place<NameValueTable>(numNames);
|
|
}
|
|
|
|
TypedValue** origLocs = new (varenv_arena()) TypedValue*[
|
|
func->numNamedLocals()];
|
|
TypedValue* loc = frame_local(fp, 0);
|
|
for (Id i = 0; i < numNames; ++i, --loc) {
|
|
assert(func->lookupVarId(func->localVarName(i)) == (int)i);
|
|
origLocs[i] = m_nvTable->migrate(func->localVarName(i), loc);
|
|
}
|
|
m_restoreLocations.push_back(origLocs);
|
|
}
|
|
|
|
void VarEnv::detach(ActRec* fp) {
|
|
TRACE(3, "Detaching VarEnv %p [%s] @%p\n",
|
|
this,
|
|
isGlobalScope() ? "global scope" : "local scope",
|
|
fp);
|
|
assert(fp == m_cfp);
|
|
assert(m_depth > 0);
|
|
|
|
// Merge/remove fp's overlaid locals, if it had any.
|
|
const Func* func = fp->m_func;
|
|
if (Id const numLocals = func->numNamedLocals()) {
|
|
/*
|
|
* In the case of a lazily attached VarEnv, we have our locations
|
|
* for the first (lazy) attach stored immediately following the
|
|
* VarEnv in memory. In this case m_restoreLocations will be empty.
|
|
*/
|
|
assert((!isGlobalScope() && m_depth == 1) == m_restoreLocations.empty());
|
|
TypedValue** origLocs =
|
|
!m_restoreLocations.empty()
|
|
? m_restoreLocations.back()
|
|
: reinterpret_cast<TypedValue**>(uintptr_t(this) + sizeof(VarEnv));
|
|
|
|
for (Id i = 0; i < numLocals; i++) {
|
|
m_nvTable->resettle(func->localVarName(i), origLocs[i]);
|
|
}
|
|
if (!m_restoreLocations.empty()) {
|
|
m_restoreLocations.pop_back();
|
|
}
|
|
}
|
|
|
|
VMExecutionContext* context = g_vmContext;
|
|
m_cfp = context->getPrevVMState(fp);
|
|
m_depth--;
|
|
if (m_depth == 0) {
|
|
m_cfp = nullptr;
|
|
// don't free global varEnv
|
|
if (context->m_globalVarEnv != this) {
|
|
assert(!isGlobalScope());
|
|
destroy(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
// This helper is creating a NVT because of dynamic variable accesses,
|
|
// even though we're already attached to a frame and it had no named
|
|
// locals.
|
|
void VarEnv::ensureNvt() {
|
|
const size_t kLazyNvtSize = 3;
|
|
if (!m_nvTable) {
|
|
m_nvTable = boost::in_place<NameValueTable>(kLazyNvtSize);
|
|
}
|
|
}
|
|
|
|
void VarEnv::set(const StringData* name, TypedValue* tv) {
|
|
ensureNvt();
|
|
m_nvTable->set(name, tv);
|
|
}
|
|
|
|
void VarEnv::bind(const StringData* name, TypedValue* tv) {
|
|
ensureNvt();
|
|
m_nvTable->bind(name, tv);
|
|
}
|
|
|
|
void VarEnv::setWithRef(const StringData* name, TypedValue* tv) {
|
|
if (tv->m_type == KindOfRef) {
|
|
bind(name, tv);
|
|
} else {
|
|
set(name, tv);
|
|
}
|
|
}
|
|
|
|
TypedValue* VarEnv::lookup(const StringData* name) {
|
|
if (!m_nvTable) {
|
|
return 0;
|
|
}
|
|
return m_nvTable->lookup(name);
|
|
}
|
|
|
|
TypedValue* VarEnv::lookupAdd(const StringData* name) {
|
|
ensureNvt();
|
|
return m_nvTable->lookupAdd(name);
|
|
}
|
|
|
|
TypedValue* VarEnv::lookupRawPointer(const StringData* name) {
|
|
ensureNvt();
|
|
return m_nvTable->lookupRawPointer(name);
|
|
}
|
|
|
|
TypedValue* VarEnv::lookupAddRawPointer(const StringData* name) {
|
|
ensureNvt();
|
|
return m_nvTable->lookupAddRawPointer(name);
|
|
}
|
|
|
|
bool VarEnv::unset(const StringData* name) {
|
|
if (!m_nvTable) return true;
|
|
m_nvTable->unset(name);
|
|
return true;
|
|
}
|
|
|
|
Array VarEnv::getDefinedVariables() const {
|
|
Array ret = Array::Create();
|
|
|
|
if (!m_nvTable) return ret;
|
|
|
|
NameValueTable::Iterator iter(&*m_nvTable);
|
|
for (; iter.valid(); iter.next()) {
|
|
const StringData* sd = iter.curKey();
|
|
const TypedValue* tv = iter.curVal();
|
|
if (tvAsCVarRef(tv).isReferenced()) {
|
|
ret.setRef(StrNR(sd).asString(), tvAsCVarRef(tv));
|
|
} else {
|
|
ret.add(StrNR(sd).asString(), tvAsCVarRef(tv));
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
TypedValue* VarEnv::getExtraArg(unsigned argInd) const {
|
|
return m_extraArgs->getExtraArg(argInd);
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
ExtraArgs::ExtraArgs() {}
|
|
ExtraArgs::~ExtraArgs() {}
|
|
|
|
void* ExtraArgs::allocMem(unsigned nargs) {
|
|
return smart_malloc(sizeof(TypedValue) * nargs + sizeof(ExtraArgs));
|
|
}
|
|
|
|
ExtraArgs* ExtraArgs::allocateCopy(TypedValue* args, unsigned nargs) {
|
|
void* mem = allocMem(nargs);
|
|
ExtraArgs* ea = new (mem) ExtraArgs();
|
|
|
|
/*
|
|
* The stack grows downward, so the args in memory are "backward"; i.e. the
|
|
* leftmost (in PHP) extra arg is highest in memory.
|
|
*/
|
|
std::reverse_copy(args, args + nargs, &ea->m_extraArgs[0]);
|
|
return ea;
|
|
}
|
|
|
|
ExtraArgs* ExtraArgs::allocateUninit(unsigned nargs) {
|
|
void* mem = ExtraArgs::allocMem(nargs);
|
|
return new (mem) ExtraArgs();
|
|
}
|
|
|
|
void ExtraArgs::deallocate(ExtraArgs* ea, unsigned nargs) {
|
|
assert(nargs > 0);
|
|
|
|
for (unsigned i = 0; i < nargs; ++i) {
|
|
tvRefcountedDecRef(ea->m_extraArgs + i);
|
|
}
|
|
ea->~ExtraArgs();
|
|
smart_free(ea);
|
|
}
|
|
|
|
void ExtraArgs::deallocate(ActRec* ar) {
|
|
const int numExtra = ar->numArgs() - ar->m_func->numParams();
|
|
deallocate(ar->getExtraArgs(), numExtra);
|
|
}
|
|
|
|
TypedValue* ExtraArgs::getExtraArg(unsigned argInd) const {
|
|
return const_cast<TypedValue*>(&m_extraArgs[argInd]);
|
|
}
|
|
|
|
//=============================================================================
|
|
// Stack.
|
|
|
|
// Store actual stack elements array in a thread-local in order to amortize the
|
|
// cost of allocation.
|
|
class StackElms {
|
|
public:
|
|
StackElms() : m_elms(nullptr) {}
|
|
~StackElms() {
|
|
flush();
|
|
}
|
|
TypedValue* elms() {
|
|
if (m_elms == nullptr) {
|
|
// RuntimeOption::EvalVMStackElms-sized and -aligned.
|
|
size_t algnSz = RuntimeOption::EvalVMStackElms * sizeof(TypedValue);
|
|
if (posix_memalign((void**)&m_elms, algnSz, algnSz) != 0) {
|
|
throw std::runtime_error(
|
|
std::string("VM stack initialization failed: ") + strerror(errno));
|
|
}
|
|
}
|
|
return m_elms;
|
|
}
|
|
void flush() {
|
|
if (m_elms != nullptr) {
|
|
free(m_elms);
|
|
m_elms = nullptr;
|
|
}
|
|
}
|
|
private:
|
|
TypedValue* m_elms;
|
|
};
|
|
IMPLEMENT_THREAD_LOCAL(StackElms, t_se);
|
|
|
|
const int Stack::sSurprisePageSize = sysconf(_SC_PAGESIZE);
|
|
// We reserve the bottom page of each stack for use as the surprise
|
|
// page, so the minimum useful stack size is the next power of two.
|
|
const uint Stack::sMinStackElms = 2 * sSurprisePageSize / sizeof(TypedValue);
|
|
|
|
void Stack::ValidateStackSize() {
|
|
if (RuntimeOption::EvalVMStackElms < sMinStackElms) {
|
|
throw std::runtime_error(str(
|
|
boost::format("VM stack size of 0x%llx is below the minimum of 0x%x")
|
|
% RuntimeOption::EvalVMStackElms
|
|
% sMinStackElms));
|
|
}
|
|
if (!Util::isPowerOfTwo(RuntimeOption::EvalVMStackElms)) {
|
|
throw std::runtime_error(str(
|
|
boost::format("VM stack size of 0x%llx is not a power of 2")
|
|
% RuntimeOption::EvalVMStackElms));
|
|
}
|
|
}
|
|
|
|
Stack::Stack()
|
|
: m_elms(nullptr), m_top(nullptr), m_base(nullptr) {
|
|
}
|
|
|
|
Stack::~Stack() {
|
|
requestExit();
|
|
}
|
|
|
|
void
|
|
Stack::protect() {
|
|
if (Transl::trustSigSegv) {
|
|
mprotect(m_elms, sizeof(void*), PROT_NONE);
|
|
}
|
|
}
|
|
|
|
void
|
|
Stack::unprotect() {
|
|
if (Transl::trustSigSegv) {
|
|
mprotect(m_elms, sizeof(void*), PROT_READ | PROT_WRITE);
|
|
}
|
|
}
|
|
|
|
void
|
|
Stack::requestInit() {
|
|
m_elms = t_se->elms();
|
|
if (Transl::trustSigSegv) {
|
|
RequestInjectionData& data = ThreadInfo::s_threadInfo->m_reqInjectionData;
|
|
Lock l(data.surpriseLock);
|
|
assert(data.surprisePage == nullptr);
|
|
data.surprisePage = m_elms;
|
|
}
|
|
// Burn one element of the stack, to satisfy the constraint that
|
|
// valid m_top values always have the same high-order (>
|
|
// log(RuntimeOption::EvalVMStackElms)) bits.
|
|
m_top = m_base = m_elms + RuntimeOption::EvalVMStackElms - 1;
|
|
|
|
// Because of the surprise page at the bottom of the stack we lose an
|
|
// additional 256 elements which must be taken into account when checking for
|
|
// overflow.
|
|
UNUSED size_t maxelms =
|
|
RuntimeOption::EvalVMStackElms - sSurprisePageSize / sizeof(TypedValue);
|
|
assert(!wouldOverflow(maxelms - 1));
|
|
assert(wouldOverflow(maxelms));
|
|
|
|
// Reset permissions on our stack's surprise page
|
|
unprotect();
|
|
}
|
|
|
|
void
|
|
Stack::requestExit() {
|
|
if (m_elms != nullptr) {
|
|
if (Transl::trustSigSegv) {
|
|
RequestInjectionData& data = ThreadInfo::s_threadInfo->m_reqInjectionData;
|
|
Lock l(data.surpriseLock);
|
|
assert(data.surprisePage == m_elms);
|
|
unprotect();
|
|
data.surprisePage = nullptr;
|
|
}
|
|
m_elms = nullptr;
|
|
}
|
|
}
|
|
|
|
void flush_evaluation_stack() {
|
|
if (g_context.isNull()) {
|
|
// For RPCRequestHandler threads, the ExecutionContext can stay alive
|
|
// across requests, and hold references to the VM stack, and
|
|
// the TargetCache needs to keep track of which classes are live etc
|
|
// So only flush the VM stack and the target cache if the execution
|
|
// context is dead.
|
|
|
|
if (!t_se.isNull()) {
|
|
t_se->flush();
|
|
}
|
|
Transl::TargetCache::flush();
|
|
}
|
|
}
|
|
|
|
static std::string toStringElm(const TypedValue* tv) {
|
|
std::ostringstream os;
|
|
|
|
if (tv->m_type < MinDataType || tv->m_type > MaxNumDataTypes) {
|
|
os << " ??? type " << tv->m_type << "\n";
|
|
return os.str();
|
|
}
|
|
|
|
assert(tv->m_type >= MinDataType && tv->m_type < MaxNumDataTypes);
|
|
if (IS_REFCOUNTED_TYPE(tv->m_type) && tv->m_data.pref->m_count <= 0) {
|
|
// OK in the invoking frame when running a destructor.
|
|
os << " ??? inner_count " << tv->m_data.pref->m_count << " ";
|
|
return os.str();
|
|
}
|
|
|
|
switch (tv->m_type) {
|
|
case KindOfRef:
|
|
os << "V:(";
|
|
os << "@" << tv->m_data.pref;
|
|
os << toStringElm(tv->m_data.pref->tv());
|
|
os << ")";
|
|
return os.str();
|
|
case KindOfClass:
|
|
os << "A:";
|
|
break;
|
|
default:
|
|
os << "C:";
|
|
break;
|
|
}
|
|
|
|
switch (tv->m_type) {
|
|
case KindOfUninit:
|
|
os << "Uninit";
|
|
break;
|
|
case KindOfNull:
|
|
os << "Null";
|
|
break;
|
|
case KindOfBoolean:
|
|
os << (tv->m_data.num ? "True" : "False");
|
|
break;
|
|
case KindOfInt64:
|
|
os << "0x" << std::hex << tv->m_data.num << std::dec;
|
|
break;
|
|
case KindOfDouble:
|
|
os << tv->m_data.dbl;
|
|
break;
|
|
case KindOfStaticString:
|
|
case KindOfString:
|
|
{
|
|
int len = tv->m_data.pstr->size();
|
|
bool truncated = false;
|
|
if (len > 128) {
|
|
len = 128;
|
|
truncated = true;
|
|
}
|
|
os << tv->m_data.pstr
|
|
<< "c(" << tv->m_data.pstr->getCount() << ")"
|
|
<< ":\""
|
|
<< Util::escapeStringForCPP(tv->m_data.pstr->data(), len)
|
|
<< "\"" << (truncated ? "..." : "");
|
|
}
|
|
break;
|
|
case KindOfArray:
|
|
assert(tv->m_data.parr->getCount() > 0);
|
|
os << tv->m_data.parr
|
|
<< "c(" << tv->m_data.parr->getCount() << ")"
|
|
<< ":Array";
|
|
break;
|
|
case KindOfObject:
|
|
assert(tv->m_data.pobj->getCount() > 0);
|
|
os << tv->m_data.pobj
|
|
<< "c(" << tv->m_data.pobj->getCount() << ")"
|
|
<< ":Object("
|
|
<< tvAsCVarRef(tv).asCObjRef().get()->o_getClassName().get()->data()
|
|
<< ")";
|
|
break;
|
|
case KindOfRef:
|
|
not_reached();
|
|
case KindOfClass:
|
|
os << tv->m_data.pcls
|
|
<< ":" << tv->m_data.pcls->name()->data();
|
|
break;
|
|
default:
|
|
os << "?";
|
|
break;
|
|
}
|
|
|
|
return os.str();
|
|
}
|
|
|
|
static std::string toStringIter(const Iter* it, bool itRef) {
|
|
if (itRef) return "I:MutableArray";
|
|
|
|
// TODO(#2458166): it might be a CufIter, but we're just lucky that
|
|
// the bit pattern for the CufIter is going to have a 0 in
|
|
// getIterType for now.
|
|
switch (it->arr().getIterType()) {
|
|
case ArrayIter::TypeUndefined:
|
|
return "I:Undefined";
|
|
case ArrayIter::TypeArray:
|
|
return "I:Array";
|
|
case ArrayIter::TypeIterator:
|
|
return "I:Iterator";
|
|
}
|
|
assert(false);
|
|
return "I:?";
|
|
}
|
|
|
|
void Stack::toStringFrame(std::ostream& os, const ActRec* fp,
|
|
int offset, const TypedValue* ftop,
|
|
const string& prefix) const {
|
|
assert(fp);
|
|
|
|
// Use depth-first recursion to output the most deeply nested stack frame
|
|
// first.
|
|
{
|
|
Offset prevPc = 0;
|
|
TypedValue* prevStackTop = nullptr;
|
|
ActRec* prevFp = g_vmContext->getPrevVMState(fp, &prevPc, &prevStackTop);
|
|
if (prevFp != nullptr) {
|
|
toStringFrame(os, prevFp, prevPc, prevStackTop, prefix);
|
|
}
|
|
}
|
|
|
|
os << prefix;
|
|
const Func* func = fp->m_func;
|
|
assert(func);
|
|
func->validate();
|
|
string funcName(func->fullName()->data());
|
|
os << "{func:" << funcName
|
|
<< ",soff:" << fp->m_soff
|
|
<< ",this:0x" << std::hex << (fp->hasThis() ? fp->getThis() : nullptr)
|
|
<< std::dec << "}";
|
|
TypedValue* tv = (TypedValue*)fp;
|
|
tv--;
|
|
|
|
if (func->numLocals() > 0) {
|
|
os << "<";
|
|
int n = func->numLocals();
|
|
for (int i = 0; i < n; i++, tv--) {
|
|
if (i > 0) {
|
|
os << " ";
|
|
}
|
|
os << toStringElm(tv);
|
|
}
|
|
os << ">";
|
|
}
|
|
|
|
assert(!func->info() || func->numIterators() == 0);
|
|
if (func->numIterators() > 0) {
|
|
os << "|";
|
|
Iter* it = &((Iter*)&tv[1])[-1];
|
|
for (int i = 0; i < func->numIterators(); i++, it--) {
|
|
if (i > 0) {
|
|
os << " ";
|
|
}
|
|
bool itRef;
|
|
if (func->checkIterScope(offset, i, itRef)) {
|
|
os << toStringIter(it, itRef);
|
|
} else {
|
|
os << "I:Undefined";
|
|
}
|
|
}
|
|
os << "|";
|
|
}
|
|
|
|
std::vector<std::string> stackElems;
|
|
visitStackElems(
|
|
fp, ftop, offset,
|
|
[&](const ActRec* ar) {
|
|
stackElems.push_back(
|
|
folly::format("{{func:{}}}", ar->m_func->fullName()->data()).str()
|
|
);
|
|
},
|
|
[&](const TypedValue* tv) {
|
|
stackElems.push_back(toStringElm(tv));
|
|
}
|
|
);
|
|
std::reverse(stackElems.begin(), stackElems.end());
|
|
os << ' ' << folly::join(' ', stackElems);
|
|
|
|
os << '\n';
|
|
}
|
|
|
|
string Stack::toString(const ActRec* fp, int offset,
|
|
const string prefix/* = "" */) const {
|
|
// The only way to figure out which stack elements are activation records is
|
|
// to follow the frame chain. However, the goal for each stack frame is to
|
|
// print stack fragments from deepest to shallowest -- a then b in the
|
|
// following example:
|
|
//
|
|
// {func:foo,soff:51}<C:8> {func:bar} C:8 C:1 {func:biz} C:0
|
|
// aaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbb
|
|
//
|
|
// Use depth-first recursion to get the output order correct.
|
|
|
|
std::ostringstream os;
|
|
os << prefix << "=== Stack at " << curUnit()->filepath()->data() << ":" <<
|
|
curUnit()->getLineNumber(curUnit()->offsetOf(vmpc())) << " func " <<
|
|
curFunc()->fullName()->data() << " ===\n";
|
|
|
|
toStringFrame(os, fp, offset, m_top, prefix);
|
|
|
|
return os.str();
|
|
}
|
|
|
|
bool Stack::wouldOverflow(int numCells) const {
|
|
// The funny approach here is to validate the translator's assembly
|
|
// technique. We've aligned and sized the stack so that the high order
|
|
// bits of valid cells are all the same. In the translator, numCells
|
|
// can be hardcoded, and m_top is wired into a register,
|
|
// so the expression requires no loads.
|
|
intptr_t truncatedTop = intptr_t(m_top) / sizeof(TypedValue);
|
|
truncatedTop &= RuntimeOption::EvalVMStackElms - 1;
|
|
intptr_t diff = truncatedTop - numCells -
|
|
sSurprisePageSize / sizeof(TypedValue);
|
|
return diff < 0;
|
|
}
|
|
|
|
TypedValue* Stack::frameStackBase(const ActRec* fp) {
|
|
const Func* func = fp->m_func;
|
|
assert(!func->isGenerator());
|
|
return (TypedValue*)((uintptr_t)fp
|
|
- (uintptr_t)(func->numLocals()) * sizeof(TypedValue)
|
|
- (uintptr_t)(func->numIterators() * sizeof(Iter)));
|
|
}
|
|
|
|
TypedValue* Stack::generatorStackBase(const ActRec* fp) {
|
|
assert(fp->m_func->isGenerator());
|
|
VMExecutionContext* context = g_vmContext;
|
|
ActRec* sfp = fp->arGetSfp();
|
|
if (sfp == fp) {
|
|
// In the reentrant case, we can consult the savedVM state. We simply
|
|
// use the top of stack of the previous VM frame (since the ActRec,
|
|
// locals, and iters for this frame do not reside on the VM stack).
|
|
return context->m_nestedVMs.back().m_savedState.sp;
|
|
}
|
|
// In the non-reentrant case, we know generators are always called from a
|
|
// function with an empty stack. So we find the caller's FP, compensate
|
|
// for its locals, and then we've found the base of the generator's stack.
|
|
return (TypedValue*)sfp - sfp->m_func->numSlotsInFrame();
|
|
}
|
|
|
|
|
|
__thread RequestArenaStorage s_requestArenaStorage;
|
|
__thread VarEnvArenaStorage s_varEnvArenaStorage;
|
|
|
|
|
|
//=============================================================================
|
|
// ExecutionContext.
|
|
|
|
using namespace HPHP;
|
|
using namespace HPHP::MethodLookup;
|
|
|
|
ActRec* VMExecutionContext::getOuterVMFrame(const ActRec* ar) {
|
|
ActRec* prevFrame = (ActRec*)ar->m_savedRbp;
|
|
if (LIKELY(((uintptr_t)prevFrame - Util::s_stackLimit) >=
|
|
Util::s_stackSize)) {
|
|
if (LIKELY(prevFrame != nullptr)) return prevFrame;
|
|
}
|
|
|
|
if (LIKELY(!m_nestedVMs.empty())) return m_nestedVMs.back().m_savedState.fp;
|
|
return nullptr;
|
|
}
|
|
|
|
TypedValue* VMExecutionContext::lookupClsCns(const NamedEntity* ne,
|
|
const StringData* cls,
|
|
const StringData* cns) {
|
|
Class* class_ = Unit::loadClass(ne, cls);
|
|
if (class_ == nullptr) {
|
|
raise_error(Strings::UNKNOWN_CLASS, cls->data());
|
|
}
|
|
TypedValue* clsCns = class_->clsCnsGet(cns);
|
|
if (clsCns == nullptr) {
|
|
raise_error("Couldn't find constant %s::%s",
|
|
cls->data(), cns->data());
|
|
}
|
|
return clsCns;
|
|
}
|
|
|
|
TypedValue* VMExecutionContext::lookupClsCns(const StringData* cls,
|
|
const StringData* cns) {
|
|
return lookupClsCns(Unit::GetNamedEntity(cls), cls, cns);
|
|
}
|
|
|
|
// Look up the method specified by methodName from the class specified by cls
|
|
// and enforce accessibility. Accessibility checks depend on the relationship
|
|
// between the class that first declared the method (baseClass) and the context
|
|
// class (ctx).
|
|
//
|
|
// If there are multiple accessible methods with the specified name declared in
|
|
// cls and ancestors of cls, the method from the most derived class will be
|
|
// returned, except if we are doing an ObjMethod call ("$obj->foo()") and there
|
|
// is an accessible private method, in which case the accessible private method
|
|
// will be returned.
|
|
//
|
|
// Accessibility rules:
|
|
//
|
|
// | baseClass/ctx relationship | public | protected | private |
|
|
// +----------------------------+--------+-----------+---------+
|
|
// | anon/unrelated | yes | no | no |
|
|
// | baseClass == ctx | yes | yes | yes |
|
|
// | baseClass derived from ctx | yes | yes | no |
|
|
// | ctx derived from baseClass | yes | yes | no |
|
|
// +----------------------------+--------+-----------+---------+
|
|
|
|
const Func* VMExecutionContext::lookupMethodCtx(const Class* cls,
|
|
const StringData* methodName,
|
|
Class* ctx,
|
|
CallType callType,
|
|
bool raise /* = false */) {
|
|
const Func* method;
|
|
if (callType == CallType::CtorMethod) {
|
|
assert(methodName == nullptr);
|
|
method = cls->getCtor();
|
|
} else {
|
|
assert(callType == CallType::ObjMethod || callType == CallType::ClsMethod);
|
|
assert(methodName != nullptr);
|
|
method = cls->lookupMethod(methodName);
|
|
while (!method) {
|
|
static StringData* sd__construct
|
|
= StringData::GetStaticString("__construct");
|
|
if (UNLIKELY(methodName == sd__construct)) {
|
|
// We were looking up __construct and failed to find it. Fall back
|
|
// to old-style constructor: same as class name.
|
|
method = cls->getCtor();
|
|
if (!Func::isSpecial(method->name())) break;
|
|
}
|
|
if (raise) {
|
|
raise_error("Call to undefined method %s::%s from %s%s",
|
|
cls->name()->data(),
|
|
methodName->data(),
|
|
ctx ? "context " : "anonymous context",
|
|
ctx ? ctx->name()->data() : "");
|
|
}
|
|
return nullptr;
|
|
}
|
|
}
|
|
assert(method);
|
|
bool accessible = true;
|
|
// If we found a protected or private method, we need to do some
|
|
// accessibility checks.
|
|
if ((method->attrs() & (AttrProtected|AttrPrivate)) &&
|
|
!g_vmContext->getDebuggerBypassCheck()) {
|
|
Class* baseClass = method->baseCls();
|
|
assert(baseClass);
|
|
// If the context class is the same as the class that first
|
|
// declared this method, then we know we have the right method
|
|
// and we can stop here.
|
|
if (ctx == baseClass) {
|
|
return method;
|
|
}
|
|
// The anonymous context cannot access protected or private methods,
|
|
// so we can fail fast here.
|
|
if (ctx == nullptr) {
|
|
if (raise) {
|
|
raise_error("Call to %s method %s::%s from anonymous context",
|
|
(method->attrs() & AttrPrivate) ? "private" : "protected",
|
|
cls->name()->data(),
|
|
method->name()->data());
|
|
}
|
|
return nullptr;
|
|
}
|
|
assert(ctx);
|
|
if (method->attrs() & AttrPrivate) {
|
|
// The context class is not the same as the class that declared
|
|
// this private method, so this private method is not accessible.
|
|
// We need to keep going because the context class may define a
|
|
// private method with this name.
|
|
accessible = false;
|
|
} else {
|
|
// If the context class is derived from the class that first
|
|
// declared this protected method, then we know this method is
|
|
// accessible and we know the context class cannot have a private
|
|
// method with the same name, so we're done.
|
|
if (ctx->classof(baseClass)) {
|
|
return method;
|
|
}
|
|
if (!baseClass->classof(ctx)) {
|
|
// The context class is not the same, an ancestor, or a descendent
|
|
// of the class that first declared this protected method, so
|
|
// this method is not accessible. Because the context class is
|
|
// not the same or an ancestor of the class which first declared
|
|
// the method, we know that the context class is not the same
|
|
// or an ancestor of cls, and therefore we don't need to check
|
|
// if the context class declares a private method with this name,
|
|
// so we can fail fast here.
|
|
if (raise) {
|
|
raise_error("Call to protected method %s::%s from context %s",
|
|
cls->name()->data(),
|
|
method->name()->data(),
|
|
ctx->name()->data());
|
|
}
|
|
return nullptr;
|
|
}
|
|
// We now know this protected method is accessible, but we need to
|
|
// keep going because the context class may define a private method
|
|
// with this name.
|
|
assert(accessible && baseClass->classof(ctx));
|
|
}
|
|
}
|
|
// If this is an ObjMethod call ("$obj->foo()") AND there is an ancestor
|
|
// of cls that declares a private method with this name AND the context
|
|
// class is an ancestor of cls, check if the context class declares a
|
|
// private method with this name.
|
|
if (method->hasPrivateAncestor() && callType == CallType::ObjMethod &&
|
|
ctx && cls->classof(ctx)) {
|
|
const Func* ctxMethod = ctx->lookupMethod(methodName);
|
|
if (ctxMethod && ctxMethod->cls() == ctx &&
|
|
(ctxMethod->attrs() & AttrPrivate)) {
|
|
// For ObjMethod calls a private method from the context class
|
|
// trumps any other method we may have found.
|
|
return ctxMethod;
|
|
}
|
|
}
|
|
if (accessible) {
|
|
return method;
|
|
}
|
|
if (raise) {
|
|
raise_error("Call to private method %s::%s from %s%s",
|
|
method->baseCls()->name()->data(),
|
|
method->name()->data(),
|
|
ctx ? "context " : "anonymous context",
|
|
ctx ? ctx->name()->data() : "");
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
LookupResult VMExecutionContext::lookupObjMethod(const Func*& f,
|
|
const Class* cls,
|
|
const StringData* methodName,
|
|
bool raise /* = false */) {
|
|
Class* ctx = arGetContextClass(getFP());
|
|
f = lookupMethodCtx(cls, methodName, ctx, CallType::ObjMethod, false);
|
|
if (!f) {
|
|
f = cls->lookupMethod(s___call.get());
|
|
if (!f) {
|
|
if (raise) {
|
|
// Throw a fatal error
|
|
lookupMethodCtx(cls, methodName, ctx, CallType::ObjMethod, true);
|
|
}
|
|
return LookupResult::MethodNotFound;
|
|
}
|
|
return LookupResult::MagicCallFound;
|
|
}
|
|
if (f->attrs() & AttrStatic && !f->isClosureBody()) {
|
|
return LookupResult::MethodFoundNoThis;
|
|
}
|
|
return LookupResult::MethodFoundWithThis;
|
|
}
|
|
|
|
LookupResult
|
|
VMExecutionContext::lookupClsMethod(const Func*& f,
|
|
const Class* cls,
|
|
const StringData* methodName,
|
|
ObjectData* obj,
|
|
ActRec* vmfp,
|
|
bool raise /* = false */) {
|
|
Class* ctx = arGetContextClass(vmfp);
|
|
f = lookupMethodCtx(cls, methodName, ctx, CallType::ClsMethod, false);
|
|
if (!f) {
|
|
if (obj && obj->instanceof(cls)) {
|
|
f = obj->getVMClass()->lookupMethod(s___call.get());
|
|
}
|
|
if (!f) {
|
|
f = cls->lookupMethod(s___callStatic.get());
|
|
if (!f) {
|
|
if (raise) {
|
|
// Throw a fatal errpr
|
|
lookupMethodCtx(cls, methodName, ctx, CallType::ClsMethod, true);
|
|
}
|
|
return LookupResult::MethodNotFound;
|
|
}
|
|
f->validate();
|
|
assert(f);
|
|
assert(f->attrs() & AttrStatic);
|
|
return LookupResult::MagicCallStaticFound;
|
|
}
|
|
assert(f);
|
|
assert(obj);
|
|
// __call cannot be static, this should be enforced by semantic
|
|
// checks defClass time or earlier
|
|
assert(!(f->attrs() & AttrStatic));
|
|
return LookupResult::MagicCallFound;
|
|
}
|
|
if (obj && !(f->attrs() & AttrStatic) && obj->instanceof(cls)) {
|
|
return LookupResult::MethodFoundWithThis;
|
|
}
|
|
return LookupResult::MethodFoundNoThis;
|
|
}
|
|
|
|
LookupResult VMExecutionContext::lookupCtorMethod(const Func*& f,
|
|
const Class* cls,
|
|
bool raise /* = false */) {
|
|
f = cls->getCtor();
|
|
if (!(f->attrs() & AttrPublic)) {
|
|
Class* ctx = arGetContextClass(getFP());
|
|
f = lookupMethodCtx(cls, nullptr, ctx, CallType::CtorMethod, raise);
|
|
if (!f) {
|
|
// If raise was true than lookupMethodCtx should have thrown,
|
|
// so we should only be able to get here if raise was false
|
|
assert(!raise);
|
|
return LookupResult::MethodNotFound;
|
|
}
|
|
}
|
|
return LookupResult::MethodFoundWithThis;
|
|
}
|
|
|
|
ObjectData* VMExecutionContext::createObject(StringData* clsName,
|
|
CArrRef params,
|
|
bool init /* = true */) {
|
|
Class* class_ = Unit::loadClass(clsName);
|
|
if (class_ == nullptr) {
|
|
throw_missing_class(clsName->data());
|
|
}
|
|
Object o;
|
|
o = newInstance(class_);
|
|
if (init) {
|
|
// call constructor
|
|
TypedValue ret;
|
|
invokeFunc(&ret, class_->getCtor(), params, o.get());
|
|
tvRefcountedDecRef(&ret);
|
|
}
|
|
|
|
ObjectData* ret = o.detach();
|
|
ret->decRefCount();
|
|
return ret;
|
|
}
|
|
|
|
ObjectData* VMExecutionContext::createObjectOnly(StringData* clsName) {
|
|
return createObject(clsName, null_array, false);
|
|
}
|
|
|
|
ActRec* VMExecutionContext::getStackFrame() {
|
|
VMRegAnchor _;
|
|
return getFP();
|
|
}
|
|
|
|
ObjectData* VMExecutionContext::getThis() {
|
|
VMRegAnchor _;
|
|
ActRec* fp = getFP();
|
|
if (fp->skipFrame()) {
|
|
fp = getPrevVMState(fp);
|
|
if (!fp) return nullptr;
|
|
}
|
|
if (fp->hasThis()) {
|
|
return fp->getThis();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Class* VMExecutionContext::getContextClass() {
|
|
VMRegAnchor _;
|
|
ActRec* ar = getFP();
|
|
assert(ar != nullptr);
|
|
if (ar->skipFrame()) {
|
|
ar = getPrevVMState(ar);
|
|
if (!ar) return nullptr;
|
|
}
|
|
return ar->m_func->cls();
|
|
}
|
|
|
|
Class* VMExecutionContext::getParentContextClass() {
|
|
if (Class* ctx = getContextClass()) {
|
|
return ctx->parent();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
CStrRef VMExecutionContext::getContainingFileName() {
|
|
VMRegAnchor _;
|
|
ActRec* ar = getFP();
|
|
if (ar == nullptr) return empty_string;
|
|
if (ar->skipFrame()) {
|
|
ar = getPrevVMState(ar);
|
|
if (ar == nullptr) return empty_string;
|
|
}
|
|
Unit* unit = ar->m_func->unit();
|
|
return unit->filepathRef();
|
|
}
|
|
|
|
int VMExecutionContext::getLine() {
|
|
VMRegAnchor _;
|
|
ActRec* ar = getFP();
|
|
Unit* unit = ar ? ar->m_func->unit() : nullptr;
|
|
Offset pc = unit ? pcOff() : 0;
|
|
if (ar == nullptr) return -1;
|
|
if (ar->skipFrame()) {
|
|
ar = getPrevVMState(ar, &pc);
|
|
}
|
|
if (ar == nullptr || (unit = ar->m_func->unit()) == nullptr) return -1;
|
|
return unit->getLineNumber(pc);
|
|
}
|
|
|
|
Array VMExecutionContext::getCallerInfo() {
|
|
VMRegAnchor _;
|
|
Array result = Array::Create();
|
|
ActRec* ar = getFP();
|
|
if (ar->skipFrame()) {
|
|
ar = getPrevVMState(ar);
|
|
}
|
|
while (ar->m_func->name()->isame(s_call_user_func.get())
|
|
|| ar->m_func->name()->isame(s_call_user_func_array.get())) {
|
|
ar = getPrevVMState(ar);
|
|
if (ar == nullptr) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
Offset pc = 0;
|
|
ar = getPrevVMState(ar, &pc);
|
|
while (ar != nullptr) {
|
|
if (!ar->m_func->name()->isame(s_call_user_func.get())
|
|
&& !ar->m_func->name()->isame(s_call_user_func_array.get())) {
|
|
Unit* unit = ar->m_func->unit();
|
|
int lineNumber;
|
|
if ((lineNumber = unit->getLineNumber(pc)) != -1) {
|
|
result.set(s_file, unit->filepath()->data(), true);
|
|
result.set(s_line, lineNumber);
|
|
return result;
|
|
}
|
|
}
|
|
ar = getPrevVMState(ar, &pc);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool VMExecutionContext::renameFunction(const StringData* oldName,
|
|
const StringData* newName) {
|
|
return m_renamedFuncs.rename(oldName, newName);
|
|
}
|
|
|
|
bool VMExecutionContext::isFunctionRenameable(const StringData* name) {
|
|
return m_renamedFuncs.isFunctionRenameable(name);
|
|
}
|
|
|
|
void VMExecutionContext::addRenameableFunctions(ArrayData* arr) {
|
|
m_renamedFuncs.addRenameableFunctions(arr);
|
|
}
|
|
|
|
VarEnv* VMExecutionContext::getVarEnv() {
|
|
VMRegAnchor _;
|
|
|
|
ActRec* fp = getFP();
|
|
if (UNLIKELY(!fp)) return NULL;
|
|
if (fp->skipFrame()) {
|
|
fp = getPrevVMState(fp);
|
|
}
|
|
if (!fp) return nullptr;
|
|
assert(!fp->hasInvName());
|
|
if (!fp->hasVarEnv()) {
|
|
fp->setVarEnv(VarEnv::createLocalOnStack(fp));
|
|
}
|
|
return fp->m_varEnv;
|
|
}
|
|
|
|
void VMExecutionContext::setVar(StringData* name, TypedValue* v, bool ref) {
|
|
VMRegAnchor _;
|
|
// setVar() should only be called after getVarEnv() has been called
|
|
// to create a varEnv
|
|
ActRec *fp = getFP();
|
|
if (!fp) return;
|
|
if (fp->skipFrame()) {
|
|
fp = getPrevVMState(fp);
|
|
}
|
|
assert(!fp->hasInvName());
|
|
assert(!fp->hasExtraArgs());
|
|
assert(fp->m_varEnv != nullptr);
|
|
if (ref) {
|
|
fp->m_varEnv->bind(name, v);
|
|
} else {
|
|
fp->m_varEnv->set(name, v);
|
|
}
|
|
}
|
|
|
|
Array VMExecutionContext::getLocalDefinedVariables(int frame) {
|
|
VMRegAnchor _;
|
|
ActRec *fp = getFP();
|
|
for (; frame > 0; --frame) {
|
|
if (!fp) break;
|
|
fp = getPrevVMState(fp);
|
|
}
|
|
if (!fp) {
|
|
return Array::Create();
|
|
}
|
|
assert(!fp->hasInvName());
|
|
if (fp->hasVarEnv()) {
|
|
return fp->m_varEnv->getDefinedVariables();
|
|
}
|
|
const Func *func = fp->m_func;
|
|
auto numLocals = func->numNamedLocals();
|
|
ArrayInit ret(numLocals);
|
|
for (Id id = 0; id < numLocals; ++id) {
|
|
TypedValue* ptv = frame_local(fp, id);
|
|
if (ptv->m_type == KindOfUninit) {
|
|
continue;
|
|
}
|
|
Variant name(func->localVarName(id));
|
|
ret.add(name, tvAsVariant(ptv));
|
|
}
|
|
return ret.toArray();
|
|
}
|
|
|
|
void VMExecutionContext::shuffleMagicArgs(ActRec* ar) {
|
|
// We need to put this where the first argument is
|
|
StringData* invName = ar->getInvName();
|
|
int nargs = ar->numArgs();
|
|
ar->setVarEnv(nullptr);
|
|
assert(!ar->hasVarEnv() && !ar->hasInvName());
|
|
// We need to make an array containing all the arguments passed by the
|
|
// caller and put it where the second argument is
|
|
ArrayData* argArray = pack_args_into_array(ar, nargs);
|
|
argArray->incRefCount();
|
|
// Remove the arguments from the stack
|
|
for (int i = 0; i < nargs; ++i) {
|
|
m_stack.popC();
|
|
}
|
|
// Move invName to where the first argument belongs, no need
|
|
// to incRef/decRef since we are transferring ownership
|
|
m_stack.pushStringNoRc(invName);
|
|
// Move argArray to where the second argument belongs. We've already
|
|
// incReffed the array above so we don't need to do it here.
|
|
m_stack.pushArrayNoRc(argArray);
|
|
|
|
ar->setNumArgs(2);
|
|
}
|
|
|
|
static inline void checkStack(Stack& stk, const Func* f) {
|
|
ThreadInfo* info = ThreadInfo::s_threadInfo.getNoCheck();
|
|
// Check whether func's maximum stack usage would overflow the stack.
|
|
// Both native and VM stack overflows are independently possible.
|
|
if (!stack_in_bounds(info) ||
|
|
stk.wouldOverflow(f->maxStackCells() + kStackCheckPadding)) {
|
|
TRACE(1, "Maximum VM stack depth exceeded.\n");
|
|
raise_error("Stack overflow");
|
|
}
|
|
}
|
|
|
|
bool VMExecutionContext::prepareFuncEntry(ActRec *ar, PC& pc) {
|
|
const Func* func = ar->m_func;
|
|
Offset firstDVInitializer = InvalidAbsoluteOffset;
|
|
bool raiseMissingArgumentWarnings = false;
|
|
int nparams = func->numParams();
|
|
if (UNLIKELY(ar->m_varEnv != nullptr)) {
|
|
/*
|
|
* m_varEnv != nullptr => we have a varEnv, extraArgs, or an invName.
|
|
*/
|
|
if (ar->hasInvName()) {
|
|
// shuffleMagicArgs deals with everything. no need for
|
|
// further argument munging
|
|
shuffleMagicArgs(ar);
|
|
} else if (ar->hasVarEnv()) {
|
|
m_fp = ar;
|
|
if (!func->isGenerator()) {
|
|
assert(func->isPseudoMain());
|
|
pushLocalsAndIterators(func);
|
|
ar->m_varEnv->attach(ar);
|
|
}
|
|
pc = func->getEntry();
|
|
// Nothing more to do; get out
|
|
return true;
|
|
} else {
|
|
assert(ar->hasExtraArgs());
|
|
assert(func->numParams() < ar->numArgs());
|
|
}
|
|
} else {
|
|
int nargs = ar->numArgs();
|
|
if (nargs != nparams) {
|
|
if (nargs < nparams) {
|
|
// Push uninitialized nulls for missing arguments. Some of them may end
|
|
// up getting default-initialized, but regardless, we need to make space
|
|
// for them on the stack.
|
|
const Func::ParamInfoVec& paramInfo = func->params();
|
|
for (int i = nargs; i < nparams; ++i) {
|
|
m_stack.pushUninit();
|
|
Offset dvInitializer = paramInfo[i].funcletOff();
|
|
if (dvInitializer == InvalidAbsoluteOffset) {
|
|
// We wait to raise warnings until after all the locals have been
|
|
// initialized. This is important because things need to be in a
|
|
// consistent state in case the user error handler throws.
|
|
raiseMissingArgumentWarnings = true;
|
|
} else if (firstDVInitializer == InvalidAbsoluteOffset) {
|
|
// This is the first unpassed arg with a default value, so
|
|
// this is where we'll need to jump to.
|
|
firstDVInitializer = dvInitializer;
|
|
}
|
|
}
|
|
} else {
|
|
if (func->attrs() & AttrMayUseVV) {
|
|
// Extra parameters must be moved off the stack.
|
|
const int numExtras = nargs - nparams;
|
|
ar->setExtraArgs(ExtraArgs::allocateCopy((TypedValue*)ar - nargs,
|
|
numExtras));
|
|
m_stack.ndiscard(numExtras);
|
|
} else {
|
|
// The function we're calling is not marked as "MayUseVV",
|
|
// so just discard the extra arguments
|
|
int numExtras = nargs - nparams;
|
|
for (int i = 0; i < numExtras; i++) {
|
|
m_stack.popTV();
|
|
}
|
|
ar->setNumArgs(nparams);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int nlocals = nparams;
|
|
if (UNLIKELY(func->isClosureBody())) {
|
|
int nuse = init_closure(ar, m_stack.top());
|
|
// init_closure doesn't move m_stack
|
|
m_stack.nalloc(nuse);
|
|
nlocals += nuse;
|
|
func = ar->m_func;
|
|
}
|
|
|
|
if (LIKELY(!func->isGenerator())) {
|
|
/*
|
|
* we only get here from callAndResume
|
|
* if we failed to get a translation for
|
|
* a generator's prologue
|
|
*/
|
|
pushLocalsAndIterators(func, nlocals);
|
|
}
|
|
|
|
m_fp = ar;
|
|
if (firstDVInitializer != InvalidAbsoluteOffset) {
|
|
pc = func->unit()->entry() + firstDVInitializer;
|
|
} else {
|
|
pc = func->getEntry();
|
|
}
|
|
// cppext functions/methods have their own logic for raising
|
|
// warnings for missing arguments, so we only need to do this work
|
|
// for non-cppext functions/methods
|
|
if (raiseMissingArgumentWarnings && !func->info()) {
|
|
// need to sync m_pc to pc for backtraces/re-entry
|
|
SYNC();
|
|
const Func::ParamInfoVec& paramInfo = func->params();
|
|
for (int i = ar->numArgs(); i < nparams; ++i) {
|
|
Offset dvInitializer = paramInfo[i].funcletOff();
|
|
if (dvInitializer == InvalidAbsoluteOffset) {
|
|
const char* name = func->name()->data();
|
|
if (nparams == 1) {
|
|
raise_warning(Strings::MISSING_ARGUMENT, name, i);
|
|
} else {
|
|
raise_warning(Strings::MISSING_ARGUMENTS, name, nparams, i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void VMExecutionContext::syncGdbState() {
|
|
if (RuntimeOption::EvalJit && !RuntimeOption::EvalJitNoGdb) {
|
|
tx()->getDebugInfo()->debugSync();
|
|
}
|
|
}
|
|
|
|
void VMExecutionContext::enterVMPrologue(ActRec* enterFnAr) {
|
|
assert(enterFnAr);
|
|
Stats::inc(Stats::VMEnter);
|
|
if (ThreadInfo::s_threadInfo->m_reqInjectionData.getJit()) {
|
|
int np = enterFnAr->m_func->numParams();
|
|
int na = enterFnAr->numArgs();
|
|
if (na > np) na = np + 1;
|
|
Transl::TCA start = enterFnAr->m_func->getPrologue(na);
|
|
tx()->enterTCAtProlog(enterFnAr, start);
|
|
} else {
|
|
if (prepareFuncEntry(enterFnAr, m_pc)) {
|
|
enterVMWork(enterFnAr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void VMExecutionContext::enterVMWork(ActRec* enterFnAr) {
|
|
Transl::TCA start = nullptr;
|
|
if (enterFnAr) {
|
|
if (!EventHook::FunctionEnter(enterFnAr, EventHook::NormalFunc)) return;
|
|
checkStack(m_stack, enterFnAr->m_func);
|
|
start = enterFnAr->m_func->getFuncBody();
|
|
}
|
|
Stats::inc(Stats::VMEnter);
|
|
if (ThreadInfo::s_threadInfo->m_reqInjectionData.getJit()) {
|
|
(void) curUnit()->offsetOf(m_pc); /* assert */
|
|
if (enterFnAr) {
|
|
assert(start);
|
|
tx()->enterTCAfterProlog(start);
|
|
} else {
|
|
SrcKey sk(curFunc(), m_pc);
|
|
tx()->enterTCAtSrcKey(sk);
|
|
}
|
|
} else {
|
|
dispatch();
|
|
}
|
|
}
|
|
|
|
void VMExecutionContext::enterVM(TypedValue* retval, ActRec* ar) {
|
|
DEBUG_ONLY int faultDepth = m_faults.size();
|
|
SCOPE_EXIT { assert(m_faults.size() == faultDepth); };
|
|
|
|
m_firstAR = ar;
|
|
ar->m_savedRip = reinterpret_cast<uintptr_t>(tx()->getCallToExit());
|
|
assert(isReturnHelper(ar->m_savedRip));
|
|
|
|
/*
|
|
* When an exception is propagating, each nesting of the VM is
|
|
* responsible for unwinding its portion of the execution stack, and
|
|
* finding user handlers if it is a catchable exception.
|
|
*
|
|
* This try/catch is where all this logic is centered. The actual
|
|
* unwinding happens under exception_handler in unwind.cpp, which
|
|
* returns a UnwindAction here to indicate what to do next.
|
|
*
|
|
* Either we'll enter the VM loop again at a user error/fault
|
|
* handler, or propagate the exception to a less-nested VM.
|
|
*/
|
|
bool first = true;
|
|
resume:
|
|
try {
|
|
if (first) {
|
|
first = false;
|
|
if (m_fp && !ar->m_varEnv) {
|
|
enterVMPrologue(ar);
|
|
} else if (prepareFuncEntry(ar, m_pc)) {
|
|
enterVMWork(ar);
|
|
}
|
|
} else {
|
|
enterVMWork(0);
|
|
}
|
|
|
|
// Everything succeeded with no exception---return to the previous
|
|
// VM nesting level.
|
|
*retval = *m_stack.topTV();
|
|
m_stack.discard();
|
|
return;
|
|
|
|
} catch (...) {
|
|
always_assert(Transl::tl_regState == Transl::VMRegState::CLEAN);
|
|
auto const action = exception_handler();
|
|
if (action == UnwindAction::ResumeVM) {
|
|
goto resume;
|
|
}
|
|
always_assert(action == UnwindAction::Propagate);
|
|
}
|
|
|
|
/*
|
|
* Here we have to propagate an exception out of this VM's nesting
|
|
* level.
|
|
*/
|
|
|
|
if (g_vmContext->m_nestedVMs.empty()) {
|
|
m_fp = nullptr;
|
|
m_pc = nullptr;
|
|
}
|
|
|
|
assert(m_faults.size() > 0);
|
|
Fault fault = m_faults.back();
|
|
m_faults.pop_back();
|
|
|
|
switch (fault.m_faultType) {
|
|
case Fault::Type::UserException:
|
|
{
|
|
Object obj = fault.m_userException;
|
|
fault.m_userException->decRefCount();
|
|
throw obj;
|
|
}
|
|
case Fault::Type::CppException:
|
|
// throwException() will take care of deleting heap-allocated
|
|
// exception object for us
|
|
fault.m_cppException->throwException();
|
|
not_reached();
|
|
}
|
|
|
|
not_reached();
|
|
}
|
|
|
|
void VMExecutionContext::reenterVM(TypedValue* retval,
|
|
ActRec* ar,
|
|
TypedValue* savedSP) {
|
|
ar->m_soff = 0;
|
|
ar->m_savedRbp = 0;
|
|
VMState savedVM = { getPC(), getFP(), m_firstAR, savedSP };
|
|
TRACE(3, "savedVM: %p %p %p %p\n", m_pc, m_fp, m_firstAR, savedSP);
|
|
pushVMState(savedVM, ar);
|
|
assert(m_nestedVMs.size() >= 1);
|
|
try {
|
|
enterVM(retval, ar);
|
|
popVMState();
|
|
} catch (...) {
|
|
popVMState();
|
|
throw;
|
|
}
|
|
TRACE(1, "Reentry: exit fp %p pc %p\n", m_fp, m_pc);
|
|
}
|
|
|
|
void VMExecutionContext::invokeFunc(TypedValue* retval,
|
|
const Func* f,
|
|
CArrRef params,
|
|
ObjectData* this_ /* = NULL */,
|
|
Class* cls /* = NULL */,
|
|
VarEnv* varEnv /* = NULL */,
|
|
StringData* invName /* = NULL */,
|
|
InvokeFlags flags /* = InvokeNormal */) {
|
|
assert(retval);
|
|
assert(f);
|
|
// If this is a regular function, this_ and cls must be NULL
|
|
assert(f->preClass() || f->isPseudoMain() || (!this_ && !cls));
|
|
// If this is a method, either this_ or cls must be non-NULL
|
|
assert(!f->preClass() || (this_ || cls));
|
|
// If this is a static method, this_ must be NULL
|
|
assert(!(f->attrs() & AttrStatic && !f->isClosureBody()) ||
|
|
(!this_));
|
|
// invName should only be non-NULL if we are calling __call or
|
|
// __callStatic
|
|
assert(!invName || f->name()->isame(s___call.get()) ||
|
|
f->name()->isame(s___callStatic.get()));
|
|
// If a variable environment is being inherited then params must be empty
|
|
assert(!varEnv || params.empty());
|
|
|
|
VMRegAnchor _;
|
|
|
|
bool isMagicCall = (invName != nullptr);
|
|
|
|
if (this_ != nullptr) {
|
|
this_->incRefCount();
|
|
}
|
|
Cell* savedSP = m_stack.top();
|
|
|
|
if (f->numParams() > kStackCheckReenterPadding - kNumActRecCells) {
|
|
checkStack(m_stack, f);
|
|
}
|
|
|
|
if (flags & InvokePseudoMain) {
|
|
assert(f->isPseudoMain() && !params.get());
|
|
Unit* toMerge = f->unit();
|
|
toMerge->merge();
|
|
if (toMerge->isMergeOnly()) {
|
|
*retval = *toMerge->getMainReturn();
|
|
return;
|
|
}
|
|
}
|
|
|
|
ActRec* ar = m_stack.allocA();
|
|
ar->m_soff = 0;
|
|
ar->m_savedRbp = 0;
|
|
ar->m_func = f;
|
|
if (this_) {
|
|
ar->setThis(this_);
|
|
} else if (cls) {
|
|
ar->setClass(cls);
|
|
} else {
|
|
ar->setThis(nullptr);
|
|
}
|
|
if (isMagicCall) {
|
|
ar->initNumArgs(2);
|
|
} else {
|
|
ar->initNumArgs(params.size());
|
|
}
|
|
ar->setVarEnv(varEnv);
|
|
|
|
#ifdef HPHP_TRACE
|
|
if (m_fp == nullptr) {
|
|
TRACE(1, "Reentry: enter %s(%p) from top-level\n",
|
|
f->name()->data(), ar);
|
|
} else {
|
|
TRACE(1, "Reentry: enter %s(pc %p ar %p) from %s(%p)\n",
|
|
f->name()->data(), m_pc, ar,
|
|
m_fp->m_func ? m_fp->m_func->name()->data() : "unknownBuiltin", m_fp);
|
|
}
|
|
#endif
|
|
|
|
ArrayData *arr = params.get();
|
|
if (isMagicCall) {
|
|
// Put the method name into the location of the first parameter. We
|
|
// are transferring ownership, so no need to incRef/decRef here.
|
|
m_stack.pushStringNoRc(invName);
|
|
// Put array of arguments into the location of the second parameter
|
|
m_stack.pushArray(arr);
|
|
} else if (arr) {
|
|
const int numParams = f->numParams();
|
|
const int numExtraArgs = arr->size() - numParams;
|
|
ExtraArgs* extraArgs = nullptr;
|
|
if (numExtraArgs > 0 && (f->attrs() & AttrMayUseVV)) {
|
|
extraArgs = ExtraArgs::allocateUninit(numExtraArgs);
|
|
ar->setExtraArgs(extraArgs);
|
|
}
|
|
int paramId = 0;
|
|
for (ssize_t i = arr->iter_begin();
|
|
i != ArrayData::invalid_index;
|
|
i = arr->iter_advance(i), ++paramId) {
|
|
TypedValue *from = arr->nvGetValueRef(i);
|
|
TypedValue *to;
|
|
if (LIKELY(paramId < numParams)) {
|
|
to = m_stack.allocTV();
|
|
} else {
|
|
if (!(f->attrs() & AttrMayUseVV)) {
|
|
// Discard extra arguments, since the function cannot
|
|
// possibly use them.
|
|
assert(extraArgs == nullptr);
|
|
ar->setNumArgs(numParams);
|
|
break;
|
|
}
|
|
assert(extraArgs != nullptr && numExtraArgs > 0);
|
|
// VarEnv expects the extra args to be in "reverse" order
|
|
// (i.e. the last extra arg has the lowest address)
|
|
to = extraArgs->getExtraArg(paramId - numParams);
|
|
}
|
|
tvDup(*from, *to);
|
|
if (LIKELY(!f->byRef(paramId))) {
|
|
if (to->m_type == KindOfRef) {
|
|
tvUnbox(to);
|
|
}
|
|
} else if (!(flags & InvokeIgnoreByRefErrors) &&
|
|
(from->m_type != KindOfRef ||
|
|
from->m_data.pref->m_count == 2)) {
|
|
raise_warning("Parameter %d to %s() expected to be "
|
|
"a reference, value given",
|
|
paramId + 1, f->fullName()->data());
|
|
if (skipCufOnInvalidParams) {
|
|
if (extraArgs) {
|
|
int n = paramId >= numParams ? paramId - numParams + 1 : 0;
|
|
ExtraArgs::deallocate(extraArgs, n);
|
|
ar->m_varEnv = nullptr;
|
|
paramId -= n;
|
|
}
|
|
while (paramId >= 0) {
|
|
m_stack.popTV();
|
|
paramId--;
|
|
}
|
|
m_stack.popAR();
|
|
tvWriteNull(retval);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_fp) {
|
|
reenterVM(retval, ar, savedSP);
|
|
} else {
|
|
assert(m_nestedVMs.size() == 0);
|
|
enterVM(retval, ar);
|
|
}
|
|
}
|
|
|
|
void VMExecutionContext::invokeFuncFew(TypedValue* retval,
|
|
const Func* f,
|
|
void* thisOrCls,
|
|
StringData* invName,
|
|
int argc, TypedValue* argv) {
|
|
assert(retval);
|
|
assert(f);
|
|
// If this is a regular function, this_ and cls must be NULL
|
|
assert(f->preClass() || !thisOrCls);
|
|
// If this is a method, either this_ or cls must be non-NULL
|
|
assert(!f->preClass() || thisOrCls);
|
|
// If this is a static method, this_ must be NULL
|
|
assert(!(f->attrs() & AttrStatic && !f->isClosureBody()) ||
|
|
!ActRec::decodeThis(thisOrCls));
|
|
// invName should only be non-NULL if we are calling __call or
|
|
// __callStatic
|
|
assert(!invName || f->name()->isame(s___call.get()) ||
|
|
f->name()->isame(s___callStatic.get()));
|
|
|
|
VMRegAnchor _;
|
|
|
|
if (ObjectData* thiz = ActRec::decodeThis(thisOrCls)) {
|
|
thiz->incRefCount();
|
|
}
|
|
Cell* savedSP = m_stack.top();
|
|
if (argc > kStackCheckReenterPadding - kNumActRecCells) {
|
|
checkStack(m_stack, f);
|
|
}
|
|
ActRec* ar = m_stack.allocA();
|
|
ar->m_soff = 0;
|
|
ar->m_savedRbp = 0;
|
|
ar->m_func = f;
|
|
ar->m_this = (ObjectData*)thisOrCls;
|
|
ar->initNumArgs(argc);
|
|
if (UNLIKELY(invName != nullptr)) {
|
|
ar->setInvName(invName);
|
|
} else {
|
|
ar->m_varEnv = nullptr;
|
|
}
|
|
|
|
#ifdef HPHP_TRACE
|
|
if (m_fp == nullptr) {
|
|
TRACE(1, "Reentry: enter %s(%p) from top-level\n",
|
|
f->name()->data(), ar);
|
|
} else {
|
|
TRACE(1, "Reentry: enter %s(pc %p ar %p) from %s(%p)\n",
|
|
f->name()->data(), m_pc, ar,
|
|
m_fp->m_func ? m_fp->m_func->name()->data() : "unknownBuiltin", m_fp);
|
|
}
|
|
#endif
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
*m_stack.allocTV() = *argv++;
|
|
}
|
|
|
|
if (m_fp) {
|
|
reenterVM(retval, ar, savedSP);
|
|
} else {
|
|
assert(m_nestedVMs.size() == 0);
|
|
enterVM(retval, ar);
|
|
}
|
|
}
|
|
|
|
void VMExecutionContext::invokeContFunc(const Func* f,
|
|
ObjectData* this_,
|
|
Cell* param /* = NULL */) {
|
|
assert(f);
|
|
assert(this_);
|
|
|
|
EagerVMRegAnchor _;
|
|
|
|
this_->incRefCount();
|
|
|
|
Cell* savedSP = m_stack.top();
|
|
|
|
// no need to check stack due to ReenterPadding
|
|
assert(kStackCheckReenterPadding - kNumActRecCells >= 1);
|
|
|
|
ActRec* ar = m_stack.allocA();
|
|
ar->m_savedRbp = 0;
|
|
ar->m_func = f;
|
|
ar->m_soff = 0;
|
|
ar->initNumArgs(param != nullptr ? 1 : 0);
|
|
ar->setThis(this_);
|
|
ar->setVarEnv(nullptr);
|
|
|
|
if (param != nullptr) {
|
|
cellDup(*param, *m_stack.allocC());
|
|
}
|
|
|
|
TypedValue retval;
|
|
reenterVM(&retval, ar, savedSP);
|
|
// Codegen for generator functions guarantees that they will return null
|
|
assert(IS_NULL_TYPE(retval.m_type));
|
|
}
|
|
|
|
void VMExecutionContext::invokeUnit(TypedValue* retval, Unit* unit) {
|
|
Func* func = unit->getMain();
|
|
invokeFunc(retval, func, null_array, nullptr, nullptr,
|
|
m_globalVarEnv, nullptr, InvokePseudoMain);
|
|
}
|
|
|
|
/*
|
|
* Given a pointer to a VM frame, returns the previous VM frame in the call
|
|
* stack. This function will also pass back by reference the previous PC (if
|
|
* prevPc is non-null) and the previous SP (if prevSp is non-null).
|
|
*
|
|
* If there is no previous VM frame, this function returns NULL and does not
|
|
* set prevPc and prevSp.
|
|
*/
|
|
ActRec* VMExecutionContext::getPrevVMState(const ActRec* fp,
|
|
Offset* prevPc /* = NULL */,
|
|
TypedValue** prevSp /* = NULL */,
|
|
bool* fromVMEntry /* = NULL */) {
|
|
if (fp == nullptr) {
|
|
return nullptr;
|
|
}
|
|
ActRec* prevFp = fp->arGetSfp();
|
|
if (prevFp != fp) {
|
|
if (prevSp) {
|
|
if (UNLIKELY(fp->m_func->isGenerator())) {
|
|
*prevSp = (TypedValue*)prevFp - prevFp->m_func->numSlotsInFrame();
|
|
} else {
|
|
*prevSp = (TypedValue*)&fp[1];
|
|
}
|
|
}
|
|
if (prevPc) *prevPc = prevFp->m_func->base() + fp->m_soff;
|
|
if (fromVMEntry) *fromVMEntry = false;
|
|
return prevFp;
|
|
}
|
|
// Linear search from end of m_nestedVMs. In practice, we're probably
|
|
// looking for something recently pushed.
|
|
int i = m_nestedVMs.size() - 1;
|
|
for (; i >= 0; --i) {
|
|
if (m_nestedVMs[i].m_entryFP == fp) break;
|
|
}
|
|
if (i == -1) return nullptr;
|
|
const VMState& vmstate = m_nestedVMs[i].m_savedState;
|
|
prevFp = vmstate.fp;
|
|
assert(prevFp);
|
|
assert(prevFp->m_func->unit());
|
|
if (prevSp) *prevSp = vmstate.sp;
|
|
if (prevPc) *prevPc = prevFp->m_func->unit()->offsetOf(vmstate.pc);
|
|
if (fromVMEntry) *fromVMEntry = true;
|
|
return prevFp;
|
|
}
|
|
|
|
Array VMExecutionContext::debugBacktrace(bool skip /* = false */,
|
|
bool withSelf /* = false */,
|
|
bool withThis /* = false */,
|
|
VMParserFrame*
|
|
parserFrame /* = NULL */,
|
|
bool ignoreArgs /* = false */,
|
|
int limit /* = 0 */) {
|
|
Array bt = Array::Create();
|
|
|
|
// If there is a parser frame, put it at the beginning of
|
|
// the backtrace
|
|
if (parserFrame) {
|
|
bt.append(
|
|
ArrayInit(2)
|
|
.set(s_file, parserFrame->filename, true)
|
|
.set(s_line, parserFrame->lineNumber, true)
|
|
.toVariant()
|
|
);
|
|
}
|
|
|
|
VMRegAnchor _;
|
|
if (!getFP()) {
|
|
// If there are no VM frames, we're done
|
|
return bt;
|
|
}
|
|
|
|
int depth = 0;
|
|
ActRec* fp = nullptr;
|
|
Offset pc = 0;
|
|
|
|
// Get the fp and pc of the top frame (possibly skipping one frame)
|
|
{
|
|
if (skip) {
|
|
fp = getPrevVMState(getFP(), &pc);
|
|
if (!fp) {
|
|
// We skipped over the only VM frame, we're done
|
|
return bt;
|
|
}
|
|
} else {
|
|
fp = getFP();
|
|
Unit *unit = getFP()->m_func->unit();
|
|
assert(unit);
|
|
pc = unit->offsetOf(m_pc);
|
|
}
|
|
|
|
// Handle the top frame
|
|
if (withSelf) {
|
|
// Builtins don't have a file and line number
|
|
if (!fp->m_func->isBuiltin()) {
|
|
Unit *unit = fp->m_func->unit();
|
|
assert(unit);
|
|
const char* filename = unit->filepath()->data();
|
|
if (fp->m_func->originalFilename()) {
|
|
filename = fp->m_func->originalFilename()->data();
|
|
}
|
|
assert(filename);
|
|
Offset off = pc;
|
|
|
|
ArrayInit frame(parserFrame ? 4 : 2);
|
|
frame.set(s_file, filename, true);
|
|
frame.set(s_line, unit->getLineNumber(off), true);
|
|
if (parserFrame) {
|
|
frame.set(s_function, s_include, true);
|
|
frame.set(s_args, Array::Create(parserFrame->filename), true);
|
|
}
|
|
bt.append(frame.toVariant());
|
|
depth++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle the subsequent VM frames
|
|
Offset prevPc = 0;
|
|
for (ActRec* prevFp = getPrevVMState(fp, &prevPc);
|
|
fp != nullptr && (limit == 0 || depth < limit);
|
|
fp = prevFp, pc = prevPc, prevFp = getPrevVMState(fp, &prevPc)) {
|
|
// do not capture frame for HPHP only functions
|
|
if (fp->m_func->isNoInjection()) {
|
|
continue;
|
|
}
|
|
|
|
ArrayInit frame(7);
|
|
|
|
auto const curUnit = fp->m_func->unit();
|
|
auto const curOp = toOp(*curUnit->at(pc));
|
|
auto const isReturning = curOp == OpRetC || curOp == OpRetV;
|
|
|
|
// Builtins and generators don't have a file and line number
|
|
if (prevFp && !prevFp->m_func->isBuiltin() && !fp->m_func->isGenerator()) {
|
|
auto const prevUnit = prevFp->m_func->unit();
|
|
auto prevFile = prevUnit->filepath();
|
|
if (prevFp->m_func->originalFilename()) {
|
|
prevFile = prevFp->m_func->originalFilename();
|
|
}
|
|
assert(prevFile);
|
|
frame.set(s_file, const_cast<StringData*>(prevFile), true);
|
|
|
|
// In the normal method case, the "saved pc" for line number printing is
|
|
// pointing at the cell conversion (Unbox/Pop) instruction, not the call
|
|
// itself. For multi-line calls, this instruction is associated with the
|
|
// subsequent line which results in an off-by-n. We're subtracting one
|
|
// in order to look up the line associated with the FCall/FCallArray
|
|
// instruction. Exception handling and the other opcodes (ex. BoxR)
|
|
// already do the right thing. The emitter associates object access with
|
|
// the subsequent expression and this would be difficult to modify.
|
|
auto const opAtPrevPc =
|
|
toOp(*reinterpret_cast<const Opcode*>(prevUnit->at(prevPc)));
|
|
Offset pcAdjust = 0;
|
|
if (opAtPrevPc == OpPopR || opAtPrevPc == OpUnboxR) {
|
|
pcAdjust = 1;
|
|
}
|
|
frame.set(s_line,
|
|
prevFp->m_func->unit()->getLineNumber(prevPc - pcAdjust),
|
|
true);
|
|
}
|
|
|
|
// check for include
|
|
String funcname = const_cast<StringData*>(fp->m_func->name());
|
|
if (fp->m_func->isGenerator()) {
|
|
// retrieve the original function name from the inner continuation
|
|
funcname = frame_continuation(fp)->t_getorigfuncname();
|
|
}
|
|
|
|
if (fp->m_func->isClosureBody()) {
|
|
static StringData* s_closure_label =
|
|
StringData::GetStaticString("{closure}");
|
|
funcname = s_closure_label;
|
|
}
|
|
|
|
// check for pseudomain
|
|
if (funcname->empty()) {
|
|
if (!prevFp) continue;
|
|
funcname = s_include;
|
|
}
|
|
|
|
frame.set(s_function, funcname, true);
|
|
|
|
if (!funcname.same(s_include)) {
|
|
// Closures have an m_this but they aren't in object context
|
|
Class* ctx = arGetContextClass(fp);
|
|
if (ctx != nullptr && !fp->m_func->isClosureBody()) {
|
|
frame.set(s_class, ctx->name()->data(), true);
|
|
if (fp->hasThis() && !isReturning) {
|
|
if (withThis) {
|
|
frame.set(s_object, Object(fp->getThis()), true);
|
|
}
|
|
frame.set(s_type, "->", true);
|
|
} else {
|
|
frame.set(s_type, "::", true);
|
|
}
|
|
}
|
|
}
|
|
|
|
Array args = Array::Create();
|
|
if (ignoreArgs) {
|
|
// do nothing
|
|
} else if (funcname.same(s_include)) {
|
|
if (depth) {
|
|
args.append(const_cast<StringData*>(curUnit->filepath()));
|
|
frame.set(s_args, args, true);
|
|
}
|
|
} else if (!RuntimeOption::EnableArgsInBacktraces || isReturning) {
|
|
// Provide an empty 'args' array to be consistent with hphpc
|
|
frame.set(s_args, args, true);
|
|
} else {
|
|
int nparams = fp->m_func->numParams();
|
|
int nargs = fp->numArgs();
|
|
/* builtin extra args are not stored in varenv */
|
|
if (nargs <= nparams) {
|
|
for (int i = 0; i < nargs; i++) {
|
|
TypedValue *arg = frame_local(fp, i);
|
|
args.append(tvAsVariant(arg));
|
|
}
|
|
} else {
|
|
int i;
|
|
for (i = 0; i < nparams; i++) {
|
|
TypedValue *arg = frame_local(fp, i);
|
|
args.append(tvAsVariant(arg));
|
|
}
|
|
for (; i < nargs; i++) {
|
|
TypedValue *arg = fp->getExtraArg(i - nparams);
|
|
args.append(tvAsVariant(arg));
|
|
}
|
|
}
|
|
frame.set(s_args, args, true);
|
|
}
|
|
|
|
bt.append(frame.toVariant());
|
|
depth++;
|
|
}
|
|
return bt;
|
|
}
|
|
|
|
MethodInfoVM::~MethodInfoVM() {
|
|
for (std::vector<const ClassInfo::ParameterInfo*>::iterator it =
|
|
parameters.begin(); it != parameters.end(); ++it) {
|
|
if ((*it)->value != nullptr) {
|
|
free((void*)(*it)->value);
|
|
}
|
|
}
|
|
}
|
|
|
|
ClassInfoVM::~ClassInfoVM() {
|
|
destroyMembers(m_methodsVec);
|
|
destroyMapValues(m_properties);
|
|
destroyMapValues(m_constants);
|
|
}
|
|
|
|
Array VMExecutionContext::getUserFunctionsInfo() {
|
|
// Return an array of all user-defined function names. This method is used to
|
|
// support get_defined_functions().
|
|
return Unit::getUserFunctions();
|
|
}
|
|
|
|
Array VMExecutionContext::getConstantsInfo() {
|
|
// Return an array of all defined constant:value pairs. This method is used
|
|
// to support get_defined_constants().
|
|
return Array::Create();
|
|
}
|
|
|
|
const ClassInfo::MethodInfo* VMExecutionContext::findFunctionInfo(
|
|
CStrRef name) {
|
|
StringIMap<AtomicSmartPtr<MethodInfoVM> >::iterator it =
|
|
m_functionInfos.find(name);
|
|
if (it == m_functionInfos.end()) {
|
|
Func* func = Unit::loadFunc(name.get());
|
|
if (func == nullptr || func->builtinFuncPtr()) {
|
|
return nullptr;
|
|
}
|
|
AtomicSmartPtr<MethodInfoVM> &m = m_functionInfos[name];
|
|
m = new MethodInfoVM();
|
|
func->getFuncInfo(m.get());
|
|
return m.get();
|
|
} else {
|
|
return it->second.get();
|
|
}
|
|
}
|
|
|
|
const ClassInfo* VMExecutionContext::findClassInfo(CStrRef name) {
|
|
if (name->empty()) return nullptr;
|
|
StringIMap<AtomicSmartPtr<ClassInfoVM> >::iterator it =
|
|
m_classInfos.find(name);
|
|
if (it == m_classInfos.end()) {
|
|
Class* cls = Unit::lookupClass(name.get());
|
|
if (cls == nullptr) return nullptr;
|
|
if (cls->clsInfo()) return cls->clsInfo();
|
|
if (cls->attrs() & (AttrInterface | AttrTrait)) {
|
|
// If the specified name matches with something that is not formally
|
|
// a class, return NULL
|
|
return nullptr;
|
|
}
|
|
AtomicSmartPtr<ClassInfoVM> &c = m_classInfos[name];
|
|
c = new ClassInfoVM();
|
|
cls->getClassInfo(c.get());
|
|
return c.get();
|
|
} else {
|
|
return it->second.get();
|
|
}
|
|
}
|
|
|
|
const ClassInfo* VMExecutionContext::findInterfaceInfo(CStrRef name) {
|
|
StringIMap<AtomicSmartPtr<ClassInfoVM> >::iterator it =
|
|
m_interfaceInfos.find(name);
|
|
if (it == m_interfaceInfos.end()) {
|
|
Class* cls = Unit::lookupClass(name.get());
|
|
if (cls == nullptr) return nullptr;
|
|
if (cls->clsInfo()) return cls->clsInfo();
|
|
if (!(cls->attrs() & AttrInterface)) {
|
|
// If the specified name matches with something that is not formally
|
|
// an interface, return NULL
|
|
return nullptr;
|
|
}
|
|
AtomicSmartPtr<ClassInfoVM> &c = m_interfaceInfos[name];
|
|
c = new ClassInfoVM();
|
|
cls->getClassInfo(c.get());
|
|
return c.get();
|
|
} else {
|
|
return it->second.get();
|
|
}
|
|
}
|
|
|
|
const ClassInfo* VMExecutionContext::findTraitInfo(CStrRef name) {
|
|
StringIMap<AtomicSmartPtr<ClassInfoVM> >::iterator it =
|
|
m_traitInfos.find(name);
|
|
if (it != m_traitInfos.end()) {
|
|
return it->second.get();
|
|
}
|
|
Class* cls = Unit::lookupClass(name.get());
|
|
if (cls == nullptr) return nullptr;
|
|
if (cls->clsInfo()) return cls->clsInfo();
|
|
if (!(cls->attrs() & AttrTrait)) {
|
|
return nullptr;
|
|
}
|
|
AtomicSmartPtr<ClassInfoVM> &classInfo = m_traitInfos[name];
|
|
classInfo = new ClassInfoVM();
|
|
cls->getClassInfo(classInfo.get());
|
|
return classInfo.get();
|
|
}
|
|
|
|
const ClassInfo::ConstantInfo* VMExecutionContext::findConstantInfo(
|
|
CStrRef name) {
|
|
TypedValue* tv = Unit::lookupCns(name.get());
|
|
if (tv == nullptr) {
|
|
return nullptr;
|
|
}
|
|
ConstInfoMap::const_iterator it = m_constInfo.find(name.get());
|
|
if (it != m_constInfo.end()) {
|
|
return it->second;
|
|
}
|
|
StringData* key = StringData::GetStaticString(name.get());
|
|
ClassInfo::ConstantInfo* ci = new ClassInfo::ConstantInfo();
|
|
ci->name = *(const String*)&key;
|
|
ci->valueLen = 0;
|
|
ci->valueText = "";
|
|
ci->setValue(tvAsCVarRef(tv));
|
|
m_constInfo[key] = ci;
|
|
return ci;
|
|
}
|
|
|
|
HPHP::Eval::PhpFile* VMExecutionContext::lookupPhpFile(StringData* path,
|
|
const char* currentDir,
|
|
bool* initial_opt) {
|
|
bool init;
|
|
bool &initial = initial_opt ? *initial_opt : init;
|
|
initial = true;
|
|
|
|
struct stat s;
|
|
String spath = Eval::resolveVmInclude(path, currentDir, &s);
|
|
if (spath.isNull()) return nullptr;
|
|
|
|
// Check if this file has already been included.
|
|
EvaledFilesMap::const_iterator it = m_evaledFiles.find(spath.get());
|
|
HPHP::Eval::PhpFile* efile = nullptr;
|
|
if (it != m_evaledFiles.end()) {
|
|
// We found it! Return the unit.
|
|
efile = it->second;
|
|
initial = false;
|
|
return efile;
|
|
}
|
|
// We didn't find it, so try the realpath.
|
|
bool alreadyResolved =
|
|
RuntimeOption::RepoAuthoritative ||
|
|
(!RuntimeOption::CheckSymLink && (spath[0] == '/'));
|
|
bool hasRealpath = false;
|
|
String rpath;
|
|
if (!alreadyResolved) {
|
|
std::string rp = StatCache::realpath(spath.data());
|
|
if (rp.size() != 0) {
|
|
rpath = NEW(StringData)(rp.data(), rp.size(), CopyString);
|
|
if (!rpath.same(spath)) {
|
|
hasRealpath = true;
|
|
it = m_evaledFiles.find(rpath.get());
|
|
if (it != m_evaledFiles.end()) {
|
|
// We found it! Update the mapping for spath and
|
|
// return the unit.
|
|
efile = it->second;
|
|
m_evaledFiles[spath.get()] = efile;
|
|
spath.get()->incRefCount();
|
|
initial = false;
|
|
return efile;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// This file hasn't been included yet, so we need to parse the file
|
|
efile = HPHP::Eval::FileRepository::checkoutFile(
|
|
hasRealpath ? rpath.get() : spath.get(), s);
|
|
if (efile && initial_opt) {
|
|
// if initial_opt is not set, this shouldnt be recorded as a
|
|
// per request fetch of the file.
|
|
if (Transl::TargetCache::testAndSetBit(efile->getId())) {
|
|
initial = false;
|
|
}
|
|
// if parsing was successful, update the mappings for spath and
|
|
// rpath (if it exists).
|
|
m_evaledFiles[spath.get()] = efile;
|
|
spath.get()->incRefCount();
|
|
// Don't incRef efile; checkoutFile() already counted it.
|
|
if (hasRealpath) {
|
|
m_evaledFiles[rpath.get()] = efile;
|
|
rpath.get()->incRefCount();
|
|
}
|
|
DEBUGGER_ATTACHED_ONLY(phpDebuggerFileLoadHook(efile));
|
|
}
|
|
return efile;
|
|
}
|
|
|
|
Unit* VMExecutionContext::evalInclude(StringData* path,
|
|
const StringData* curUnitFilePath,
|
|
bool* initial) {
|
|
namespace fs = boost::filesystem;
|
|
HPHP::Eval::PhpFile* efile = nullptr;
|
|
if (curUnitFilePath) {
|
|
fs::path currentUnit(curUnitFilePath->data());
|
|
fs::path currentDir(currentUnit.branch_path());
|
|
efile = lookupPhpFile(path, currentDir.string().c_str(), initial);
|
|
} else {
|
|
efile = lookupPhpFile(path, "", initial);
|
|
}
|
|
if (efile) {
|
|
return efile->unit();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
HPHP::Unit* VMExecutionContext::evalIncludeRoot(
|
|
StringData* path, InclOpFlags flags, bool* initial) {
|
|
HPHP::Eval::PhpFile* efile = lookupIncludeRoot(path, flags, initial);
|
|
return efile ? efile->unit() : 0;
|
|
}
|
|
|
|
HPHP::Eval::PhpFile* VMExecutionContext::lookupIncludeRoot(StringData* path,
|
|
InclOpFlags flags,
|
|
bool* initial,
|
|
Unit* unit) {
|
|
String absPath;
|
|
if ((flags & InclOpRelative)) {
|
|
namespace fs = boost::filesystem;
|
|
if (!unit) unit = getFP()->m_func->unit();
|
|
fs::path currentUnit(unit->filepath()->data());
|
|
fs::path currentDir(currentUnit.branch_path());
|
|
absPath = currentDir.string() + '/';
|
|
TRACE(2, "lookupIncludeRoot(%s): relative -> %s\n",
|
|
path->data(),
|
|
absPath->data());
|
|
} else {
|
|
assert(flags & InclOpDocRoot);
|
|
absPath = SourceRootInfo::GetCurrentPhpRoot();
|
|
TRACE(2, "lookupIncludeRoot(%s): docRoot -> %s\n",
|
|
path->data(),
|
|
absPath->data());
|
|
}
|
|
|
|
absPath += StrNR(path);
|
|
|
|
EvaledFilesMap::const_iterator it = m_evaledFiles.find(absPath.get());
|
|
if (it != m_evaledFiles.end()) {
|
|
if (initial) *initial = false;
|
|
return it->second;
|
|
}
|
|
|
|
return lookupPhpFile(absPath.get(), "", initial);
|
|
}
|
|
|
|
/*
|
|
Instantiate hoistable classes and functions.
|
|
If there is any more work left to do, setup a
|
|
new frame ready to execute the pseudomain.
|
|
|
|
return true iff the pseudomain needs to be executed.
|
|
*/
|
|
bool VMExecutionContext::evalUnit(Unit* unit, PC& pc, int funcType) {
|
|
m_pc = pc;
|
|
unit->merge();
|
|
if (unit->isMergeOnly()) {
|
|
Stats::inc(Stats::PseudoMain_Skipped);
|
|
*m_stack.allocTV() = *unit->getMainReturn();
|
|
return false;
|
|
}
|
|
Stats::inc(Stats::PseudoMain_Executed);
|
|
|
|
|
|
ActRec* ar = m_stack.allocA();
|
|
assert((uintptr_t)&ar->m_func < (uintptr_t)&ar->m_r);
|
|
Class* cls = curClass();
|
|
if (m_fp->hasThis()) {
|
|
ObjectData *this_ = m_fp->getThis();
|
|
this_->incRefCount();
|
|
ar->setThis(this_);
|
|
} else if (m_fp->hasClass()) {
|
|
ar->setClass(m_fp->getClass());
|
|
} else {
|
|
ar->setThis(nullptr);
|
|
}
|
|
Func* func = unit->getMain(cls);
|
|
assert(!func->info());
|
|
assert(!func->isGenerator());
|
|
ar->m_func = func;
|
|
ar->initNumArgs(0);
|
|
assert(getFP());
|
|
assert(!m_fp->hasInvName());
|
|
arSetSfp(ar, m_fp);
|
|
ar->m_soff = uintptr_t(m_fp->m_func->unit()->offsetOf(pc) -
|
|
m_fp->m_func->base());
|
|
ar->m_savedRip =
|
|
reinterpret_cast<uintptr_t>(tx()->getRetFromInterpretedFrame());
|
|
assert(isReturnHelper(ar->m_savedRip));
|
|
pushLocalsAndIterators(func);
|
|
if (!m_fp->hasVarEnv()) {
|
|
m_fp->setVarEnv(VarEnv::createLocalOnStack(m_fp));
|
|
}
|
|
ar->m_varEnv = m_fp->m_varEnv;
|
|
ar->m_varEnv->attach(ar);
|
|
|
|
m_fp = ar;
|
|
pc = func->getEntry();
|
|
SYNC();
|
|
bool ret = EventHook::FunctionEnter(m_fp, funcType);
|
|
pc = m_pc;
|
|
return ret;
|
|
}
|
|
|
|
StaticString
|
|
s_php_namespace("<?php namespace "),
|
|
s_curly_return(" { return "),
|
|
s_semicolon_curly("; }"),
|
|
s_php_return("<?php return "),
|
|
s_semicolon(";");
|
|
CVarRef VMExecutionContext::getEvaledArg(const StringData* val,
|
|
CStrRef namespacedName) {
|
|
CStrRef key = *(String*)&val;
|
|
|
|
if (m_evaledArgs.get()) {
|
|
CVarRef arg = m_evaledArgs.get()->get(key);
|
|
if (&arg != &null_variant) return arg;
|
|
}
|
|
|
|
String code;
|
|
int pos = namespacedName.rfind('\\');
|
|
if (pos != -1) {
|
|
auto ns = namespacedName.substr(0, pos);
|
|
code = s_php_namespace + ns + s_curly_return + key + s_semicolon_curly;
|
|
} else {
|
|
code = s_php_return + key + s_semicolon;
|
|
}
|
|
Unit* unit = compileEvalString(code.get());
|
|
assert(unit != nullptr);
|
|
Variant v;
|
|
// Default arg values are not currently allowed to depend on class context.
|
|
g_vmContext->invokeFunc((TypedValue*)&v, unit->getMain(),
|
|
null_array, nullptr, nullptr, nullptr, nullptr,
|
|
InvokePseudoMain);
|
|
Variant &lv = m_evaledArgs.lvalAt(key, AccessFlags::Key);
|
|
lv = v;
|
|
return lv;
|
|
}
|
|
|
|
/*
|
|
* Helper for function entry, including pseudo-main entry.
|
|
*/
|
|
void
|
|
VMExecutionContext::pushLocalsAndIterators(const Func* func,
|
|
int nparams /*= 0*/) {
|
|
// Push locals.
|
|
for (int i = nparams; i < func->numLocals(); i++) {
|
|
m_stack.pushUninit();
|
|
}
|
|
// Push iterators.
|
|
for (int i = 0; i < func->numIterators(); i++) {
|
|
m_stack.allocI();
|
|
}
|
|
}
|
|
|
|
void VMExecutionContext::destructObjects() {
|
|
if (UNLIKELY(RuntimeOption::EnableObjDestructCall)) {
|
|
while (!m_liveBCObjs.empty()) {
|
|
ObjectData* obj = *m_liveBCObjs.begin();
|
|
obj->destruct(); // Let the instance remove the node.
|
|
}
|
|
m_liveBCObjs.clear();
|
|
}
|
|
}
|
|
|
|
// Evaled units have a footprint in the TC and translation metadata. The
|
|
// applications we care about tend to have few, short, stereotyped evals,
|
|
// where the same code keeps getting eval'ed over and over again; so we
|
|
// keep around units for each eval'ed string, so that the TC space isn't
|
|
// wasted on each eval.
|
|
typedef RankedCHM<StringData*, HPHP::Unit*,
|
|
StringDataHashCompare,
|
|
RankEvaledUnits> EvaledUnitsMap;
|
|
static EvaledUnitsMap s_evaledUnits;
|
|
Unit* VMExecutionContext::compileEvalString(StringData* code) {
|
|
EvaledUnitsMap::accessor acc;
|
|
// Promote this to a static string; otherwise it may get swept
|
|
// across requests.
|
|
code = StringData::GetStaticString(code);
|
|
if (s_evaledUnits.insert(acc, code)) {
|
|
acc->second = compile_string(code->data(), code->size());
|
|
}
|
|
return acc->second;
|
|
}
|
|
|
|
CStrRef VMExecutionContext::createFunction(CStrRef args, CStrRef code) {
|
|
VMRegAnchor _;
|
|
// It doesn't matter if there's a user function named __lambda_func; we only
|
|
// use this name during parsing, and then change it to an impossible name
|
|
// with a NUL byte before we merge it into the request's func map. This also
|
|
// has the bonus feature that the value of __FUNCTION__ inside the created
|
|
// function will match Zend. (Note: Zend will actually fatal if there's a
|
|
// user function named __lambda_func when you call create_function. Huzzah!)
|
|
static StringData* oldName = StringData::GetStaticString("__lambda_func");
|
|
std::ostringstream codeStr;
|
|
codeStr << "<?php function " << oldName->data()
|
|
<< "(" << args.data() << ") {"
|
|
<< code.data() << "}\n";
|
|
StringData* evalCode = StringData::GetStaticString(codeStr.str());
|
|
Unit* unit = compile_string(evalCode->data(), evalCode->size());
|
|
// Move the function to a different name.
|
|
std::ostringstream newNameStr;
|
|
newNameStr << '\0' << "lambda_" << ++m_lambdaCounter;
|
|
StringData* newName = StringData::GetStaticString(newNameStr.str());
|
|
unit->renameFunc(oldName, newName);
|
|
m_createdFuncs.push_back(unit);
|
|
unit->merge();
|
|
|
|
// Technically we shouldn't have to eval the unit right now (it'll execute
|
|
// the pseudo-main, which should be empty) and could get away with just
|
|
// mergeFuncs. However, Zend does it this way, as proven by the fact that you
|
|
// can inject code into the evaled unit's pseudo-main:
|
|
//
|
|
// create_function('', '} echo "hi"; if (0) {');
|
|
//
|
|
// We have to eval now to emulate this behavior.
|
|
TypedValue retval;
|
|
invokeFunc(&retval, unit->getMain(), null_array,
|
|
nullptr, nullptr, nullptr, nullptr,
|
|
InvokePseudoMain);
|
|
|
|
// __lambda_func will be the only hoistable function.
|
|
// Any functions or closures defined in it will not be hoistable.
|
|
Func* lambda = unit->firstHoistable();
|
|
return lambda->nameRef();
|
|
}
|
|
|
|
void VMExecutionContext::evalPHPDebugger(TypedValue* retval, StringData *code,
|
|
int frame) {
|
|
assert(retval);
|
|
// The code has "<?php" prepended already
|
|
Unit* unit = compileEvalString(code);
|
|
if (unit == nullptr) {
|
|
raise_error("Syntax error");
|
|
tvWriteNull(retval);
|
|
return;
|
|
}
|
|
|
|
VarEnv *varEnv = nullptr;
|
|
ActRec *fp = getFP();
|
|
ActRec *cfpSave = nullptr;
|
|
if (fp) {
|
|
for (; frame > 0; --frame) {
|
|
ActRec* prevFp = getPrevVMState(fp);
|
|
if (!prevFp) {
|
|
// To be safe in case we failed to get prevFp. This would mean we've
|
|
// been asked to eval in a frame which is beyond the top of the stack.
|
|
// This suggests the debugger client has made an error.
|
|
break;
|
|
}
|
|
fp = prevFp;
|
|
}
|
|
if (!fp->hasVarEnv()) {
|
|
fp->setVarEnv(VarEnv::createLocalOnHeap(fp));
|
|
}
|
|
varEnv = fp->m_varEnv;
|
|
cfpSave = varEnv->getCfp();
|
|
}
|
|
ObjectData *this_ = nullptr;
|
|
// NB: the ActRec and function within the AR may have different classes. The
|
|
// class in the ActRec is the type used when invoking the function (i.e.,
|
|
// Derived in Derived::Foo()) while the class obtained from the function is
|
|
// the type that declared the function Foo, which may be Base. We need both
|
|
// the class to match any object that this function may have been invoked on,
|
|
// and we need the class from the function execution is stopped in.
|
|
Class *frameClass = nullptr;
|
|
Class *functionClass = nullptr;
|
|
if (fp) {
|
|
if (fp->hasThis()) {
|
|
this_ = fp->getThis();
|
|
} else if (fp->hasClass()) {
|
|
frameClass = fp->getClass();
|
|
}
|
|
functionClass = fp->m_func->cls();
|
|
phpDebuggerEvalHook(fp->m_func);
|
|
}
|
|
|
|
const static StaticString s_cppException("Hit an exception");
|
|
const static StaticString s_phpException("Hit a php exception");
|
|
const static StaticString s_exit("Hit exit");
|
|
const static StaticString s_fatal("Hit fatal");
|
|
try {
|
|
// Invoke the given PHP, possibly specialized to match the type of the
|
|
// current function on the stack, optionally passing a this pointer or
|
|
// class used to execute the current function.
|
|
invokeFunc(retval, unit->getMain(functionClass), null_array,
|
|
this_, frameClass, varEnv, nullptr, InvokePseudoMain);
|
|
} catch (FatalErrorException &e) {
|
|
g_vmContext->write(s_fatal);
|
|
g_vmContext->write(" : ");
|
|
g_vmContext->write(e.getMessage().c_str());
|
|
g_vmContext->write("\n");
|
|
g_vmContext->write(ExtendedLogger::StringOfStackTrace(e.getBackTrace()));
|
|
} catch (ExitException &e) {
|
|
g_vmContext->write(s_exit.data());
|
|
g_vmContext->write(" : ");
|
|
std::ostringstream os;
|
|
os << ExitException::ExitCode;
|
|
g_vmContext->write(os.str());
|
|
} catch (Eval::DebuggerException &e) {
|
|
if (varEnv) {
|
|
varEnv->setCfp(cfpSave);
|
|
}
|
|
throw;
|
|
} catch (Exception &e) {
|
|
g_vmContext->write(s_cppException.data());
|
|
g_vmContext->write(" : ");
|
|
g_vmContext->write(e.getMessage().c_str());
|
|
ExtendedException* ee = dynamic_cast<ExtendedException*>(&e);
|
|
if (ee) {
|
|
g_vmContext->write("\n");
|
|
g_vmContext->write(
|
|
ExtendedLogger::StringOfStackTrace(ee->getBackTrace()));
|
|
}
|
|
} catch (Object &e) {
|
|
g_vmContext->write(s_phpException.data());
|
|
g_vmContext->write(" : ");
|
|
g_vmContext->write(e->t___tostring().data());
|
|
} catch (...) {
|
|
g_vmContext->write(s_cppException.data());
|
|
}
|
|
|
|
if (varEnv) {
|
|
// The debugger eval frame may have attached to the VarEnv from a
|
|
// frame that was not the top frame, so we need to manually set
|
|
// cfp back to what it was before
|
|
varEnv->setCfp(cfpSave);
|
|
}
|
|
}
|
|
|
|
void VMExecutionContext::enterDebuggerDummyEnv() {
|
|
static Unit* s_debuggerDummy = compile_string("<?php?>", 7);
|
|
// Ensure that the VM stack is completely empty (m_fp should be null)
|
|
// and that we're not in a nested VM (reentrancy)
|
|
assert(getFP() == nullptr);
|
|
assert(m_nestedVMs.size() == 0);
|
|
assert(m_nesting == 0);
|
|
assert(m_stack.count() == 0);
|
|
ActRec* ar = m_stack.allocA();
|
|
ar->m_func = s_debuggerDummy->getMain();
|
|
ar->setThis(nullptr);
|
|
ar->m_soff = 0;
|
|
ar->m_savedRbp = 0;
|
|
ar->m_savedRip = reinterpret_cast<uintptr_t>(tx()->getCallToExit());
|
|
assert(isReturnHelper(ar->m_savedRip));
|
|
m_fp = ar;
|
|
m_pc = s_debuggerDummy->entry();
|
|
m_firstAR = ar;
|
|
m_fp->setVarEnv(m_globalVarEnv);
|
|
m_globalVarEnv->attach(m_fp);
|
|
}
|
|
|
|
void VMExecutionContext::exitDebuggerDummyEnv() {
|
|
assert(m_globalVarEnv);
|
|
// Ensure that m_fp is valid
|
|
assert(getFP() != nullptr);
|
|
// Ensure that m_fp points to the only frame on the call stack.
|
|
// In other words, make sure there are no VM frames directly below
|
|
// this one and that we are not in a nested VM (reentrancy)
|
|
assert(m_fp->arGetSfp() == m_fp);
|
|
assert(m_nestedVMs.size() == 0);
|
|
assert(m_nesting == 0);
|
|
// Teardown the frame we erected by enterDebuggerDummyEnv()
|
|
const Func* func = m_fp->m_func;
|
|
try {
|
|
frame_free_locals_inl_no_hook<true>(m_fp, func->numLocals());
|
|
} catch (...) {}
|
|
m_stack.ndiscard(func->numSlotsInFrame());
|
|
m_stack.discardAR();
|
|
// After tearing down this frame, the VM stack should be completely empty
|
|
assert(m_stack.count() == 0);
|
|
m_fp = nullptr;
|
|
m_pc = nullptr;
|
|
}
|
|
|
|
// Identifies the set of return helpers that we may set m_savedRip to in an
|
|
// ActRec.
|
|
bool VMExecutionContext::isReturnHelper(uintptr_t address) {
|
|
auto tcAddr = reinterpret_cast<Transl::TCA>(address);
|
|
return ((tcAddr == tx()->getRetFromInterpretedFrame()) ||
|
|
(tcAddr == tx()->getRetFromInterpretedGeneratorFrame()) ||
|
|
(tcAddr == tx()->getCallToExit()));
|
|
}
|
|
|
|
// Walk the stack and find any return address to jitted code and bash it to
|
|
// the appropriate RetFromInterpreted*Frame helper. This ensures that we don't
|
|
// return into jitted code and gives the system the proper chance to interpret
|
|
// blacklisted tracelets.
|
|
void VMExecutionContext::preventReturnsToTC() {
|
|
assert(isDebuggerAttached());
|
|
if (RuntimeOption::EvalJit) {
|
|
ActRec *ar = getFP();
|
|
while (ar) {
|
|
if (!isReturnHelper(ar->m_savedRip) &&
|
|
(tx()->isValidCodeAddress((Transl::TCA)ar->m_savedRip))) {
|
|
TRACE_RB(2, "Replace RIP in fp %p, savedRip 0x%" PRIx64 ", "
|
|
"func %s\n", ar, ar->m_savedRip,
|
|
ar->m_func->fullName()->data());
|
|
if (ar->m_func->isGenerator()) {
|
|
ar->m_savedRip =
|
|
reinterpret_cast<uintptr_t>(
|
|
tx()->getRetFromInterpretedGeneratorFrame());
|
|
} else {
|
|
ar->m_savedRip =
|
|
reinterpret_cast<uintptr_t>(tx()->getRetFromInterpretedFrame());
|
|
}
|
|
assert(isReturnHelper(ar->m_savedRip));
|
|
}
|
|
ar = getPrevVMState(ar);
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline StringData* lookup_name(TypedValue* key) {
|
|
return prepareKey(key);
|
|
}
|
|
|
|
static inline void lookup_var(ActRec* fp,
|
|
StringData*& name,
|
|
TypedValue* key,
|
|
TypedValue*& val) {
|
|
name = lookup_name(key);
|
|
const Func* func = fp->m_func;
|
|
Id id = func->lookupVarId(name);
|
|
if (id != kInvalidId) {
|
|
val = frame_local(fp, id);
|
|
} else {
|
|
assert(!fp->hasInvName());
|
|
if (fp->hasVarEnv()) {
|
|
val = fp->m_varEnv->lookup(name);
|
|
} else {
|
|
val = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void lookupd_var(ActRec* fp,
|
|
StringData*& name,
|
|
TypedValue* key,
|
|
TypedValue*& val) {
|
|
name = lookup_name(key);
|
|
const Func* func = fp->m_func;
|
|
Id id = func->lookupVarId(name);
|
|
if (id != kInvalidId) {
|
|
val = frame_local(fp, id);
|
|
} else {
|
|
assert(!fp->hasInvName());
|
|
if (!fp->hasVarEnv()) {
|
|
fp->setVarEnv(VarEnv::createLocalOnStack(fp));
|
|
}
|
|
val = fp->m_varEnv->lookup(name);
|
|
if (val == nullptr) {
|
|
TypedValue tv;
|
|
tvWriteNull(&tv);
|
|
fp->m_varEnv->set(name, &tv);
|
|
val = fp->m_varEnv->lookup(name);
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void lookup_gbl(ActRec* fp,
|
|
StringData*& name,
|
|
TypedValue* key,
|
|
TypedValue*& val) {
|
|
name = lookup_name(key);
|
|
assert(g_vmContext->m_globalVarEnv);
|
|
val = g_vmContext->m_globalVarEnv->lookup(name);
|
|
}
|
|
|
|
static inline void lookupd_gbl(ActRec* fp,
|
|
StringData*& name,
|
|
TypedValue* key,
|
|
TypedValue*& val) {
|
|
name = lookup_name(key);
|
|
assert(g_vmContext->m_globalVarEnv);
|
|
VarEnv* varEnv = g_vmContext->m_globalVarEnv;
|
|
val = varEnv->lookup(name);
|
|
if (val == nullptr) {
|
|
TypedValue tv;
|
|
tvWriteNull(&tv);
|
|
varEnv->set(name, &tv);
|
|
val = varEnv->lookup(name);
|
|
}
|
|
}
|
|
|
|
static inline void lookup_sprop(ActRec* fp,
|
|
TypedValue* clsRef,
|
|
StringData*& name,
|
|
TypedValue* key,
|
|
TypedValue*& val,
|
|
bool& visible,
|
|
bool& accessible) {
|
|
assert(clsRef->m_type == KindOfClass);
|
|
name = lookup_name(key);
|
|
Class* ctx = arGetContextClass(fp);
|
|
val = clsRef->m_data.pcls->getSProp(ctx, name, visible, accessible);
|
|
}
|
|
|
|
static inline void lookupClsRef(TypedValue* input,
|
|
TypedValue* output,
|
|
bool decRef = false) {
|
|
const Class* class_ = nullptr;
|
|
if (IS_STRING_TYPE(input->m_type)) {
|
|
class_ = Unit::loadClass(input->m_data.pstr);
|
|
if (class_ == nullptr) {
|
|
output->m_type = KindOfNull;
|
|
raise_error(Strings::UNKNOWN_CLASS, input->m_data.pstr->data());
|
|
}
|
|
} else if (input->m_type == KindOfObject) {
|
|
class_ = input->m_data.pobj->getVMClass();
|
|
} else {
|
|
output->m_type = KindOfNull;
|
|
raise_error("Cls: Expected string or object");
|
|
}
|
|
if (decRef) {
|
|
tvRefcountedDecRef(input);
|
|
}
|
|
output->m_data.pcls = const_cast<Class*>(class_);
|
|
output->m_type = KindOfClass;
|
|
}
|
|
|
|
static UNUSED int innerCount(const TypedValue* tv) {
|
|
if (IS_REFCOUNTED_TYPE(tv->m_type)) {
|
|
// We're using pref here arbitrarily; any refcounted union member works.
|
|
return tv->m_data.pref->m_count;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static inline void ratchetRefs(TypedValue*& result, TypedValue& tvRef,
|
|
TypedValue& tvRef2) {
|
|
TRACE(5, "Ratchet: result %p(k%d c%d), ref %p(k%d c%d) ref2 %p(k%d c%d)\n",
|
|
result, result->m_type, innerCount(result),
|
|
&tvRef, tvRef.m_type, innerCount(&tvRef),
|
|
&tvRef2, tvRef2.m_type, innerCount(&tvRef2));
|
|
// Due to complications associated with ArrayAccess, it is possible to acquire
|
|
// a reference as a side effect of vector operation processing. Such a
|
|
// reference must be retained until after the next iteration is complete.
|
|
// Therefore, move the reference from tvRef to tvRef2, so that the reference
|
|
// will be released one iteration later. But only do this if tvRef was used in
|
|
// this iteration, otherwise we may wipe out the last reference to something
|
|
// that we need to stay alive until the next iteration.
|
|
if (tvRef.m_type != KindOfUninit) {
|
|
if (IS_REFCOUNTED_TYPE(tvRef2.m_type)) {
|
|
tvDecRef(&tvRef2);
|
|
TRACE(5, "Ratchet: decref tvref2\n");
|
|
tvWriteUninit(&tvRef2);
|
|
}
|
|
|
|
memcpy(&tvRef2, &tvRef, sizeof(TypedValue));
|
|
tvWriteUninit(&tvRef);
|
|
// Update result to point to relocated reference. This can be done
|
|
// unconditionally here because we maintain the invariant throughout that
|
|
// either tvRef is KindOfUninit, or tvRef contains a valid object that
|
|
// result points to.
|
|
assert(result == &tvRef);
|
|
result = &tvRef2;
|
|
}
|
|
}
|
|
|
|
#define DECLARE_MEMBERHELPER_ARGS \
|
|
unsigned ndiscard; \
|
|
TypedValue* base; \
|
|
TypedValue tvScratch; \
|
|
TypedValue tvLiteral; \
|
|
Variant tvRef; \
|
|
Variant tvRef2; \
|
|
MemberCode mcode = MEL; \
|
|
TypedValue* curMember = 0;
|
|
#define DECLARE_SETHELPER_ARGS DECLARE_MEMBERHELPER_ARGS
|
|
#define DECLARE_GETHELPER_ARGS \
|
|
DECLARE_MEMBERHELPER_ARGS \
|
|
TypedValue* tvRet;
|
|
|
|
#define MEMBERHELPERPRE_ARGS \
|
|
pc, ndiscard, base, tvScratch, tvLiteral, \
|
|
*tvRef.asTypedValue(), *tvRef2.asTypedValue(), mcode, curMember
|
|
|
|
#define MEMBERHELPERPRE_OUT \
|
|
pc, ndiscard, base, tvScratch, tvLiteral, \
|
|
tvRef, tvRef2, mcode, curMember
|
|
|
|
// The following arguments are outputs:
|
|
// pc: bytecode instruction after the vector instruction
|
|
// ndiscard: number of stack elements to discard
|
|
// base: ultimate result of the vector-get
|
|
// tvScratch: temporary result storage
|
|
// tvRef: temporary result storage
|
|
// tvRef2: temporary result storage
|
|
// mcode: output MemberCode for the last member if LeaveLast
|
|
// curMember: output last member value one if LeaveLast; but undefined
|
|
// if the last mcode == MW
|
|
//
|
|
// If saveResult is true, then upon completion of getHelperPre(),
|
|
// tvScratch contains a reference to the result (a duplicate of what
|
|
// base refers to). getHelperPost<true>(...) then saves the result
|
|
// to its final location.
|
|
template <bool warn,
|
|
bool saveResult,
|
|
VMExecutionContext::VectorLeaveCode mleave>
|
|
inline void OPTBLD_INLINE VMExecutionContext::getHelperPre(
|
|
PC& pc,
|
|
unsigned& ndiscard,
|
|
TypedValue*& base,
|
|
TypedValue& tvScratch,
|
|
TypedValue& tvLiteral,
|
|
TypedValue& tvRef,
|
|
TypedValue& tvRef2,
|
|
MemberCode& mcode,
|
|
TypedValue*& curMember) {
|
|
memberHelperPre<false, warn, false, false,
|
|
false, 0, mleave, saveResult>(MEMBERHELPERPRE_OUT);
|
|
}
|
|
|
|
#define GETHELPERPOST_ARGS ndiscard, tvRet, tvScratch, tvRef, tvRef2
|
|
template <bool saveResult>
|
|
inline void OPTBLD_INLINE VMExecutionContext::getHelperPost(
|
|
unsigned ndiscard, TypedValue*& tvRet, TypedValue& tvScratch,
|
|
Variant& tvRef, Variant& tvRef2) {
|
|
// Clean up all ndiscard elements on the stack. Actually discard
|
|
// only ndiscard - 1, and overwrite the last cell with the result,
|
|
// or if ndiscard is zero we actually need to allocate a cell.
|
|
for (unsigned depth = 0; depth < ndiscard; ++depth) {
|
|
TypedValue* tv = m_stack.indTV(depth);
|
|
tvRefcountedDecRef(tv);
|
|
}
|
|
|
|
if (!ndiscard) {
|
|
tvRet = m_stack.allocTV();
|
|
} else {
|
|
m_stack.ndiscard(ndiscard - 1);
|
|
tvRet = m_stack.topTV();
|
|
}
|
|
|
|
if (saveResult) {
|
|
// If tvRef wasn't just allocated, we've already decref'd it in
|
|
// the loop above.
|
|
memcpy(tvRet, &tvScratch, sizeof(TypedValue));
|
|
}
|
|
}
|
|
|
|
#define GETHELPER_ARGS \
|
|
pc, ndiscard, tvRet, base, tvScratch, tvLiteral, \
|
|
tvRef, tvRef2, mcode, curMember
|
|
inline void OPTBLD_INLINE
|
|
VMExecutionContext::getHelper(PC& pc,
|
|
unsigned& ndiscard,
|
|
TypedValue*& tvRet,
|
|
TypedValue*& base,
|
|
TypedValue& tvScratch,
|
|
TypedValue& tvLiteral,
|
|
Variant& tvRef,
|
|
Variant& tvRef2,
|
|
MemberCode& mcode,
|
|
TypedValue*& curMember) {
|
|
getHelperPre<true, true, VectorLeaveCode::ConsumeAll>(MEMBERHELPERPRE_ARGS);
|
|
getHelperPost<true>(GETHELPERPOST_ARGS);
|
|
}
|
|
|
|
void
|
|
VMExecutionContext::getElem(TypedValue* base, TypedValue* key,
|
|
TypedValue* dest) {
|
|
assert(base->m_type != KindOfArray);
|
|
VMRegAnchor _;
|
|
tvWriteUninit(dest);
|
|
TypedValue* result = Elem<true>(*dest, *dest, base, key);
|
|
if (result != dest) {
|
|
tvDup(*result, *dest);
|
|
}
|
|
}
|
|
|
|
template <bool setMember,
|
|
bool warn,
|
|
bool define,
|
|
bool unset,
|
|
bool reffy,
|
|
unsigned mdepth, // extra args on stack for set (e.g. rhs)
|
|
VMExecutionContext::VectorLeaveCode mleave,
|
|
bool saveResult>
|
|
inline bool OPTBLD_INLINE VMExecutionContext::memberHelperPre(
|
|
PC& pc, unsigned& ndiscard, TypedValue*& base,
|
|
TypedValue& tvScratch, TypedValue& tvLiteral,
|
|
TypedValue& tvRef, TypedValue& tvRef2,
|
|
MemberCode& mcode, TypedValue*& curMember) {
|
|
// The caller must move pc to the vector immediate before calling
|
|
// {get, set}HelperPre.
|
|
const ImmVector immVec = ImmVector::createFromStream(pc);
|
|
const uint8_t* vec = immVec.vec();
|
|
assert(immVec.size() > 0);
|
|
|
|
// PC needs to be advanced before we do anything, otherwise if we
|
|
// raise a notice in the middle of this we could resume at the wrong
|
|
// instruction.
|
|
pc += immVec.size() + sizeof(int32_t) + sizeof(int32_t);
|
|
|
|
if (!setMember) {
|
|
assert(mdepth == 0);
|
|
assert(!define);
|
|
assert(!unset);
|
|
}
|
|
|
|
ndiscard = immVec.numStackValues();
|
|
int depth = mdepth + ndiscard - 1;
|
|
const LocationCode lcode = LocationCode(*vec++);
|
|
|
|
TypedValue* loc = nullptr;
|
|
TypedValue dummy;
|
|
Class* const ctx = arGetContextClass(getFP());
|
|
|
|
StringData* name;
|
|
TypedValue* fr = nullptr;
|
|
TypedValue* cref;
|
|
TypedValue* pname;
|
|
tvWriteUninit(&tvScratch);
|
|
|
|
switch (lcode) {
|
|
case LNL:
|
|
loc = frame_local_inner(m_fp, decodeVariableSizeImm(&vec));
|
|
goto lcodeName;
|
|
case LNC:
|
|
loc = m_stack.indTV(depth--);
|
|
goto lcodeName;
|
|
|
|
lcodeName:
|
|
if (define) {
|
|
lookupd_var(m_fp, name, loc, fr);
|
|
} else {
|
|
lookup_var(m_fp, name, loc, fr);
|
|
}
|
|
if (fr == nullptr) {
|
|
if (warn) {
|
|
raise_notice(Strings::UNDEFINED_VARIABLE, name->data());
|
|
}
|
|
tvWriteNull(&dummy);
|
|
loc = &dummy;
|
|
} else {
|
|
loc = fr;
|
|
}
|
|
decRefStr(name);
|
|
break;
|
|
|
|
case LGL:
|
|
loc = frame_local_inner(m_fp, decodeVariableSizeImm(&vec));
|
|
goto lcodeGlobal;
|
|
case LGC:
|
|
loc = m_stack.indTV(depth--);
|
|
goto lcodeGlobal;
|
|
|
|
lcodeGlobal:
|
|
if (define) {
|
|
lookupd_gbl(m_fp, name, loc, fr);
|
|
} else {
|
|
lookup_gbl(m_fp, name, loc, fr);
|
|
}
|
|
if (fr == nullptr) {
|
|
if (warn) {
|
|
raise_notice(Strings::UNDEFINED_VARIABLE, name->data());
|
|
}
|
|
tvWriteNull(&dummy);
|
|
loc = &dummy;
|
|
} else {
|
|
loc = fr;
|
|
}
|
|
decRefStr(name);
|
|
break;
|
|
|
|
case LSC:
|
|
cref = m_stack.indTV(mdepth);
|
|
pname = m_stack.indTV(depth--);
|
|
goto lcodeSprop;
|
|
case LSL:
|
|
cref = m_stack.indTV(mdepth);
|
|
pname = frame_local_inner(m_fp, decodeVariableSizeImm(&vec));
|
|
goto lcodeSprop;
|
|
|
|
lcodeSprop: {
|
|
bool visible, accessible;
|
|
assert(cref->m_type == KindOfClass);
|
|
const Class* class_ = cref->m_data.pcls;
|
|
StringData* name = lookup_name(pname);
|
|
loc = class_->getSProp(ctx, name, visible, accessible);
|
|
if (!(visible && accessible)) {
|
|
raise_error("Invalid static property access: %s::%s",
|
|
class_->name()->data(),
|
|
name->data());
|
|
}
|
|
decRefStr(name);
|
|
break;
|
|
}
|
|
|
|
case LL: {
|
|
int localInd = decodeVariableSizeImm(&vec);
|
|
loc = frame_local_inner(m_fp, localInd);
|
|
if (warn) {
|
|
if (loc->m_type == KindOfUninit) {
|
|
raise_notice(Strings::UNDEFINED_VARIABLE,
|
|
m_fp->m_func->localVarName(localInd)->data());
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case LC:
|
|
case LR:
|
|
loc = m_stack.indTV(depth--);
|
|
break;
|
|
case LH:
|
|
assert(m_fp->hasThis());
|
|
tvScratch.m_type = KindOfObject;
|
|
tvScratch.m_data.pobj = m_fp->getThis();
|
|
loc = &tvScratch;
|
|
break;
|
|
|
|
default: not_reached();
|
|
}
|
|
|
|
base = loc;
|
|
tvWriteUninit(&tvLiteral);
|
|
tvWriteUninit(&tvRef);
|
|
tvWriteUninit(&tvRef2);
|
|
|
|
// Iterate through the members.
|
|
while (vec < pc) {
|
|
mcode = MemberCode(*vec++);
|
|
if (memberCodeHasImm(mcode)) {
|
|
int64_t memberImm = decodeMemberCodeImm(&vec, mcode);
|
|
if (memberCodeImmIsString(mcode)) {
|
|
tvAsVariant(&tvLiteral) =
|
|
m_fp->m_func->unit()->lookupLitstrId(memberImm);
|
|
assert(!IS_REFCOUNTED_TYPE(tvLiteral.m_type));
|
|
curMember = &tvLiteral;
|
|
} else if (mcode == MEI) {
|
|
tvAsVariant(&tvLiteral) = memberImm;
|
|
curMember = &tvLiteral;
|
|
} else {
|
|
assert(memberCodeImmIsLoc(mcode));
|
|
curMember = frame_local_inner(m_fp, memberImm);
|
|
}
|
|
} else {
|
|
curMember = (setMember && mcode == MW) ? nullptr : m_stack.indTV(depth--);
|
|
}
|
|
|
|
if (mleave == VectorLeaveCode::LeaveLast) {
|
|
if (vec >= pc) {
|
|
assert(vec == pc);
|
|
break;
|
|
}
|
|
}
|
|
|
|
TypedValue* result;
|
|
switch (mcode) {
|
|
case MEL:
|
|
case MEC:
|
|
case MET:
|
|
case MEI:
|
|
if (unset) {
|
|
result = ElemU(tvScratch, tvRef, base, curMember);
|
|
} else if (define) {
|
|
result = ElemD<warn,reffy>(tvScratch, tvRef, base, curMember);
|
|
} else {
|
|
result = Elem<warn>(tvScratch, tvRef, base, curMember);
|
|
}
|
|
break;
|
|
case MPL:
|
|
case MPC:
|
|
case MPT:
|
|
result = Prop<warn, define, unset>(tvScratch, tvRef, ctx, base,
|
|
curMember);
|
|
break;
|
|
case MW:
|
|
if (setMember) {
|
|
assert(define);
|
|
result = NewElem(tvScratch, tvRef, base);
|
|
} else {
|
|
raise_error("Cannot use [] for reading");
|
|
result = nullptr;
|
|
}
|
|
break;
|
|
default:
|
|
assert(false);
|
|
result = nullptr; // Silence compiler warning.
|
|
}
|
|
assert(result != nullptr);
|
|
ratchetRefs(result, tvRef, tvRef2);
|
|
// Check whether an error occurred (i.e. no result was set).
|
|
if (setMember && result == &tvScratch && result->m_type == KindOfUninit) {
|
|
return true;
|
|
}
|
|
base = result;
|
|
}
|
|
|
|
if (mleave == VectorLeaveCode::ConsumeAll) {
|
|
assert(vec == pc);
|
|
if (debug) {
|
|
if (lcode == LSC || lcode == LSL) {
|
|
assert(depth == int(mdepth));
|
|
} else {
|
|
assert(depth == int(mdepth) - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (saveResult) {
|
|
assert(!setMember);
|
|
// If requested, save a copy of the result. If base already points to
|
|
// tvScratch, no reference counting is necessary, because (with the
|
|
// exception of the following block), tvScratch is never populated such
|
|
// that it owns a reference that must be accounted for.
|
|
if (base != &tvScratch) {
|
|
// Acquire a reference to the result via tvDup(); base points to the
|
|
// result but does not own a reference.
|
|
tvDup(*base, tvScratch);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// The following arguments are outputs: (TODO put them in struct)
|
|
// pc: bytecode instruction after the vector instruction
|
|
// ndiscard: number of stack elements to discard
|
|
// base: ultimate result of the vector-get
|
|
// tvScratch: temporary result storage
|
|
// tvRef: temporary result storage
|
|
// tvRef2: temporary result storage
|
|
// mcode: output MemberCode for the last member if LeaveLast
|
|
// curMember: output last member value one if LeaveLast; but undefined
|
|
// if the last mcode == MW
|
|
template <bool warn,
|
|
bool define,
|
|
bool unset,
|
|
bool reffy,
|
|
unsigned mdepth, // extra args on stack for set (e.g. rhs)
|
|
VMExecutionContext::VectorLeaveCode mleave>
|
|
inline bool OPTBLD_INLINE VMExecutionContext::setHelperPre(
|
|
PC& pc, unsigned& ndiscard, TypedValue*& base,
|
|
TypedValue& tvScratch, TypedValue& tvLiteral,
|
|
TypedValue& tvRef, TypedValue& tvRef2,
|
|
MemberCode& mcode, TypedValue*& curMember) {
|
|
return memberHelperPre<true, warn, define, unset,
|
|
reffy, mdepth, mleave, false>(MEMBERHELPERPRE_OUT);
|
|
}
|
|
|
|
#define SETHELPERPOST_ARGS ndiscard, tvRef, tvRef2
|
|
template <unsigned mdepth>
|
|
inline void OPTBLD_INLINE VMExecutionContext::setHelperPost(
|
|
unsigned ndiscard, Variant& tvRef, Variant& tvRef2) {
|
|
// Clean up the stack. Decref all the elements for the vector, but
|
|
// leave the first mdepth (they are not part of the vector data).
|
|
for (unsigned depth = mdepth; depth-mdepth < ndiscard; ++depth) {
|
|
TypedValue* tv = m_stack.indTV(depth);
|
|
tvRefcountedDecRef(tv);
|
|
}
|
|
|
|
// NOTE: currently the only instructions using this that have return
|
|
// values on the stack also have more inputs than the -vector, so
|
|
// mdepth > 0. They also always return the original top value of
|
|
// the stack.
|
|
if (mdepth > 0) {
|
|
assert(mdepth == 1 &&
|
|
"We don't really support mdepth > 1 in setHelperPost");
|
|
|
|
if (ndiscard > 0) {
|
|
TypedValue* retSrc = m_stack.topTV();
|
|
TypedValue* dest = m_stack.indTV(ndiscard + mdepth - 1);
|
|
assert(dest != retSrc);
|
|
memcpy(dest, retSrc, sizeof *dest);
|
|
}
|
|
}
|
|
|
|
m_stack.ndiscard(ndiscard);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopLowInvalid(PC& pc) {
|
|
fprintf(stderr, "invalid bytecode executed\n");
|
|
abort();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopNop(PC& pc) {
|
|
NEXT();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopPopC(PC& pc) {
|
|
NEXT();
|
|
m_stack.popC();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopPopV(PC& pc) {
|
|
NEXT();
|
|
m_stack.popV();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopPopR(PC& pc) {
|
|
NEXT();
|
|
if (m_stack.topTV()->m_type != KindOfRef) {
|
|
m_stack.popC();
|
|
} else {
|
|
m_stack.popV();
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopDup(PC& pc) {
|
|
NEXT();
|
|
m_stack.dup();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopBox(PC& pc) {
|
|
NEXT();
|
|
m_stack.box();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopUnbox(PC& pc) {
|
|
NEXT();
|
|
m_stack.unbox();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopBoxR(PC& pc) {
|
|
NEXT();
|
|
TypedValue* tv = m_stack.topTV();
|
|
if (tv->m_type != KindOfRef) {
|
|
tvBox(tv);
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopUnboxR(PC& pc) {
|
|
NEXT();
|
|
if (m_stack.topTV()->m_type == KindOfRef) {
|
|
m_stack.unbox();
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopNull(PC& pc) {
|
|
NEXT();
|
|
m_stack.pushNull();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopNullUninit(PC& pc) {
|
|
NEXT();
|
|
m_stack.pushNullUninit();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopTrue(PC& pc) {
|
|
NEXT();
|
|
m_stack.pushTrue();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFalse(PC& pc) {
|
|
NEXT();
|
|
m_stack.pushFalse();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFile(PC& pc) {
|
|
NEXT();
|
|
const StringData* s = m_fp->m_func->unit()->filepath();
|
|
m_stack.pushStaticString(const_cast<StringData*>(s));
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopDir(PC& pc) {
|
|
NEXT();
|
|
const StringData* s = m_fp->m_func->unit()->dirpath();
|
|
m_stack.pushStaticString(const_cast<StringData*>(s));
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopInt(PC& pc) {
|
|
NEXT();
|
|
DECODE(int64_t, i);
|
|
m_stack.pushInt(i);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopDouble(PC& pc) {
|
|
NEXT();
|
|
DECODE(double, d);
|
|
m_stack.pushDouble(d);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopString(PC& pc) {
|
|
NEXT();
|
|
DECODE_LITSTR(s);
|
|
m_stack.pushStaticString(s);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopArray(PC& pc) {
|
|
NEXT();
|
|
DECODE(Id, id);
|
|
ArrayData* a = m_fp->m_func->unit()->lookupArrayId(id);
|
|
m_stack.pushStaticArray(a);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopNewArray(PC& pc) {
|
|
NEXT();
|
|
// Clever sizing avoids extra work in HphpArray construction.
|
|
auto arr = ArrayData::Make(size_t(3U) << (HphpArray::MinLgTableSize-2));
|
|
m_stack.pushArray(arr);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopNewTuple(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(n);
|
|
// This constructor moves values, no inc/decref is necessary.
|
|
HphpArray* arr = ArrayData::Make(n, m_stack.topC());
|
|
m_stack.ndiscard(n);
|
|
m_stack.pushArray(arr);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopAddElemC(PC& pc) {
|
|
NEXT();
|
|
Cell* c1 = m_stack.topC();
|
|
Cell* c2 = m_stack.indC(1);
|
|
Cell* c3 = m_stack.indC(2);
|
|
if (c3->m_type != KindOfArray) {
|
|
raise_error("AddElemC: $3 must be an array");
|
|
}
|
|
if (c2->m_type == KindOfInt64) {
|
|
cellAsVariant(*c3).asArrRef().set(c2->m_data.num, tvAsCVarRef(c1));
|
|
} else {
|
|
cellAsVariant(*c3).asArrRef().set(tvAsCVarRef(c2), tvAsCVarRef(c1));
|
|
}
|
|
m_stack.popC();
|
|
m_stack.popC();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopAddElemV(PC& pc) {
|
|
NEXT();
|
|
Ref* r1 = m_stack.topV();
|
|
Cell* c2 = m_stack.indC(1);
|
|
Cell* c3 = m_stack.indC(2);
|
|
if (c3->m_type != KindOfArray) {
|
|
raise_error("AddElemV: $3 must be an array");
|
|
}
|
|
if (c2->m_type == KindOfInt64) {
|
|
cellAsVariant(*c3).asArrRef().set(c2->m_data.num, ref(tvAsCVarRef(r1)));
|
|
} else {
|
|
cellAsVariant(*c3).asArrRef().set(tvAsCVarRef(c2), ref(tvAsCVarRef(r1)));
|
|
}
|
|
m_stack.popV();
|
|
m_stack.popC();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopAddNewElemC(PC& pc) {
|
|
NEXT();
|
|
Cell* c1 = m_stack.topC();
|
|
Cell* c2 = m_stack.indC(1);
|
|
if (c2->m_type != KindOfArray) {
|
|
raise_error("AddNewElemC: $2 must be an array");
|
|
}
|
|
cellAsVariant(*c2).asArrRef().append(tvAsCVarRef(c1));
|
|
m_stack.popC();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopAddNewElemV(PC& pc) {
|
|
NEXT();
|
|
Ref* r1 = m_stack.topV();
|
|
Cell* c2 = m_stack.indC(1);
|
|
if (c2->m_type != KindOfArray) {
|
|
raise_error("AddNewElemV: $2 must be an array");
|
|
}
|
|
cellAsVariant(*c2).asArrRef().append(ref(tvAsCVarRef(r1)));
|
|
m_stack.popV();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopNewCol(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(cType);
|
|
DECODE_IVA(nElms);
|
|
ObjectData* obj;
|
|
switch (cType) {
|
|
case Collection::VectorType: obj = NEWOBJ(c_Vector)(); break;
|
|
case Collection::MapType: obj = NEWOBJ(c_Map)(); break;
|
|
case Collection::StableMapType: obj = NEWOBJ(c_StableMap)(); break;
|
|
case Collection::SetType: obj = NEWOBJ(c_Set)(); break;
|
|
case Collection::PairType: obj = NEWOBJ(c_Pair)(); break;
|
|
default:
|
|
obj = nullptr;
|
|
raise_error("NewCol: Invalid collection type");
|
|
break;
|
|
}
|
|
// Reserve enough room for nElms elements in advance
|
|
if (nElms) {
|
|
collectionReserve(obj, nElms);
|
|
}
|
|
m_stack.pushObject(obj);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopColAddNewElemC(PC& pc) {
|
|
NEXT();
|
|
Cell* c1 = m_stack.topC();
|
|
Cell* c2 = m_stack.indC(1);
|
|
if (c2->m_type == KindOfObject && c2->m_data.pobj->isCollection()) {
|
|
collectionAppend(c2->m_data.pobj, c1);
|
|
} else {
|
|
raise_error("ColAddNewElemC: $2 must be a collection");
|
|
}
|
|
m_stack.popC();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopColAddElemC(PC& pc) {
|
|
NEXT();
|
|
Cell* c1 = m_stack.topC();
|
|
Cell* c2 = m_stack.indC(1);
|
|
Cell* c3 = m_stack.indC(2);
|
|
if (c3->m_type == KindOfObject && c3->m_data.pobj->isCollection()) {
|
|
collectionSet(c3->m_data.pobj, c2, c1);
|
|
} else {
|
|
raise_error("ColAddElemC: $3 must be a collection");
|
|
}
|
|
m_stack.popC();
|
|
m_stack.popC();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCns(PC& pc) {
|
|
NEXT();
|
|
DECODE_LITSTR(s);
|
|
TypedValue* cns = Unit::loadCns(s);
|
|
if (cns == nullptr) {
|
|
raise_notice(Strings::UNDEFINED_CONSTANT, s->data(), s->data());
|
|
m_stack.pushStaticString(s);
|
|
return;
|
|
}
|
|
Cell* c1 = m_stack.allocC();
|
|
tvReadCell(cns, c1);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCnsE(PC& pc) {
|
|
NEXT();
|
|
DECODE_LITSTR(s);
|
|
TypedValue* cns = Unit::loadCns(s);
|
|
if (cns == nullptr) {
|
|
raise_error("Undefined constant '%s'", s->data());
|
|
}
|
|
Cell* c1 = m_stack.allocC();
|
|
tvReadCell(cns, c1);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCnsU(PC& pc) {
|
|
NEXT();
|
|
DECODE_LITSTR(name);
|
|
DECODE_LITSTR(fallback);
|
|
TypedValue* cns = Unit::loadCns(name);
|
|
if (cns == nullptr) {
|
|
cns = Unit::loadCns(fallback);
|
|
if (cns == nullptr) {
|
|
raise_notice(
|
|
Strings::UNDEFINED_CONSTANT,
|
|
fallback->data(),
|
|
fallback->data()
|
|
);
|
|
m_stack.pushStaticString(fallback);
|
|
return;
|
|
}
|
|
}
|
|
Cell* c1 = m_stack.allocC();
|
|
tvReadCell(cns, c1);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopDefCns(PC& pc) {
|
|
NEXT();
|
|
DECODE_LITSTR(s);
|
|
TypedValue* tv = m_stack.topTV();
|
|
tvAsVariant(tv) = Unit::defCns(s, tv);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopClsCns(PC& pc) {
|
|
NEXT();
|
|
DECODE_LITSTR(clsCnsName);
|
|
TypedValue* tv = m_stack.topTV();
|
|
assert(tv->m_type == KindOfClass);
|
|
Class* class_ = tv->m_data.pcls;
|
|
assert(class_ != nullptr);
|
|
TypedValue* clsCns = class_->clsCnsGet(clsCnsName);
|
|
if (clsCns == nullptr) {
|
|
raise_error("Couldn't find constant %s::%s",
|
|
class_->name()->data(), clsCnsName->data());
|
|
}
|
|
tvReadCell(clsCns, tv);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopClsCnsD(PC& pc) {
|
|
NEXT();
|
|
DECODE_LITSTR(clsCnsName);
|
|
DECODE(Id, classId);
|
|
const NamedEntityPair& classNamedEntity =
|
|
m_fp->m_func->unit()->lookupNamedEntityPairId(classId);
|
|
|
|
TypedValue* clsCns = lookupClsCns(classNamedEntity.second,
|
|
classNamedEntity.first, clsCnsName);
|
|
assert(clsCns != nullptr);
|
|
Cell* c1 = m_stack.allocC();
|
|
tvReadCell(clsCns, c1);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopConcat(PC& pc) {
|
|
NEXT();
|
|
Cell* c1 = m_stack.topC();
|
|
Cell* c2 = m_stack.indC(1);
|
|
if (IS_STRING_TYPE(c1->m_type) && IS_STRING_TYPE(c2->m_type)) {
|
|
cellAsVariant(*c2) = concat(
|
|
cellAsVariant(*c2).toString(), cellAsCVarRef(*c1).toString());
|
|
} else {
|
|
cellAsVariant(*c2) = concat(cellAsVariant(*c2).toString(),
|
|
cellAsCVarRef(*c1).toString());
|
|
}
|
|
assert(c2->m_data.pstr->getCount() > 0);
|
|
m_stack.popC();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopNot(PC& pc) {
|
|
NEXT();
|
|
Cell* c1 = m_stack.topC();
|
|
cellAsVariant(*c1) = !cellAsVariant(*c1).toBoolean();
|
|
}
|
|
|
|
template<class Op>
|
|
inline void OPTBLD_INLINE VMExecutionContext::implCellBinOp(PC& pc, Op op) {
|
|
NEXT();
|
|
auto const c1 = m_stack.topC();
|
|
auto const c2 = m_stack.indC(1);
|
|
auto const result = op(*c2, *c1);
|
|
tvRefcountedDecRefCell(c2);
|
|
*c2 = result;
|
|
m_stack.popC();
|
|
}
|
|
|
|
template<class Op>
|
|
inline void OPTBLD_INLINE VMExecutionContext::implCellBinOpBool(PC& pc, Op op) {
|
|
NEXT();
|
|
auto const c1 = m_stack.topC();
|
|
auto const c2 = m_stack.indC(1);
|
|
bool const result = op(*c2, *c1);
|
|
tvRefcountedDecRefCell(c2);
|
|
*c2 = make_tv<KindOfBoolean>(result);
|
|
m_stack.popC();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopAdd(PC& pc) {
|
|
implCellBinOp(pc, cellAdd);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopSub(PC& pc) {
|
|
implCellBinOp(pc, cellSub);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopMul(PC& pc) {
|
|
implCellBinOp(pc, cellMul);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopDiv(PC& pc) {
|
|
implCellBinOp(pc, cellDiv);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopMod(PC& pc) {
|
|
implCellBinOp(pc, cellMod);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopBitAnd(PC& pc) {
|
|
implCellBinOp(pc, cellBitAnd);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopBitOr(PC& pc) {
|
|
implCellBinOp(pc, cellBitOr);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopBitXor(PC& pc) {
|
|
implCellBinOp(pc, cellBitXor);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopXor(PC& pc) {
|
|
implCellBinOpBool(pc, [&] (Cell c1, Cell c2) -> bool {
|
|
return cellToBool(c1) ^ cellToBool(c2);
|
|
});
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopSame(PC& pc) {
|
|
implCellBinOpBool(pc, cellSame);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopNSame(PC& pc) {
|
|
implCellBinOpBool(pc, [&] (Cell c1, Cell c2) {
|
|
return !cellSame(c1, c2);
|
|
});
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopEq(PC& pc) {
|
|
implCellBinOpBool(pc, [&] (Cell c1, Cell c2) {
|
|
return cellEqual(c1, c2);
|
|
});
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopNeq(PC& pc) {
|
|
implCellBinOpBool(pc, [&] (Cell c1, Cell c2) {
|
|
return !cellEqual(c1, c2);
|
|
});
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopLt(PC& pc) {
|
|
implCellBinOpBool(pc, [&] (Cell c1, Cell c2) {
|
|
return cellLess(c1, c2);
|
|
});
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopLte(PC& pc) {
|
|
implCellBinOpBool(pc, cellLessOrEqual);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopGt(PC& pc) {
|
|
implCellBinOpBool(pc, [&] (Cell c1, Cell c2) {
|
|
return cellGreater(c1, c2);
|
|
});
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopGte(PC& pc) {
|
|
implCellBinOpBool(pc, cellGreaterOrEqual);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopShl(PC& pc) {
|
|
implCellBinOp(pc, [&] (Cell c1, Cell c2) {
|
|
return make_tv<KindOfInt64>(cellToInt(c1) << cellToInt(c2));
|
|
});
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopShr(PC& pc) {
|
|
implCellBinOp(pc, [&] (Cell c1, Cell c2) {
|
|
return make_tv<KindOfInt64>(cellToInt(c1) >> cellToInt(c2));
|
|
});
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopBitNot(PC& pc) {
|
|
NEXT();
|
|
Cell* c1 = m_stack.topC();
|
|
if (LIKELY(c1->m_type == KindOfInt64)) {
|
|
c1->m_data.num = ~c1->m_data.num;
|
|
} else if (c1->m_type == KindOfDouble) {
|
|
c1->m_type = KindOfInt64;
|
|
c1->m_data.num = ~int64_t(c1->m_data.dbl);
|
|
} else if (IS_STRING_TYPE(c1->m_type)) {
|
|
cellAsVariant(*c1) = cellAsVariant(*c1).bitNot();
|
|
} else {
|
|
raise_error("Unsupported operand type for ~");
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCastBool(PC& pc) {
|
|
NEXT();
|
|
Cell* c1 = m_stack.topC();
|
|
tvCastToBooleanInPlace(c1);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCastInt(PC& pc) {
|
|
NEXT();
|
|
Cell* c1 = m_stack.topC();
|
|
tvCastToInt64InPlace(c1);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCastDouble(PC& pc) {
|
|
NEXT();
|
|
Cell* c1 = m_stack.topC();
|
|
tvCastToDoubleInPlace(c1);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCastString(PC& pc) {
|
|
NEXT();
|
|
Cell* c1 = m_stack.topC();
|
|
tvCastToStringInPlace(c1);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCastArray(PC& pc) {
|
|
NEXT();
|
|
Cell* c1 = m_stack.topC();
|
|
tvCastToArrayInPlace(c1);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCastObject(PC& pc) {
|
|
NEXT();
|
|
Cell* c1 = m_stack.topC();
|
|
tvCastToObjectInPlace(c1);
|
|
}
|
|
|
|
inline bool OPTBLD_INLINE VMExecutionContext::cellInstanceOf(
|
|
TypedValue* tv, const NamedEntity* ne) {
|
|
assert(tv->m_type != KindOfRef);
|
|
if (tv->m_type == KindOfObject) {
|
|
Class* cls = Unit::lookupClass(ne);
|
|
if (cls) return tv->m_data.pobj->instanceof(cls);
|
|
} else if (tv->m_type == KindOfArray) {
|
|
Class* cls = Unit::lookupClass(ne);
|
|
if (cls && interface_supports_array(cls->name())) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopInstanceOf(PC& pc) {
|
|
NEXT();
|
|
Cell* c1 = m_stack.topC(); // c2 instanceof c1
|
|
Cell* c2 = m_stack.indC(1);
|
|
bool r = false;
|
|
if (IS_STRING_TYPE(c1->m_type)) {
|
|
const NamedEntity* rhs = Unit::GetNamedEntity(c1->m_data.pstr);
|
|
r = cellInstanceOf(c2, rhs);
|
|
} else if (c1->m_type == KindOfObject) {
|
|
if (c2->m_type == KindOfObject) {
|
|
ObjectData* lhs = c2->m_data.pobj;
|
|
ObjectData* rhs = c1->m_data.pobj;
|
|
r = lhs->instanceof(rhs->getVMClass());
|
|
}
|
|
} else {
|
|
raise_error("Class name must be a valid object or a string");
|
|
}
|
|
m_stack.popC();
|
|
tvRefcountedDecRefCell(c2);
|
|
c2->m_data.num = r;
|
|
c2->m_type = KindOfBoolean;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopInstanceOfD(PC& pc) {
|
|
NEXT();
|
|
DECODE(Id, id);
|
|
if (shouldProfile()) {
|
|
Class::profileInstanceOf(m_fp->m_func->unit()->lookupLitstrId(id));
|
|
}
|
|
const NamedEntity* ne = m_fp->m_func->unit()->lookupNamedEntityId(id);
|
|
Cell* c1 = m_stack.topC();
|
|
bool r = cellInstanceOf(c1, ne);
|
|
tvRefcountedDecRefCell(c1);
|
|
c1->m_data.num = r;
|
|
c1->m_type = KindOfBoolean;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopPrint(PC& pc) {
|
|
NEXT();
|
|
Cell* c1 = m_stack.topC();
|
|
echo(cellAsVariant(*c1).toString());
|
|
tvRefcountedDecRefCell(c1);
|
|
c1->m_type = KindOfInt64;
|
|
c1->m_data.num = 1;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopClone(PC& pc) {
|
|
NEXT();
|
|
TypedValue* tv = m_stack.topTV();
|
|
if (tv->m_type != KindOfObject) {
|
|
raise_error("clone called on non-object");
|
|
}
|
|
ObjectData* obj = tv->m_data.pobj;
|
|
const Class* class_ UNUSED = obj->getVMClass();
|
|
ObjectData* newobj = obj->clone();
|
|
m_stack.popTV();
|
|
m_stack.pushNull();
|
|
tv->m_type = KindOfObject;
|
|
tv->m_data.pobj = newobj;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopExit(PC& pc) {
|
|
NEXT();
|
|
int exitCode = 0;
|
|
Cell* c1 = m_stack.topC();
|
|
if (c1->m_type == KindOfInt64) {
|
|
exitCode = c1->m_data.num;
|
|
} else {
|
|
echo(cellAsVariant(*c1).toString());
|
|
}
|
|
m_stack.popC();
|
|
m_stack.pushNull();
|
|
throw ExitException(exitCode);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFatal(PC& pc) {
|
|
NEXT();
|
|
TypedValue* top = m_stack.topTV();
|
|
std::string msg;
|
|
DECODE_IVA(skipFrame);
|
|
if (IS_STRING_TYPE(top->m_type)) {
|
|
msg = top->m_data.pstr->data();
|
|
} else {
|
|
msg = "Fatal error message not a string";
|
|
}
|
|
m_stack.popTV();
|
|
if (skipFrame) {
|
|
raise_error_without_first_frame(msg);
|
|
} else {
|
|
raise_error(msg);
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::jmpSurpriseCheck(Offset offset) {
|
|
if (offset <= 0 && UNLIKELY(Transl::TargetCache::loadConditionFlags())) {
|
|
EventHook::CheckSurprise();
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopJmp(PC& pc) {
|
|
NEXT();
|
|
DECODE_JMP(Offset, offset);
|
|
jmpSurpriseCheck(offset);
|
|
|
|
pc += offset - 1;
|
|
}
|
|
|
|
template<Op op>
|
|
inline void OPTBLD_INLINE VMExecutionContext::jmpOpImpl(PC& pc) {
|
|
static_assert(op == OpJmpZ || op == OpJmpNZ,
|
|
"jmpOpImpl should only be used by JmpZ and JmpNZ");
|
|
NEXT();
|
|
DECODE_JMP(Offset, offset);
|
|
jmpSurpriseCheck(offset);
|
|
|
|
Cell* c1 = m_stack.topC();
|
|
if (c1->m_type == KindOfInt64 || c1->m_type == KindOfBoolean) {
|
|
int64_t n = c1->m_data.num;
|
|
if (op == OpJmpZ ? n == 0 : n != 0) {
|
|
pc += offset - 1;
|
|
m_stack.popX();
|
|
} else {
|
|
pc += sizeof(Offset);
|
|
m_stack.popX();
|
|
}
|
|
} else {
|
|
auto const condition = toBoolean(cellAsCVarRef(*c1));
|
|
if (op == OpJmpZ ? !condition : condition) {
|
|
pc += offset - 1;
|
|
m_stack.popC();
|
|
} else {
|
|
pc += sizeof(Offset);
|
|
m_stack.popC();
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopJmpZ(PC& pc) {
|
|
jmpOpImpl<OpJmpZ>(pc);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopJmpNZ(PC& pc) {
|
|
jmpOpImpl<OpJmpNZ>(pc);
|
|
}
|
|
|
|
#define FREE_ITER_LIST(typeList, idList, vecLen) do { \
|
|
int iterIndex; \
|
|
for (iterIndex = 0; iterIndex < 2 * veclen; iterIndex += 2) { \
|
|
Id iterType = typeList[iterIndex]; \
|
|
Id iterId = idList[iterIndex]; \
|
|
\
|
|
Iter *iter = frame_iter(m_fp, iterId); \
|
|
\
|
|
switch (iterType) { \
|
|
case KindOfIter: iter->free(); break; \
|
|
case KindOfMIter: iter->mfree(); break; \
|
|
case KindOfCIter: iter->cfree(); break; \
|
|
} \
|
|
} \
|
|
} while(0)
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIterBreak(PC& pc) {
|
|
PC savedPc = pc;
|
|
NEXT();
|
|
DECODE_ITER_LIST(iterTypeList, iterIdList, veclen);
|
|
DECODE_JMP(Offset, offset);
|
|
|
|
jmpSurpriseCheck(offset); // we do this early so iterators are still dirty if
|
|
// we have an exception
|
|
|
|
FREE_ITER_LIST(iterTypeList, iterIdList, veclen);
|
|
pc = savedPc + offset;
|
|
}
|
|
|
|
#undef FREE_ITER_LIST
|
|
|
|
enum class SwitchMatch {
|
|
NORMAL, // value was converted to an int: match normally
|
|
NONZERO, // can't be converted to an int: match first nonzero case
|
|
DEFAULT, // can't be converted to an int: match default case
|
|
};
|
|
|
|
static SwitchMatch doubleCheck(double d, int64_t& out) {
|
|
if (int64_t(d) == d) {
|
|
out = d;
|
|
return SwitchMatch::NORMAL;
|
|
} else {
|
|
return SwitchMatch::DEFAULT;
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopSwitch(PC& pc) {
|
|
PC origPC = pc;
|
|
NEXT();
|
|
DECODE(int32_t, veclen);
|
|
assert(veclen > 0);
|
|
Offset* jmptab = (Offset*)pc;
|
|
pc += veclen * sizeof(*jmptab);
|
|
DECODE(int64_t, base);
|
|
DECODE_IVA(bounded);
|
|
|
|
TypedValue* val = m_stack.topTV();
|
|
if (!bounded) {
|
|
assert(val->m_type == KindOfInt64);
|
|
// Continuation switch: no bounds checking needed
|
|
int64_t label = val->m_data.num;
|
|
m_stack.popX();
|
|
assert(label >= 0 && label < veclen);
|
|
pc = origPC + jmptab[label];
|
|
} else {
|
|
// Generic integer switch
|
|
int64_t intval;
|
|
SwitchMatch match = SwitchMatch::NORMAL;
|
|
|
|
switch (val->m_type) {
|
|
case KindOfUninit:
|
|
case KindOfNull:
|
|
intval = 0;
|
|
break;
|
|
|
|
case KindOfBoolean:
|
|
// bool(true) is equal to any non-zero int, bool(false) == 0
|
|
if (val->m_data.num) {
|
|
match = SwitchMatch::NONZERO;
|
|
} else {
|
|
intval = 0;
|
|
}
|
|
break;
|
|
|
|
case KindOfInt64:
|
|
intval = val->m_data.num;
|
|
break;
|
|
|
|
case KindOfDouble:
|
|
match = doubleCheck(val->m_data.dbl, intval);
|
|
break;
|
|
|
|
case KindOfStaticString:
|
|
case KindOfString: {
|
|
double dval = 0.0;
|
|
DataType t = val->m_data.pstr->isNumericWithVal(intval, dval, 1);
|
|
switch (t) {
|
|
case KindOfNull:
|
|
intval = 0;
|
|
break;
|
|
|
|
case KindOfDouble:
|
|
match = doubleCheck(dval, intval);
|
|
break;
|
|
|
|
case KindOfInt64:
|
|
// do nothing
|
|
break;
|
|
|
|
default:
|
|
not_reached();
|
|
}
|
|
tvRefcountedDecRef(val);
|
|
break;
|
|
}
|
|
|
|
case KindOfArray:
|
|
match = SwitchMatch::DEFAULT;
|
|
tvDecRef(val);
|
|
break;
|
|
|
|
case KindOfObject:
|
|
intval = val->m_data.pobj->o_toInt64();
|
|
tvDecRef(val);
|
|
break;
|
|
|
|
default:
|
|
not_reached();
|
|
}
|
|
m_stack.discard();
|
|
|
|
if (match != SwitchMatch::NORMAL ||
|
|
intval < base || intval >= (base + veclen - 2)) {
|
|
switch (match) {
|
|
case SwitchMatch::NORMAL:
|
|
case SwitchMatch::DEFAULT:
|
|
pc = origPC + jmptab[veclen - 1];
|
|
break;
|
|
|
|
case SwitchMatch::NONZERO:
|
|
pc = origPC + jmptab[veclen - 2];
|
|
break;
|
|
}
|
|
} else {
|
|
pc = origPC + jmptab[intval - base];
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopSSwitch(PC& pc) {
|
|
PC origPC = pc;
|
|
NEXT();
|
|
DECODE(int32_t, veclen);
|
|
assert(veclen > 1);
|
|
unsigned cases = veclen - 1; // the last vector item is the default case
|
|
StrVecItem* jmptab = (StrVecItem*)pc;
|
|
pc += veclen * sizeof(*jmptab);
|
|
|
|
Cell* val = tvToCell(m_stack.topTV());
|
|
Unit* u = m_fp->m_func->unit();
|
|
unsigned i;
|
|
for (i = 0; i < cases; ++i) {
|
|
auto& item = jmptab[i];
|
|
const StringData* str = u->lookupLitstrId(item.str);
|
|
if (cellEqual(*val, str)) {
|
|
pc = origPC + item.dest;
|
|
break;
|
|
}
|
|
}
|
|
if (i == cases) {
|
|
// default case
|
|
pc = origPC + jmptab[veclen-1].dest;
|
|
}
|
|
m_stack.popC();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopRetC(PC& pc) {
|
|
NEXT();
|
|
uint soff = m_fp->m_soff;
|
|
assert(!m_fp->m_func->isGenerator());
|
|
|
|
// Call the runtime helpers to free the local variables and iterators
|
|
frame_free_locals_inl(m_fp, m_fp->m_func->numLocals());
|
|
ActRec* sfp = m_fp->arGetSfp();
|
|
// Memcpy the the return value on top of the activation record. This works
|
|
// the same regardless of whether the return value is boxed or not.
|
|
TypedValue* retval_ptr = &m_fp->m_r;
|
|
memcpy(retval_ptr, m_stack.topTV(), sizeof(TypedValue));
|
|
// Adjust the stack
|
|
m_stack.ndiscard(m_fp->m_func->numSlotsInFrame() + 1);
|
|
|
|
if (LIKELY(sfp != m_fp)) {
|
|
// Restore caller's execution state.
|
|
m_fp = sfp;
|
|
pc = m_fp->m_func->unit()->entry() + m_fp->m_func->base() + soff;
|
|
m_stack.ret();
|
|
assert(m_stack.topTV() == retval_ptr);
|
|
} else {
|
|
// No caller; terminate.
|
|
m_stack.ret();
|
|
#ifdef HPHP_TRACE
|
|
{
|
|
std::ostringstream os;
|
|
os << toStringElm(m_stack.topTV());
|
|
ONTRACE(1,
|
|
Trace::trace("Return %s from VMExecutionContext::dispatch("
|
|
"%p)\n", os.str().c_str(), m_fp));
|
|
}
|
|
#endif
|
|
pc = 0;
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopRetV(PC& pc) {
|
|
iopRetC(pc);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopUnwind(PC& pc) {
|
|
assert(!m_faults.empty());
|
|
assert(m_faults.back().m_savedRaiseOffset != kInvalidOffset);
|
|
throw VMPrepareUnwind();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopThrow(PC& pc) {
|
|
Cell* c1 = m_stack.topC();
|
|
if (c1->m_type != KindOfObject ||
|
|
!c1->m_data.pobj->instanceof(SystemLib::s_ExceptionClass)) {
|
|
raise_error("Exceptions must be valid objects derived from the "
|
|
"Exception base class");
|
|
}
|
|
|
|
Object obj(c1->m_data.pobj);
|
|
m_stack.popC();
|
|
DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionThrownHook(obj.get()));
|
|
throw obj;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopAGetC(PC& pc) {
|
|
NEXT();
|
|
TypedValue* tv = m_stack.topTV();
|
|
lookupClsRef(tv, tv, true);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopAGetL(PC& pc) {
|
|
NEXT();
|
|
DECODE_HA(local);
|
|
TypedValue* top = m_stack.allocTV();
|
|
TypedValue* fr = frame_local_inner(m_fp, local);
|
|
lookupClsRef(fr, top);
|
|
}
|
|
|
|
static void raise_undefined_local(ActRec* fp, Id pind) {
|
|
assert(pind < fp->m_func->numNamedLocals());
|
|
raise_notice(Strings::UNDEFINED_VARIABLE,
|
|
fp->m_func->localVarName(pind)->data());
|
|
}
|
|
|
|
static inline void cgetl_inner_body(TypedValue* fr, TypedValue* to) {
|
|
assert(fr->m_type != KindOfUninit);
|
|
tvDup(*fr, *to);
|
|
if (to->m_type == KindOfRef) {
|
|
tvUnbox(to);
|
|
}
|
|
}
|
|
|
|
static inline void cgetl_body(ActRec* fp,
|
|
TypedValue* fr,
|
|
TypedValue* to,
|
|
Id pind) {
|
|
if (fr->m_type == KindOfUninit) {
|
|
// `to' is uninitialized here, so we need to tvWriteNull before
|
|
// possibly causing stack unwinding.
|
|
tvWriteNull(to);
|
|
raise_undefined_local(fp, pind);
|
|
} else {
|
|
cgetl_inner_body(fr, to);
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCGetL(PC& pc) {
|
|
NEXT();
|
|
DECODE_HA(local);
|
|
Cell* to = m_stack.allocC();
|
|
TypedValue* fr = frame_local(m_fp, local);
|
|
cgetl_body(m_fp, fr, to, local);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCGetL2(PC& pc) {
|
|
NEXT();
|
|
DECODE_HA(local);
|
|
TypedValue* oldTop = m_stack.topTV();
|
|
TypedValue* newTop = m_stack.allocTV();
|
|
memcpy(newTop, oldTop, sizeof *newTop);
|
|
Cell* to = oldTop;
|
|
TypedValue* fr = frame_local(m_fp, local);
|
|
cgetl_body(m_fp, fr, to, local);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCGetL3(PC& pc) {
|
|
NEXT();
|
|
DECODE_HA(local);
|
|
TypedValue* oldTop = m_stack.topTV();
|
|
TypedValue* oldSubTop = m_stack.indTV(1);
|
|
TypedValue* newTop = m_stack.allocTV();
|
|
memmove(newTop, oldTop, sizeof *oldTop * 2);
|
|
Cell* to = oldSubTop;
|
|
TypedValue* fr = frame_local(m_fp, local);
|
|
cgetl_body(m_fp, fr, to, local);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCGetN(PC& pc) {
|
|
NEXT();
|
|
StringData* name;
|
|
TypedValue* to = m_stack.topTV();
|
|
TypedValue* fr = nullptr;
|
|
lookup_var(m_fp, name, to, fr);
|
|
if (fr == nullptr || fr->m_type == KindOfUninit) {
|
|
raise_notice(Strings::UNDEFINED_VARIABLE, name->data());
|
|
tvRefcountedDecRefCell(to);
|
|
tvWriteNull(to);
|
|
} else {
|
|
tvRefcountedDecRefCell(to);
|
|
cgetl_inner_body(fr, to);
|
|
}
|
|
decRefStr(name); // TODO(#1146727): leaks during exceptions
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCGetG(PC& pc) {
|
|
NEXT();
|
|
StringData* name;
|
|
TypedValue* to = m_stack.topTV();
|
|
TypedValue* fr = nullptr;
|
|
lookup_gbl(m_fp, name, to, fr);
|
|
if (fr == nullptr) {
|
|
if (MoreWarnings) {
|
|
raise_notice(Strings::UNDEFINED_VARIABLE, name->data());
|
|
}
|
|
tvRefcountedDecRefCell(to);
|
|
tvWriteNull(to);
|
|
} else if (fr->m_type == KindOfUninit) {
|
|
raise_notice(Strings::UNDEFINED_VARIABLE, name->data());
|
|
tvRefcountedDecRefCell(to);
|
|
tvWriteNull(to);
|
|
} else {
|
|
tvRefcountedDecRefCell(to);
|
|
cgetl_inner_body(fr, to);
|
|
}
|
|
decRefStr(name); // TODO(#1146727): leaks during exceptions
|
|
}
|
|
|
|
#define SPROP_OP_PRELUDE \
|
|
NEXT(); \
|
|
TypedValue* clsref = m_stack.topTV(); \
|
|
TypedValue* nameCell = m_stack.indTV(1); \
|
|
TypedValue* output = nameCell; \
|
|
TypedValue* val; \
|
|
bool visible, accessible; \
|
|
lookup_sprop(m_fp, clsref, name, nameCell, val, visible, \
|
|
accessible);
|
|
|
|
#define SPROP_OP_POSTLUDE \
|
|
decRefStr(name);
|
|
|
|
#define GETS(box) do { \
|
|
SPROP_OP_PRELUDE \
|
|
if (!(visible && accessible)) { \
|
|
raise_error("Invalid static property access: %s::%s", \
|
|
clsref->m_data.pcls->name()->data(), \
|
|
name->data()); \
|
|
} \
|
|
if (box) { \
|
|
if (val->m_type != KindOfRef) { \
|
|
tvBox(val); \
|
|
} \
|
|
refDup(*val, *output); \
|
|
} else { \
|
|
tvReadCell(val, output); \
|
|
} \
|
|
m_stack.popA(); \
|
|
SPROP_OP_POSTLUDE \
|
|
} while (0)
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCGetS(PC& pc) {
|
|
StringData* name;
|
|
GETS(false);
|
|
if (shouldProfile() && name && name->isStatic()) {
|
|
recordType(TypeProfileKey(TypeProfileKey::StaticPropName, name),
|
|
m_stack.top()->m_type);
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCGetM(PC& pc) {
|
|
PC oldPC = pc;
|
|
NEXT();
|
|
DECLARE_GETHELPER_ARGS
|
|
getHelper(GETHELPER_ARGS);
|
|
if (tvRet->m_type == KindOfRef) {
|
|
tvUnbox(tvRet);
|
|
}
|
|
assert(hasImmVector(toOp(*oldPC)));
|
|
const ImmVector& immVec = ImmVector::createFromStream(oldPC + 1);
|
|
StringData* name;
|
|
MemberCode mc;
|
|
if (immVec.decodeLastMember(curUnit(), name, mc)) {
|
|
recordType(TypeProfileKey(mc, name), m_stack.top()->m_type);
|
|
}
|
|
}
|
|
|
|
static inline void vgetl_body(TypedValue* fr, TypedValue* to) {
|
|
if (fr->m_type != KindOfRef) {
|
|
tvBox(fr);
|
|
}
|
|
tvDup(*fr, *to);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopVGetL(PC& pc) {
|
|
NEXT();
|
|
DECODE_HA(local);
|
|
Ref* to = m_stack.allocV();
|
|
TypedValue* fr = frame_local(m_fp, local);
|
|
vgetl_body(fr, to);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopVGetN(PC& pc) {
|
|
NEXT();
|
|
StringData* name;
|
|
TypedValue* to = m_stack.topTV();
|
|
TypedValue* fr = nullptr;
|
|
lookupd_var(m_fp, name, to, fr);
|
|
assert(fr != nullptr);
|
|
tvRefcountedDecRefCell(to);
|
|
vgetl_body(fr, to);
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopVGetG(PC& pc) {
|
|
NEXT();
|
|
StringData* name;
|
|
TypedValue* to = m_stack.topTV();
|
|
TypedValue* fr = nullptr;
|
|
lookupd_gbl(m_fp, name, to, fr);
|
|
assert(fr != nullptr);
|
|
tvRefcountedDecRefCell(to);
|
|
vgetl_body(fr, to);
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopVGetS(PC& pc) {
|
|
StringData* name;
|
|
GETS(true);
|
|
}
|
|
#undef GETS
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopVGetM(PC& pc) {
|
|
NEXT();
|
|
DECLARE_SETHELPER_ARGS
|
|
TypedValue* tv1 = m_stack.allocTV();
|
|
tvWriteUninit(tv1);
|
|
if (!setHelperPre<false, true, false, true, 1,
|
|
VectorLeaveCode::ConsumeAll>(MEMBERHELPERPRE_ARGS)) {
|
|
if (base->m_type != KindOfRef) {
|
|
tvBox(base);
|
|
}
|
|
refDup(*base, *tv1);
|
|
} else {
|
|
tvWriteNull(tv1);
|
|
tvBox(tv1);
|
|
}
|
|
setHelperPost<1>(SETHELPERPOST_ARGS);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIssetN(PC& pc) {
|
|
NEXT();
|
|
StringData* name;
|
|
TypedValue* tv1 = m_stack.topTV();
|
|
TypedValue* tv = nullptr;
|
|
bool e;
|
|
lookup_var(m_fp, name, tv1, tv);
|
|
if (tv == nullptr) {
|
|
e = false;
|
|
} else {
|
|
e = isset(tvAsCVarRef(tv));
|
|
}
|
|
tvRefcountedDecRefCell(tv1);
|
|
tv1->m_data.num = e;
|
|
tv1->m_type = KindOfBoolean;
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIssetG(PC& pc) {
|
|
NEXT();
|
|
StringData* name;
|
|
TypedValue* tv1 = m_stack.topTV();
|
|
TypedValue* tv = nullptr;
|
|
bool e;
|
|
lookup_gbl(m_fp, name, tv1, tv);
|
|
if (tv == nullptr) {
|
|
e = false;
|
|
} else {
|
|
e = isset(tvAsCVarRef(tv));
|
|
}
|
|
tvRefcountedDecRefCell(tv1);
|
|
tv1->m_data.num = e;
|
|
tv1->m_type = KindOfBoolean;
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIssetS(PC& pc) {
|
|
StringData* name;
|
|
SPROP_OP_PRELUDE
|
|
bool e;
|
|
if (!(visible && accessible)) {
|
|
e = false;
|
|
} else {
|
|
e = isset(tvAsCVarRef(val));
|
|
}
|
|
m_stack.popA();
|
|
output->m_data.num = e;
|
|
output->m_type = KindOfBoolean;
|
|
SPROP_OP_POSTLUDE
|
|
}
|
|
|
|
template <bool isEmpty>
|
|
inline void OPTBLD_INLINE VMExecutionContext::isSetEmptyM(PC& pc) {
|
|
NEXT();
|
|
DECLARE_GETHELPER_ARGS
|
|
getHelperPre<false, false, VectorLeaveCode::LeaveLast>(MEMBERHELPERPRE_ARGS);
|
|
// Process last member specially, in order to employ the IssetElem/IssetProp
|
|
// operations.
|
|
bool isSetEmptyResult = false;
|
|
switch (mcode) {
|
|
case MEL:
|
|
case MEC:
|
|
case MET:
|
|
case MEI: {
|
|
isSetEmptyResult = IssetEmptyElem<isEmpty>(tvScratch, *tvRef.asTypedValue(),
|
|
base, curMember);
|
|
break;
|
|
}
|
|
case MPL:
|
|
case MPC:
|
|
case MPT: {
|
|
Class* ctx = arGetContextClass(m_fp);
|
|
isSetEmptyResult = IssetEmptyProp<isEmpty>(ctx, base, curMember);
|
|
break;
|
|
}
|
|
default: assert(false);
|
|
}
|
|
getHelperPost<false>(GETHELPERPOST_ARGS);
|
|
tvRet->m_data.num = isSetEmptyResult;
|
|
tvRet->m_type = KindOfBoolean;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIssetM(PC& pc) {
|
|
isSetEmptyM<false>(pc);
|
|
}
|
|
|
|
#define IOP_TYPE_CHECK_INSTR_L(checkInit, what, predicate) \
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIs ## what ## L(PC& pc) { \
|
|
NEXT(); \
|
|
DECODE_HA(local); \
|
|
TypedValue* tv = frame_local(m_fp, local); \
|
|
if (checkInit && tv->m_type == KindOfUninit) { \
|
|
raise_undefined_local(m_fp, local); \
|
|
} \
|
|
bool ret = predicate(tvAsCVarRef(tv)); \
|
|
TypedValue* topTv = m_stack.allocTV(); \
|
|
topTv->m_data.num = ret; \
|
|
topTv->m_type = KindOfBoolean; \
|
|
} \
|
|
|
|
#define IOP_TYPE_CHECK_INSTR_C(checkInit, what, predicate) \
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIs ## what ## C(PC& pc) { \
|
|
NEXT(); \
|
|
TypedValue* topTv = m_stack.topTV(); \
|
|
assert(topTv->m_type != KindOfRef); \
|
|
bool ret = predicate(tvAsCVarRef(topTv)); \
|
|
tvRefcountedDecRefCell(topTv); \
|
|
topTv->m_data.num = ret; \
|
|
topTv->m_type = KindOfBoolean; \
|
|
}
|
|
|
|
#define IOP_TYPE_CHECK_INSTR(checkInit, what, predicate) \
|
|
IOP_TYPE_CHECK_INSTR_L(checkInit, what, predicate) \
|
|
IOP_TYPE_CHECK_INSTR_C(checkInit, what, predicate) \
|
|
|
|
IOP_TYPE_CHECK_INSTR_L(false, set, isset)
|
|
IOP_TYPE_CHECK_INSTR(true, Null, is_null)
|
|
IOP_TYPE_CHECK_INSTR(true, Array, is_array)
|
|
IOP_TYPE_CHECK_INSTR(true, String, is_string)
|
|
IOP_TYPE_CHECK_INSTR(true, Object, is_object)
|
|
IOP_TYPE_CHECK_INSTR(true, Int, is_int)
|
|
IOP_TYPE_CHECK_INSTR(true, Double, is_double)
|
|
IOP_TYPE_CHECK_INSTR(true, Bool, is_bool)
|
|
#undef IOP_TYPE_CHECK_INSTR
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopEmptyL(PC& pc) {
|
|
NEXT();
|
|
DECODE_HA(local);
|
|
TypedValue* loc = frame_local(m_fp, local);
|
|
bool e = empty(tvAsCVarRef(loc));
|
|
TypedValue* tv1 = m_stack.allocTV();
|
|
tv1->m_data.num = e;
|
|
tv1->m_type = KindOfBoolean;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopEmptyN(PC& pc) {
|
|
NEXT();
|
|
StringData* name;
|
|
TypedValue* tv1 = m_stack.topTV();
|
|
TypedValue* tv = nullptr;
|
|
bool e;
|
|
lookup_var(m_fp, name, tv1, tv);
|
|
if (tv == nullptr) {
|
|
e = true;
|
|
} else {
|
|
e = empty(tvAsCVarRef(tv));
|
|
}
|
|
tvRefcountedDecRefCell(tv1);
|
|
tv1->m_data.num = e;
|
|
tv1->m_type = KindOfBoolean;
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopEmptyG(PC& pc) {
|
|
NEXT();
|
|
StringData* name;
|
|
TypedValue* tv1 = m_stack.topTV();
|
|
TypedValue* tv = nullptr;
|
|
bool e;
|
|
lookup_gbl(m_fp, name, tv1, tv);
|
|
if (tv == nullptr) {
|
|
e = true;
|
|
} else {
|
|
e = empty(tvAsCVarRef(tv));
|
|
}
|
|
tvRefcountedDecRefCell(tv1);
|
|
tv1->m_data.num = e;
|
|
tv1->m_type = KindOfBoolean;
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopEmptyS(PC& pc) {
|
|
StringData* name;
|
|
SPROP_OP_PRELUDE
|
|
bool e;
|
|
if (!(visible && accessible)) {
|
|
e = true;
|
|
} else {
|
|
e = empty(tvAsCVarRef(val));
|
|
}
|
|
m_stack.popA();
|
|
output->m_data.num = e;
|
|
output->m_type = KindOfBoolean;
|
|
SPROP_OP_POSTLUDE
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopEmptyM(PC& pc) {
|
|
isSetEmptyM<true>(pc);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopAKExists(PC& pc) {
|
|
NEXT();
|
|
TypedValue* arr = m_stack.topTV();
|
|
TypedValue* key = arr + 1;
|
|
bool result = f_array_key_exists(tvAsCVarRef(key), tvAsCVarRef(arr));
|
|
m_stack.popTV();
|
|
tvRefcountedDecRef(key);
|
|
key->m_data.num = result;
|
|
key->m_type = KindOfBoolean;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopArrayIdx(PC& pc) {
|
|
NEXT();
|
|
TypedValue* def = m_stack.topTV();
|
|
TypedValue* arr = m_stack.indTV(1);
|
|
TypedValue* key = m_stack.indTV(2);
|
|
|
|
Variant result = f_hphp_array_idx(tvAsCVarRef(key),
|
|
tvAsCVarRef(arr),
|
|
tvAsCVarRef(def));
|
|
m_stack.popTV();
|
|
m_stack.popTV();
|
|
tvAsVariant(key) = result;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopSetL(PC& pc) {
|
|
NEXT();
|
|
DECODE_HA(local);
|
|
assert(local < m_fp->m_func->numLocals());
|
|
Cell* fr = m_stack.topC();
|
|
TypedValue* to = frame_local(m_fp, local);
|
|
tvSet(*fr, *to);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopSetN(PC& pc) {
|
|
NEXT();
|
|
StringData* name;
|
|
Cell* fr = m_stack.topC();
|
|
TypedValue* tv2 = m_stack.indTV(1);
|
|
TypedValue* to = nullptr;
|
|
lookupd_var(m_fp, name, tv2, to);
|
|
assert(to != nullptr);
|
|
tvSet(*fr, *to);
|
|
memcpy((void*)tv2, (void*)fr, sizeof(TypedValue));
|
|
m_stack.discard();
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopSetG(PC& pc) {
|
|
NEXT();
|
|
StringData* name;
|
|
Cell* fr = m_stack.topC();
|
|
TypedValue* tv2 = m_stack.indTV(1);
|
|
TypedValue* to = nullptr;
|
|
lookupd_gbl(m_fp, name, tv2, to);
|
|
assert(to != nullptr);
|
|
tvSet(*fr, *to);
|
|
memcpy((void*)tv2, (void*)fr, sizeof(TypedValue));
|
|
m_stack.discard();
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopSetS(PC& pc) {
|
|
NEXT();
|
|
TypedValue* tv1 = m_stack.topTV();
|
|
TypedValue* classref = m_stack.indTV(1);
|
|
TypedValue* propn = m_stack.indTV(2);
|
|
TypedValue* output = propn;
|
|
StringData* name;
|
|
TypedValue* val;
|
|
bool visible, accessible;
|
|
lookup_sprop(m_fp, classref, name, propn, val, visible, accessible);
|
|
if (!(visible && accessible)) {
|
|
raise_error("Invalid static property access: %s::%s",
|
|
classref->m_data.pcls->name()->data(),
|
|
name->data());
|
|
}
|
|
tvSet(*tv1, *val);
|
|
tvRefcountedDecRefCell(propn);
|
|
memcpy(output, tv1, sizeof(TypedValue));
|
|
m_stack.ndiscard(2);
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopSetM(PC& pc) {
|
|
NEXT();
|
|
DECLARE_SETHELPER_ARGS
|
|
if (!setHelperPre<false, true, false, false, 1,
|
|
VectorLeaveCode::LeaveLast>(MEMBERHELPERPRE_ARGS)) {
|
|
Cell* c1 = m_stack.topC();
|
|
|
|
if (mcode == MW) {
|
|
SetNewElem<true>(base, c1);
|
|
} else {
|
|
switch (mcode) {
|
|
case MEL:
|
|
case MEC:
|
|
case MET:
|
|
case MEI: {
|
|
StringData* result = SetElem<true>(base, curMember, c1);
|
|
if (result) {
|
|
tvRefcountedDecRefCell(c1);
|
|
c1->m_type = KindOfString;
|
|
c1->m_data.pstr = result;
|
|
}
|
|
break;
|
|
}
|
|
case MPL:
|
|
case MPC:
|
|
case MPT: {
|
|
Class* ctx = arGetContextClass(m_fp);
|
|
SetProp<true>(ctx, base, curMember, c1);
|
|
break;
|
|
}
|
|
default: assert(false);
|
|
}
|
|
}
|
|
}
|
|
setHelperPost<1>(SETHELPERPOST_ARGS);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopSetWithRefLM(PC& pc) {
|
|
NEXT();
|
|
DECLARE_SETHELPER_ARGS
|
|
bool skip = setHelperPre<false, true, false, false, 0,
|
|
VectorLeaveCode::ConsumeAll>(MEMBERHELPERPRE_ARGS);
|
|
DECODE_HA(local);
|
|
if (!skip) {
|
|
TypedValue* from = frame_local(m_fp, local);
|
|
tvAsVariant(base) = withRefBind(tvAsVariant(from));
|
|
}
|
|
setHelperPost<0>(SETHELPERPOST_ARGS);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopSetWithRefRM(PC& pc) {
|
|
NEXT();
|
|
DECLARE_SETHELPER_ARGS
|
|
bool skip = setHelperPre<false, true, false, false, 1,
|
|
VectorLeaveCode::ConsumeAll>(MEMBERHELPERPRE_ARGS);
|
|
if (!skip) {
|
|
TypedValue* from = m_stack.top();
|
|
tvAsVariant(base) = withRefBind(tvAsVariant(from));
|
|
}
|
|
setHelperPost<0>(SETHELPERPOST_ARGS);
|
|
m_stack.popTV();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopSetOpL(PC& pc) {
|
|
NEXT();
|
|
DECODE_HA(local);
|
|
DECODE(unsigned char, op);
|
|
Cell* fr = m_stack.topC();
|
|
TypedValue* to = frame_local(m_fp, local);
|
|
SETOP_BODY(to, op, fr);
|
|
tvRefcountedDecRefCell(fr);
|
|
tvReadCell(to, fr);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopSetOpN(PC& pc) {
|
|
NEXT();
|
|
DECODE(unsigned char, op);
|
|
StringData* name;
|
|
Cell* fr = m_stack.topC();
|
|
TypedValue* tv2 = m_stack.indTV(1);
|
|
TypedValue* to = nullptr;
|
|
// XXX We're probably not getting warnings totally correct here
|
|
lookupd_var(m_fp, name, tv2, to);
|
|
assert(to != nullptr);
|
|
SETOP_BODY(to, op, fr);
|
|
tvRefcountedDecRef(fr);
|
|
tvRefcountedDecRef(tv2);
|
|
tvReadCell(to, tv2);
|
|
m_stack.discard();
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopSetOpG(PC& pc) {
|
|
NEXT();
|
|
DECODE(unsigned char, op);
|
|
StringData* name;
|
|
Cell* fr = m_stack.topC();
|
|
TypedValue* tv2 = m_stack.indTV(1);
|
|
TypedValue* to = nullptr;
|
|
// XXX We're probably not getting warnings totally correct here
|
|
lookupd_gbl(m_fp, name, tv2, to);
|
|
assert(to != nullptr);
|
|
SETOP_BODY(to, op, fr);
|
|
tvRefcountedDecRef(fr);
|
|
tvRefcountedDecRef(tv2);
|
|
tvReadCell(to, tv2);
|
|
m_stack.discard();
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopSetOpS(PC& pc) {
|
|
NEXT();
|
|
DECODE(unsigned char, op);
|
|
Cell* fr = m_stack.topC();
|
|
TypedValue* classref = m_stack.indTV(1);
|
|
TypedValue* propn = m_stack.indTV(2);
|
|
TypedValue* output = propn;
|
|
StringData* name;
|
|
TypedValue* val;
|
|
bool visible, accessible;
|
|
lookup_sprop(m_fp, classref, name, propn, val, visible, accessible);
|
|
if (!(visible && accessible)) {
|
|
raise_error("Invalid static property access: %s::%s",
|
|
classref->m_data.pcls->name()->data(),
|
|
name->data());
|
|
}
|
|
SETOP_BODY(val, op, fr);
|
|
tvRefcountedDecRefCell(propn);
|
|
tvRefcountedDecRef(fr);
|
|
tvReadCell(val, output);
|
|
m_stack.ndiscard(2);
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopSetOpM(PC& pc) {
|
|
NEXT();
|
|
DECODE(unsigned char, op);
|
|
DECLARE_SETHELPER_ARGS
|
|
if (!setHelperPre<MoreWarnings, true, false, false, 1,
|
|
VectorLeaveCode::LeaveLast>(MEMBERHELPERPRE_ARGS)) {
|
|
TypedValue* result;
|
|
Cell* rhs = m_stack.topC();
|
|
|
|
if (mcode == MW) {
|
|
result = SetOpNewElem(tvScratch, *tvRef.asTypedValue(), op, base, rhs);
|
|
} else {
|
|
switch (mcode) {
|
|
case MEL:
|
|
case MEC:
|
|
case MET:
|
|
case MEI:
|
|
result = SetOpElem(tvScratch, *tvRef.asTypedValue(), op, base,
|
|
curMember, rhs);
|
|
break;
|
|
case MPL:
|
|
case MPC:
|
|
case MPT: {
|
|
Class *ctx = arGetContextClass(m_fp);
|
|
result = SetOpProp(tvScratch, *tvRef.asTypedValue(), ctx, op, base,
|
|
curMember, rhs);
|
|
break;
|
|
}
|
|
default:
|
|
assert(false);
|
|
result = nullptr; // Silence compiler warning.
|
|
}
|
|
}
|
|
|
|
tvRefcountedDecRef(rhs);
|
|
tvReadCell(result, rhs);
|
|
}
|
|
setHelperPost<1>(SETHELPERPOST_ARGS);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIncDecL(PC& pc) {
|
|
NEXT();
|
|
DECODE_HA(local);
|
|
DECODE(unsigned char, op);
|
|
TypedValue* to = m_stack.allocTV();
|
|
tvWriteUninit(to);
|
|
TypedValue* fr = frame_local(m_fp, local);
|
|
IncDecBody<true>(op, fr, to);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIncDecN(PC& pc) {
|
|
NEXT();
|
|
DECODE(unsigned char, op);
|
|
StringData* name;
|
|
TypedValue* nameCell = m_stack.topTV();
|
|
TypedValue* local = nullptr;
|
|
// XXX We're probably not getting warnings totally correct here
|
|
lookupd_var(m_fp, name, nameCell, local);
|
|
assert(local != nullptr);
|
|
IncDecBody<true>(op, local, nameCell);
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIncDecG(PC& pc) {
|
|
NEXT();
|
|
DECODE(unsigned char, op);
|
|
StringData* name;
|
|
TypedValue* nameCell = m_stack.topTV();
|
|
TypedValue* gbl = nullptr;
|
|
// XXX We're probably not getting warnings totally correct here
|
|
lookupd_gbl(m_fp, name, nameCell, gbl);
|
|
assert(gbl != nullptr);
|
|
IncDecBody<true>(op, gbl, nameCell);
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIncDecS(PC& pc) {
|
|
StringData* name;
|
|
SPROP_OP_PRELUDE
|
|
DECODE(unsigned char, op);
|
|
if (!(visible && accessible)) {
|
|
raise_error("Invalid static property access: %s::%s",
|
|
clsref->m_data.pcls->name()->data(),
|
|
name->data());
|
|
}
|
|
tvRefcountedDecRefCell(nameCell);
|
|
IncDecBody<true>(op, val, output);
|
|
m_stack.discard();
|
|
SPROP_OP_POSTLUDE
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIncDecM(PC& pc) {
|
|
NEXT();
|
|
DECODE(unsigned char, op);
|
|
DECLARE_SETHELPER_ARGS
|
|
TypedValue to;
|
|
tvWriteUninit(&to);
|
|
if (!setHelperPre<MoreWarnings, true, false, false, 0,
|
|
VectorLeaveCode::LeaveLast>(MEMBERHELPERPRE_ARGS)) {
|
|
if (mcode == MW) {
|
|
IncDecNewElem<true>(tvScratch, *tvRef.asTypedValue(), op, base, to);
|
|
} else {
|
|
switch (mcode) {
|
|
case MEL:
|
|
case MEC:
|
|
case MET:
|
|
case MEI:
|
|
IncDecElem<true>(tvScratch, *tvRef.asTypedValue(), op, base,
|
|
curMember, to);
|
|
break;
|
|
case MPL:
|
|
case MPC:
|
|
case MPT: {
|
|
Class* ctx = arGetContextClass(m_fp);
|
|
IncDecProp<true>(tvScratch, *tvRef.asTypedValue(), ctx, op, base,
|
|
curMember, to);
|
|
break;
|
|
}
|
|
default: assert(false);
|
|
}
|
|
}
|
|
}
|
|
setHelperPost<0>(SETHELPERPOST_ARGS);
|
|
Cell* c1 = m_stack.allocC();
|
|
memcpy(c1, &to, sizeof(TypedValue));
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopBindL(PC& pc) {
|
|
NEXT();
|
|
DECODE_HA(local);
|
|
Ref* fr = m_stack.topV();
|
|
TypedValue* to = frame_local(m_fp, local);
|
|
tvBind(fr, to);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopBindN(PC& pc) {
|
|
NEXT();
|
|
StringData* name;
|
|
TypedValue* fr = m_stack.topTV();
|
|
TypedValue* nameTV = m_stack.indTV(1);
|
|
TypedValue* to = nullptr;
|
|
lookupd_var(m_fp, name, nameTV, to);
|
|
assert(to != nullptr);
|
|
tvBind(fr, to);
|
|
memcpy((void*)nameTV, (void*)fr, sizeof(TypedValue));
|
|
m_stack.discard();
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopBindG(PC& pc) {
|
|
NEXT();
|
|
StringData* name;
|
|
TypedValue* fr = m_stack.topTV();
|
|
TypedValue* nameTV = m_stack.indTV(1);
|
|
TypedValue* to = nullptr;
|
|
lookupd_gbl(m_fp, name, nameTV, to);
|
|
assert(to != nullptr);
|
|
tvBind(fr, to);
|
|
memcpy((void*)nameTV, (void*)fr, sizeof(TypedValue));
|
|
m_stack.discard();
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopBindS(PC& pc) {
|
|
NEXT();
|
|
TypedValue* fr = m_stack.topTV();
|
|
TypedValue* classref = m_stack.indTV(1);
|
|
TypedValue* propn = m_stack.indTV(2);
|
|
TypedValue* output = propn;
|
|
StringData* name;
|
|
TypedValue* val;
|
|
bool visible, accessible;
|
|
lookup_sprop(m_fp, classref, name, propn, val, visible, accessible);
|
|
if (!(visible && accessible)) {
|
|
raise_error("Invalid static property access: %s::%s",
|
|
classref->m_data.pcls->name()->data(),
|
|
name->data());
|
|
}
|
|
tvBind(fr, val);
|
|
tvRefcountedDecRefCell(propn);
|
|
memcpy(output, fr, sizeof(TypedValue));
|
|
m_stack.ndiscard(2);
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopBindM(PC& pc) {
|
|
NEXT();
|
|
DECLARE_SETHELPER_ARGS
|
|
TypedValue* tv1 = m_stack.topTV();
|
|
if (!setHelperPre<false, true, false, true, 1,
|
|
VectorLeaveCode::ConsumeAll>(MEMBERHELPERPRE_ARGS)) {
|
|
// Bind the element/property with the var on the top of the stack
|
|
tvBind(tv1, base);
|
|
}
|
|
setHelperPost<1>(SETHELPERPOST_ARGS);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopUnsetL(PC& pc) {
|
|
NEXT();
|
|
DECODE_HA(local);
|
|
assert(local < m_fp->m_func->numLocals());
|
|
TypedValue* tv = frame_local(m_fp, local);
|
|
tvRefcountedDecRef(tv);
|
|
tvWriteUninit(tv);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopUnsetN(PC& pc) {
|
|
NEXT();
|
|
StringData* name;
|
|
TypedValue* tv1 = m_stack.topTV();
|
|
TypedValue* tv = nullptr;
|
|
lookup_var(m_fp, name, tv1, tv);
|
|
assert(!m_fp->hasInvName());
|
|
if (tv != nullptr) {
|
|
tvRefcountedDecRef(tv);
|
|
tvWriteUninit(tv);
|
|
}
|
|
m_stack.popC();
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopUnsetG(PC& pc) {
|
|
NEXT();
|
|
TypedValue* tv1 = m_stack.topTV();
|
|
StringData* name = lookup_name(tv1);
|
|
VarEnv* varEnv = m_globalVarEnv;
|
|
assert(varEnv != nullptr);
|
|
varEnv->unset(name);
|
|
m_stack.popC();
|
|
decRefStr(name);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopUnsetM(PC& pc) {
|
|
NEXT();
|
|
DECLARE_SETHELPER_ARGS
|
|
if (!setHelperPre<false, false, true, false, 0,
|
|
VectorLeaveCode::LeaveLast>(MEMBERHELPERPRE_ARGS)) {
|
|
switch (mcode) {
|
|
case MEL:
|
|
case MEC:
|
|
case MET:
|
|
case MEI:
|
|
UnsetElem(base, curMember);
|
|
break;
|
|
case MPL:
|
|
case MPC:
|
|
case MPT: {
|
|
Class* ctx = arGetContextClass(m_fp);
|
|
UnsetProp(ctx, base, curMember);
|
|
break;
|
|
}
|
|
default: assert(false);
|
|
}
|
|
}
|
|
setHelperPost<0>(SETHELPERPOST_ARGS);
|
|
}
|
|
|
|
inline ActRec* OPTBLD_INLINE VMExecutionContext::fPushFuncImpl(
|
|
const Func* func,
|
|
int numArgs) {
|
|
DEBUGGER_IF(phpBreakpointEnabled(func->name()->data()));
|
|
ActRec* ar = m_stack.allocA();
|
|
arSetSfp(ar, m_fp);
|
|
ar->m_func = func;
|
|
ar->initNumArgs(numArgs);
|
|
ar->setVarEnv(nullptr);
|
|
return ar;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPushFunc(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(numArgs);
|
|
Cell* c1 = m_stack.topC();
|
|
const Func* func = nullptr;
|
|
ObjectData* origObj = nullptr;
|
|
StringData* origSd = nullptr;
|
|
if (IS_STRING_TYPE(c1->m_type)) {
|
|
origSd = c1->m_data.pstr;
|
|
func = Unit::loadFunc(origSd);
|
|
} else if (c1->m_type == KindOfObject) {
|
|
static StringData* invokeName = StringData::GetStaticString("__invoke");
|
|
origObj = c1->m_data.pobj;
|
|
const Class* cls = origObj->getVMClass();
|
|
func = cls->lookupMethod(invokeName);
|
|
if (func == nullptr) {
|
|
raise_error(Strings::FUNCTION_NAME_MUST_BE_STRING);
|
|
}
|
|
} else {
|
|
raise_error(Strings::FUNCTION_NAME_MUST_BE_STRING);
|
|
}
|
|
if (func == nullptr) {
|
|
raise_error("Call to undefined function %s()", c1->m_data.pstr->data());
|
|
}
|
|
assert(!origObj || !origSd);
|
|
assert(origObj || origSd);
|
|
// We've already saved origObj or origSd; we'll use them after
|
|
// overwriting the pointer on the stack. Don't refcount it now; defer
|
|
// till after we're done with it.
|
|
m_stack.discard();
|
|
ActRec* ar = fPushFuncImpl(func, numArgs);
|
|
if (origObj) {
|
|
if (func->attrs() & AttrStatic && !func->isClosureBody()) {
|
|
ar->setClass(origObj->getVMClass());
|
|
decRefObj(origObj);
|
|
} else {
|
|
ar->setThis(origObj);
|
|
// Teleport the reference from the destroyed stack cell to the
|
|
// ActRec. Don't try this at home.
|
|
}
|
|
} else {
|
|
ar->setThis(nullptr);
|
|
decRefStr(origSd);
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPushFuncD(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(numArgs);
|
|
DECODE(Id, id);
|
|
const NamedEntityPair nep = m_fp->m_func->unit()->lookupNamedEntityPairId(id);
|
|
Func* func = Unit::loadFunc(nep.second, nep.first);
|
|
if (func == nullptr) {
|
|
raise_error("Call to undefined function %s()",
|
|
m_fp->m_func->unit()->lookupLitstrId(id)->data());
|
|
}
|
|
ActRec* ar = fPushFuncImpl(func, numArgs);
|
|
ar->setThis(nullptr);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPushFuncU(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(numArgs);
|
|
DECODE(Id, nsFunc);
|
|
DECODE(Id, globalFunc);
|
|
Unit* unit = m_fp->m_func->unit();
|
|
const NamedEntityPair nep = unit->lookupNamedEntityPairId(nsFunc);
|
|
Func* func = Unit::loadFunc(nep.second, nep.first);
|
|
if (func == nullptr) {
|
|
const NamedEntityPair nep2 = unit->lookupNamedEntityPairId(globalFunc);
|
|
func = Unit::loadFunc(nep2.second, nep2.first);
|
|
if (func == nullptr) {
|
|
const char *funcName = unit->lookupLitstrId(nsFunc)->data();
|
|
raise_error("Call to undefined function %s()", funcName);
|
|
}
|
|
}
|
|
ActRec* ar = fPushFuncImpl(func, numArgs);
|
|
ar->setThis(nullptr);
|
|
}
|
|
|
|
void VMExecutionContext::fPushObjMethodImpl(
|
|
Class* cls, StringData* name, ObjectData* obj, int numArgs) {
|
|
const Func* f;
|
|
LookupResult res = lookupObjMethod(f, cls, name, true);
|
|
assert(f);
|
|
ActRec* ar = m_stack.allocA();
|
|
arSetSfp(ar, m_fp);
|
|
ar->m_func = f;
|
|
if (res == LookupResult::MethodFoundNoThis) {
|
|
decRefObj(obj);
|
|
ar->setClass(cls);
|
|
} else {
|
|
assert(res == LookupResult::MethodFoundWithThis ||
|
|
res == LookupResult::MagicCallFound);
|
|
/* Transfer ownership of obj to the ActRec*/
|
|
ar->setThis(obj);
|
|
}
|
|
ar->initNumArgs(numArgs);
|
|
if (res == LookupResult::MagicCallFound) {
|
|
ar->setInvName(name);
|
|
} else {
|
|
ar->setVarEnv(NULL);
|
|
decRefStr(name);
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPushObjMethod(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(numArgs);
|
|
Cell* c1 = m_stack.topC(); // Method name.
|
|
if (!IS_STRING_TYPE(c1->m_type)) {
|
|
raise_error(Strings::METHOD_NAME_MUST_BE_STRING);
|
|
}
|
|
Cell* c2 = m_stack.indC(1); // Object.
|
|
if (c2->m_type != KindOfObject) {
|
|
throw_call_non_object(c1->m_data.pstr->data());
|
|
}
|
|
ObjectData* obj = c2->m_data.pobj;
|
|
Class* cls = obj->getVMClass();
|
|
StringData* name = c1->m_data.pstr;
|
|
// We handle decReffing obj and name in fPushObjMethodImpl
|
|
m_stack.ndiscard(2);
|
|
fPushObjMethodImpl(cls, name, obj, numArgs);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPushObjMethodD(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(numArgs);
|
|
DECODE_LITSTR(name);
|
|
Cell* c1 = m_stack.topC();
|
|
if (c1->m_type != KindOfObject) {
|
|
throw_call_non_object(name->data());
|
|
}
|
|
ObjectData* obj = c1->m_data.pobj;
|
|
Class* cls = obj->getVMClass();
|
|
// We handle decReffing obj in fPushObjMethodImpl
|
|
m_stack.discard();
|
|
fPushObjMethodImpl(cls, name, obj, numArgs);
|
|
}
|
|
|
|
template<bool forwarding>
|
|
void VMExecutionContext::pushClsMethodImpl(Class* cls,
|
|
StringData* name,
|
|
ObjectData* obj,
|
|
int numArgs) {
|
|
const Func* f;
|
|
LookupResult res = lookupClsMethod(f, cls, name, obj, getFP(), true);
|
|
if (res == LookupResult::MethodFoundNoThis ||
|
|
res == LookupResult::MagicCallStaticFound) {
|
|
obj = nullptr;
|
|
} else {
|
|
assert(obj);
|
|
assert(res == LookupResult::MethodFoundWithThis ||
|
|
res == LookupResult::MagicCallFound);
|
|
obj->incRefCount();
|
|
}
|
|
assert(f);
|
|
ActRec* ar = m_stack.allocA();
|
|
arSetSfp(ar, m_fp);
|
|
ar->m_func = f;
|
|
if (obj) {
|
|
ar->setThis(obj);
|
|
} else {
|
|
if (!forwarding) {
|
|
ar->setClass(cls);
|
|
} else {
|
|
/* Propogate the current late bound class if there is one, */
|
|
/* otherwise use the class given by this instruction's input */
|
|
if (m_fp->hasThis()) {
|
|
cls = m_fp->getThis()->getVMClass();
|
|
} else if (m_fp->hasClass()) {
|
|
cls = m_fp->getClass();
|
|
}
|
|
ar->setClass(cls);
|
|
}
|
|
}
|
|
ar->initNumArgs(numArgs);
|
|
if (res == LookupResult::MagicCallFound ||
|
|
res == LookupResult::MagicCallStaticFound) {
|
|
ar->setInvName(name);
|
|
} else {
|
|
ar->setVarEnv(nullptr);
|
|
decRefStr(const_cast<StringData*>(name));
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPushClsMethod(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(numArgs);
|
|
Cell* c1 = m_stack.indC(1); // Method name.
|
|
if (!IS_STRING_TYPE(c1->m_type)) {
|
|
raise_error(Strings::FUNCTION_NAME_MUST_BE_STRING);
|
|
}
|
|
TypedValue* tv = m_stack.top();
|
|
assert(tv->m_type == KindOfClass);
|
|
Class* cls = tv->m_data.pcls;
|
|
StringData* name = c1->m_data.pstr;
|
|
// CLSMETHOD_BODY will take care of decReffing name
|
|
m_stack.ndiscard(2);
|
|
assert(cls && name);
|
|
ObjectData* obj = m_fp->hasThis() ? m_fp->getThis() : nullptr;
|
|
pushClsMethodImpl<false>(cls, name, obj, numArgs);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPushClsMethodD(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(numArgs);
|
|
DECODE_LITSTR(name);
|
|
DECODE(Id, classId);
|
|
const NamedEntityPair &nep =
|
|
m_fp->m_func->unit()->lookupNamedEntityPairId(classId);
|
|
Class* cls = Unit::loadClass(nep.second, nep.first);
|
|
if (cls == nullptr) {
|
|
raise_error(Strings::UNKNOWN_CLASS, nep.first->data());
|
|
}
|
|
ObjectData* obj = m_fp->hasThis() ? m_fp->getThis() : nullptr;
|
|
pushClsMethodImpl<false>(cls, name, obj, numArgs);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPushClsMethodF(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(numArgs);
|
|
Cell* c1 = m_stack.indC(1); // Method name.
|
|
if (!IS_STRING_TYPE(c1->m_type)) {
|
|
raise_error(Strings::FUNCTION_NAME_MUST_BE_STRING);
|
|
}
|
|
TypedValue* tv = m_stack.top();
|
|
assert(tv->m_type == KindOfClass);
|
|
Class* cls = tv->m_data.pcls;
|
|
assert(cls);
|
|
StringData* name = c1->m_data.pstr;
|
|
// CLSMETHOD_BODY will take care of decReffing name
|
|
m_stack.ndiscard(2);
|
|
ObjectData* obj = m_fp->hasThis() ? m_fp->getThis() : nullptr;
|
|
pushClsMethodImpl<true>(cls, name, obj, numArgs);
|
|
}
|
|
|
|
#undef CLSMETHOD_BODY
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPushCtor(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(numArgs);
|
|
TypedValue* tv = m_stack.topTV();
|
|
assert(tv->m_type == KindOfClass);
|
|
Class* cls = tv->m_data.pcls;
|
|
assert(cls != nullptr);
|
|
// Lookup the ctor
|
|
const Func* f;
|
|
LookupResult res UNUSED = lookupCtorMethod(f, cls, true);
|
|
assert(res == LookupResult::MethodFoundWithThis);
|
|
// Replace input with uninitialized instance.
|
|
ObjectData* this_ = newInstance(cls);
|
|
TRACE(2, "FPushCtor: just new'ed an instance of class %s: %p\n",
|
|
cls->name()->data(), this_);
|
|
this_->incRefCount();
|
|
this_->incRefCount();
|
|
tv->m_type = KindOfObject;
|
|
tv->m_data.pobj = this_;
|
|
// Push new activation record.
|
|
ActRec* ar = m_stack.allocA();
|
|
arSetSfp(ar, m_fp);
|
|
ar->m_func = f;
|
|
ar->setThis(this_);
|
|
ar->initNumArgs(numArgs, true /* isFPushCtor */);
|
|
arSetSfp(ar, m_fp);
|
|
ar->setVarEnv(nullptr);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPushCtorD(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(numArgs);
|
|
DECODE(Id, id);
|
|
const NamedEntityPair &nep =
|
|
m_fp->m_func->unit()->lookupNamedEntityPairId(id);
|
|
Class* cls = Unit::loadClass(nep.second, nep.first);
|
|
if (cls == nullptr) {
|
|
raise_error(Strings::UNKNOWN_CLASS,
|
|
m_fp->m_func->unit()->lookupLitstrId(id)->data());
|
|
}
|
|
// Lookup the ctor
|
|
const Func* f;
|
|
LookupResult res UNUSED = lookupCtorMethod(f, cls, true);
|
|
assert(res == LookupResult::MethodFoundWithThis);
|
|
// Push uninitialized instance.
|
|
ObjectData* this_ = newInstance(cls);
|
|
TRACE(2, "FPushCtorD: new'ed an instance of class %s: %p\n",
|
|
cls->name()->data(), this_);
|
|
this_->incRefCount();
|
|
m_stack.pushObject(this_);
|
|
// Push new activation record.
|
|
ActRec* ar = m_stack.allocA();
|
|
arSetSfp(ar, m_fp);
|
|
ar->m_func = f;
|
|
ar->setThis(this_);
|
|
ar->initNumArgs(numArgs, true /* isFPushCtor */);
|
|
ar->setVarEnv(nullptr);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopDecodeCufIter(PC& pc) {
|
|
PC origPc = pc;
|
|
NEXT();
|
|
DECODE_IA(itId);
|
|
DECODE(Offset, offset);
|
|
|
|
Iter* it = frame_iter(m_fp, itId);
|
|
CufIter &cit = it->cuf();
|
|
|
|
ObjectData* obj = nullptr;
|
|
HPHP::Class* cls = nullptr;
|
|
StringData* invName = nullptr;
|
|
TypedValue *func = m_stack.topTV();
|
|
|
|
ActRec* ar = m_fp;
|
|
if (m_fp->m_func->isBuiltin()) {
|
|
ar = getOuterVMFrame(ar);
|
|
}
|
|
const Func* f = vm_decode_function(tvAsVariant(func),
|
|
ar, false,
|
|
obj, cls, invName,
|
|
false);
|
|
|
|
if (f == nullptr) {
|
|
pc = origPc + offset;
|
|
} else {
|
|
cit.setFunc(f);
|
|
if (obj) {
|
|
cit.setCtx(obj);
|
|
obj->incRefCount();
|
|
} else {
|
|
cit.setCtx(cls);
|
|
}
|
|
cit.setName(invName);
|
|
}
|
|
m_stack.popC();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPushCufIter(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(numArgs);
|
|
DECODE_IA(itId);
|
|
|
|
Iter* it = frame_iter(m_fp, itId);
|
|
|
|
auto f = it->cuf().func();
|
|
auto o = it->cuf().ctx();
|
|
auto n = it->cuf().name();
|
|
|
|
ActRec* ar = m_stack.allocA();
|
|
arSetSfp(ar, m_fp);
|
|
ar->m_func = f;
|
|
ar->m_this = (ObjectData*)o;
|
|
if (o && !(uintptr_t(o) & 1)) ar->m_this->incRefCount();
|
|
if (n) {
|
|
ar->setInvName(n);
|
|
n->incRefCount();
|
|
} else {
|
|
ar->setVarEnv(nullptr);
|
|
}
|
|
ar->initNumArgs(numArgs, false /* isFPushCtor */);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::doFPushCuf(PC& pc,
|
|
bool forward,
|
|
bool safe) {
|
|
NEXT();
|
|
DECODE_IVA(numArgs);
|
|
|
|
TypedValue func = m_stack.topTV()[safe];
|
|
|
|
ObjectData* obj = nullptr;
|
|
HPHP::Class* cls = nullptr;
|
|
StringData* invName = nullptr;
|
|
|
|
const Func* f = vm_decode_function(tvAsVariant(&func), getFP(),
|
|
forward,
|
|
obj, cls, invName,
|
|
!safe);
|
|
|
|
if (safe) m_stack.topTV()[1] = m_stack.topTV()[0];
|
|
m_stack.ndiscard(1);
|
|
if (f == nullptr) {
|
|
f = SystemLib::s_nullFunc;
|
|
if (safe) {
|
|
m_stack.pushFalse();
|
|
}
|
|
} else if (safe) {
|
|
m_stack.pushTrue();
|
|
}
|
|
|
|
ActRec* ar = m_stack.allocA();
|
|
arSetSfp(ar, m_fp);
|
|
ar->m_func = f;
|
|
if (obj) {
|
|
ar->setThis(obj);
|
|
obj->incRefCount();
|
|
} else if (cls) {
|
|
ar->setClass(cls);
|
|
} else {
|
|
ar->setThis(nullptr);
|
|
}
|
|
ar->initNumArgs(numArgs, false /* isFPushCtor */);
|
|
if (invName) {
|
|
ar->setInvName(invName);
|
|
} else {
|
|
ar->setVarEnv(nullptr);
|
|
}
|
|
tvRefcountedDecRef(&func);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPushCuf(PC& pc) {
|
|
doFPushCuf(pc, false, false);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPushCufF(PC& pc) {
|
|
doFPushCuf(pc, true, false);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPushCufSafe(PC& pc) {
|
|
doFPushCuf(pc, false, true);
|
|
}
|
|
|
|
static inline ActRec* arFromInstr(TypedValue* sp, const Op* pc) {
|
|
return arFromSpOffset((ActRec*)sp, instrSpToArDelta(pc));
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPassC(PC& pc) {
|
|
#ifdef DEBUG
|
|
ActRec* ar = arFromInstr(m_stack.top(), (Op*)pc);
|
|
#endif
|
|
NEXT();
|
|
DECODE_IVA(paramId);
|
|
#ifdef DEBUG
|
|
assert(paramId < ar->numArgs());
|
|
#endif
|
|
}
|
|
|
|
#define FPASSC_CHECKED_PRELUDE \
|
|
ActRec* ar = arFromInstr(m_stack.top(), (Op*)pc); \
|
|
NEXT(); \
|
|
DECODE_IVA(paramId); \
|
|
assert(paramId < ar->numArgs()); \
|
|
const Func* func = ar->m_func;
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPassCW(PC& pc) {
|
|
FPASSC_CHECKED_PRELUDE
|
|
if (func->mustBeRef(paramId)) {
|
|
TRACE(1, "FPassCW: function %s(%d) param %d is by reference, "
|
|
"raising a strict warning (attr:0x%x)\n",
|
|
func->name()->data(), func->numParams(), paramId,
|
|
func->info() ? func->info()->attribute : 0);
|
|
raise_strict_warning("Only variables should be passed by reference");
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPassCE(PC& pc) {
|
|
FPASSC_CHECKED_PRELUDE
|
|
if (func->mustBeRef(paramId)) {
|
|
TRACE(1, "FPassCE: function %s(%d) param %d is by reference, "
|
|
"throwing a fatal error (attr:0x%x)\n",
|
|
func->name()->data(), func->numParams(), paramId,
|
|
func->info() ? func->info()->attribute : 0);
|
|
raise_error("Cannot pass parameter %d by reference", paramId+1);
|
|
}
|
|
}
|
|
|
|
#undef FPASSC_CHECKED_PRELUDE
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPassV(PC& pc) {
|
|
ActRec* ar = arFromInstr(m_stack.top(), (Op*)pc);
|
|
NEXT();
|
|
DECODE_IVA(paramId);
|
|
assert(paramId < ar->numArgs());
|
|
const Func* func = ar->m_func;
|
|
if (!func->byRef(paramId)) {
|
|
m_stack.unbox();
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPassR(PC& pc) {
|
|
ActRec* ar = arFromInstr(m_stack.top(), (Op*)pc);
|
|
NEXT();
|
|
DECODE_IVA(paramId);
|
|
assert(paramId < ar->numArgs());
|
|
const Func* func = ar->m_func;
|
|
if (func->byRef(paramId)) {
|
|
TypedValue* tv = m_stack.topTV();
|
|
if (tv->m_type != KindOfRef) {
|
|
tvBox(tv);
|
|
}
|
|
} else {
|
|
if (m_stack.topTV()->m_type == KindOfRef) {
|
|
m_stack.unbox();
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPassL(PC& pc) {
|
|
ActRec* ar = arFromInstr(m_stack.top(), (Op*)pc);
|
|
NEXT();
|
|
DECODE_IVA(paramId);
|
|
DECODE_HA(local);
|
|
assert(paramId < ar->numArgs());
|
|
TypedValue* fr = frame_local(m_fp, local);
|
|
TypedValue* to = m_stack.allocTV();
|
|
if (!ar->m_func->byRef(paramId)) {
|
|
cgetl_body(m_fp, fr, to, local);
|
|
} else {
|
|
vgetl_body(fr, to);
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPassN(PC& pc) {
|
|
ActRec* ar = arFromInstr(m_stack.top(), (Op*)pc);
|
|
PC origPc = pc;
|
|
NEXT();
|
|
DECODE_IVA(paramId);
|
|
assert(paramId < ar->numArgs());
|
|
if (!ar->m_func->byRef(paramId)) {
|
|
iopCGetN(origPc);
|
|
} else {
|
|
iopVGetN(origPc);
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPassG(PC& pc) {
|
|
ActRec* ar = arFromInstr(m_stack.top(), (Op*)pc);
|
|
PC origPc = pc;
|
|
NEXT();
|
|
DECODE_IVA(paramId);
|
|
assert(paramId < ar->numArgs());
|
|
if (!ar->m_func->byRef(paramId)) {
|
|
iopCGetG(origPc);
|
|
} else {
|
|
iopVGetG(origPc);
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFPassS(PC& pc) {
|
|
ActRec* ar = arFromInstr(m_stack.top(), (Op*)pc);
|
|
PC origPc = pc;
|
|
NEXT();
|
|
DECODE_IVA(paramId);
|
|
assert(paramId < ar->numArgs());
|
|
if (!ar->m_func->byRef(paramId)) {
|
|
iopCGetS(origPc);
|
|
} else {
|
|
iopVGetS(origPc);
|
|
}
|
|
}
|
|
|
|
void VMExecutionContext::iopFPassM(PC& pc) {
|
|
ActRec* ar = arFromInstr(m_stack.top(), (Op*)pc);
|
|
NEXT();
|
|
DECODE_IVA(paramId);
|
|
assert(paramId < ar->numArgs());
|
|
if (!ar->m_func->byRef(paramId)) {
|
|
DECLARE_GETHELPER_ARGS
|
|
getHelper(GETHELPER_ARGS);
|
|
if (tvRet->m_type == KindOfRef) {
|
|
tvUnbox(tvRet);
|
|
}
|
|
} else {
|
|
DECLARE_SETHELPER_ARGS
|
|
TypedValue* tv1 = m_stack.allocTV();
|
|
tvWriteUninit(tv1);
|
|
if (!setHelperPre<false, true, false, true, 1,
|
|
VectorLeaveCode::ConsumeAll>(MEMBERHELPERPRE_ARGS)) {
|
|
if (base->m_type != KindOfRef) {
|
|
tvBox(base);
|
|
}
|
|
refDup(*base, *tv1);
|
|
} else {
|
|
tvWriteNull(tv1);
|
|
tvBox(tv1);
|
|
}
|
|
setHelperPost<1>(SETHELPERPOST_ARGS);
|
|
}
|
|
}
|
|
|
|
bool VMExecutionContext::doFCall(ActRec* ar, PC& pc) {
|
|
assert(getOuterVMFrame(ar) == m_fp);
|
|
ar->m_savedRip =
|
|
reinterpret_cast<uintptr_t>(tx()->getRetFromInterpretedFrame());
|
|
assert(isReturnHelper(ar->m_savedRip));
|
|
TRACE(3, "FCall: pc %p func %p base %d\n", m_pc,
|
|
m_fp->m_func->unit()->entry(),
|
|
int(m_fp->m_func->base()));
|
|
ar->m_soff = m_fp->m_func->unit()->offsetOf(pc)
|
|
- (uintptr_t)m_fp->m_func->base();
|
|
assert(pcOff() >= m_fp->m_func->base());
|
|
prepareFuncEntry(ar, pc);
|
|
SYNC();
|
|
if (EventHook::FunctionEnter(ar, EventHook::NormalFunc)) return true;
|
|
pc = m_pc;
|
|
return false;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFCall(PC& pc) {
|
|
ActRec* ar = arFromInstr(m_stack.top(), (Op*)pc);
|
|
NEXT();
|
|
DECODE_IVA(numArgs);
|
|
assert(numArgs == ar->numArgs());
|
|
checkStack(m_stack, ar->m_func);
|
|
doFCall(ar, pc);
|
|
}
|
|
|
|
// Return a function pointer type for calling a builtin with a given
|
|
// return value and args.
|
|
template<class Ret, class... Args> struct NativeFunction {
|
|
typedef Ret (*type)(Args...);
|
|
};
|
|
|
|
// Recursively pack all parameters up to call a native builtin.
|
|
template<class Ret, size_t NArgs, size_t CurArg> struct NativeFuncCaller;
|
|
template<class Ret, size_t NArgs, size_t CurArg> struct NativeFuncCaller {
|
|
template<class... Args>
|
|
static Ret call(const Func* func, TypedValue* tvs, Args... args) {
|
|
typedef NativeFuncCaller<Ret,NArgs - 1,CurArg + 1> NextArgT;
|
|
DataType type = func->params()[CurArg].builtinType();
|
|
if (type == KindOfDouble) {
|
|
// pass TV.m_data.dbl by value with C++ calling convention for doubles
|
|
return NextArgT::call(func, tvs - 1, args..., tvs->m_data.dbl);
|
|
}
|
|
if (type == KindOfInt64 || type == KindOfBoolean) {
|
|
// pass TV.m_data.num by value
|
|
return NextArgT::call(func, tvs - 1, args..., tvs->m_data.num);
|
|
}
|
|
if (IS_STRING_TYPE(type) || type == KindOfArray || type == KindOfObject) {
|
|
// pass ptr to TV.m_data for String&, Array&, or Object&
|
|
return NextArgT::call(func, tvs - 1, args..., &tvs->m_data);
|
|
}
|
|
// final case is for passing full value as Variant&
|
|
return NextArgT::call(func, tvs - 1, args..., tvs);
|
|
}
|
|
};
|
|
template<class Ret, size_t CurArg> struct NativeFuncCaller<Ret,0,CurArg> {
|
|
template<class... Args>
|
|
static Ret call(const Func* f, TypedValue*, Args... args) {
|
|
typedef typename NativeFunction<Ret,Args...>::type FuncType;
|
|
return reinterpret_cast<FuncType>(f->nativeFuncPtr())(args...);
|
|
}
|
|
};
|
|
|
|
template<class Ret>
|
|
static Ret makeNativeCall(const Func* f, TypedValue* args, size_t numArgs) {
|
|
static_assert(kMaxBuiltinArgs == 5,
|
|
"makeNativeCall needs updates for kMaxBuiltinArgs");
|
|
switch (numArgs) {
|
|
case 0: return NativeFuncCaller<Ret,0,0>::call(f, args);
|
|
case 1: return NativeFuncCaller<Ret,1,0>::call(f, args);
|
|
case 2: return NativeFuncCaller<Ret,2,0>::call(f, args);
|
|
case 3: return NativeFuncCaller<Ret,3,0>::call(f, args);
|
|
case 4: return NativeFuncCaller<Ret,4,0>::call(f, args);
|
|
case 5: return NativeFuncCaller<Ret,5,0>::call(f, args);
|
|
default: assert(false);
|
|
}
|
|
not_reached();
|
|
}
|
|
|
|
template<class Ret>
|
|
static int makeNativeRefCall(const Func* f, Ret* ret,
|
|
TypedValue* args, size_t numArgs) {
|
|
switch (numArgs) {
|
|
case 0: return NativeFuncCaller<int64_t,0,0>::call(f, args, ret);
|
|
case 1: return NativeFuncCaller<int64_t,1,0>::call(f, args, ret);
|
|
case 2: return NativeFuncCaller<int64_t,2,0>::call(f, args, ret);
|
|
case 3: return NativeFuncCaller<int64_t,3,0>::call(f, args, ret);
|
|
case 4: return NativeFuncCaller<int64_t,4,0>::call(f, args, ret);
|
|
case 5: return NativeFuncCaller<int64_t,5,0>::call(f, args, ret);
|
|
default: assert(false);
|
|
}
|
|
not_reached();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFCallBuiltin(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(numArgs);
|
|
DECODE_IVA(numNonDefault);
|
|
DECODE(Id, id);
|
|
const NamedEntity* ne = m_fp->m_func->unit()->lookupNamedEntityId(id);
|
|
Func* func = Unit::lookupFunc(ne);
|
|
if (func == nullptr) {
|
|
raise_error("Undefined function: %s",
|
|
m_fp->m_func->unit()->lookupLitstrId(id)->data());
|
|
}
|
|
TypedValue* args = m_stack.indTV(numArgs-1);
|
|
assert(numArgs == func->numParams());
|
|
bool zendParamMode = func->info()->attribute & ClassInfo::ZendParamMode;
|
|
TypedValue ret;
|
|
|
|
for (int i = 0; i < numNonDefault; i++) {
|
|
const Func::ParamInfo& pi = func->params()[i];
|
|
|
|
#define CASE(kind) \
|
|
case KindOf##kind: \
|
|
if (zendParamMode) { \
|
|
if (!tvCoerceParamTo##kind##InPlace(&args[-i])) { \
|
|
ret.m_type = KindOfNull; \
|
|
goto free_frame; \
|
|
} \
|
|
} else { \
|
|
tvCastTo##kind##InPlace(&args[-i]); \
|
|
} \
|
|
break; /* end of case */
|
|
|
|
switch (pi.builtinType()) {
|
|
CASE(Boolean)
|
|
CASE(Int64)
|
|
CASE(Double)
|
|
CASE(String)
|
|
CASE(Array)
|
|
CASE(Object)
|
|
case KindOfUnknown:
|
|
break;
|
|
default:
|
|
not_reached();
|
|
}
|
|
|
|
#undef CASE
|
|
}
|
|
|
|
ret.m_type = func->returnType();
|
|
switch (func->returnType()) {
|
|
case KindOfBoolean:
|
|
ret.m_data.num = makeNativeCall<bool>(func, args, numArgs);
|
|
break;
|
|
case KindOfNull: /* void return type */
|
|
case KindOfInt64:
|
|
ret.m_data.num = makeNativeCall<int64_t>(func, args, numArgs);
|
|
break;
|
|
case KindOfString:
|
|
case KindOfStaticString:
|
|
case KindOfArray:
|
|
case KindOfObject:
|
|
makeNativeRefCall(func, &ret.m_data, args, numArgs);
|
|
if (ret.m_data.num == 0) {
|
|
ret.m_type = KindOfNull;
|
|
}
|
|
break;
|
|
case KindOfUnknown:
|
|
makeNativeRefCall(func, &ret, args, numArgs);
|
|
if (ret.m_type == KindOfUninit) {
|
|
ret.m_type = KindOfNull;
|
|
}
|
|
break;
|
|
default:
|
|
not_reached();
|
|
}
|
|
|
|
free_frame:
|
|
frame_free_args(args, numNonDefault);
|
|
m_stack.ndiscard(numArgs);
|
|
memcpy(m_stack.allocTV(), &ret, sizeof(TypedValue));
|
|
}
|
|
|
|
bool VMExecutionContext::prepareArrayArgs(ActRec* ar,
|
|
ArrayData* args) {
|
|
if (UNLIKELY(ar->hasInvName())) {
|
|
m_stack.pushStringNoRc(ar->getInvName());
|
|
m_stack.pushArray(args);
|
|
ar->setVarEnv(0);
|
|
ar->initNumArgs(2);
|
|
return true;
|
|
}
|
|
|
|
int nargs = args->size();
|
|
const Func* f = ar->m_func;
|
|
int nparams = f->numParams();
|
|
int extra = nargs - nparams;
|
|
if (extra < 0) {
|
|
extra = 0;
|
|
nparams = nargs;
|
|
}
|
|
ssize_t pos = args->iter_begin();
|
|
for (int i = 0; i < nparams; ++i) {
|
|
TypedValue* from = const_cast<TypedValue*>(
|
|
args->getValueRef(pos).asTypedValue());
|
|
TypedValue* to = m_stack.allocTV();
|
|
if (UNLIKELY(f->byRef(i))) {
|
|
if (UNLIKELY(!tvAsVariant(from).isReferenced())) {
|
|
raise_warning("Parameter %d to %s() expected to be a reference, "
|
|
"value given", i + 1, f->fullName()->data());
|
|
if (skipCufOnInvalidParams) {
|
|
m_stack.discard();
|
|
while (i--) m_stack.popTV();
|
|
m_stack.popAR();
|
|
m_stack.pushNull();
|
|
return false;
|
|
}
|
|
}
|
|
tvDup(*from, *to);
|
|
} else {
|
|
tvDup(*from, *to);
|
|
if (UNLIKELY(to->m_type == KindOfRef)) {
|
|
tvUnbox(to);
|
|
}
|
|
}
|
|
pos = args->iter_advance(pos);
|
|
}
|
|
if (extra && (ar->m_func->attrs() & AttrMayUseVV)) {
|
|
ExtraArgs* extraArgs = ExtraArgs::allocateUninit(extra);
|
|
for (int i = 0; i < extra; ++i) {
|
|
TypedValue* to = extraArgs->getExtraArg(i);
|
|
tvDup(*args->getValueRef(pos).asTypedValue(), *to);
|
|
if (to->m_type == KindOfRef && to->m_data.pref->m_count == 2) {
|
|
tvUnbox(to);
|
|
}
|
|
pos = args->iter_advance(pos);
|
|
}
|
|
ar->setExtraArgs(extraArgs);
|
|
ar->initNumArgs(nargs);
|
|
} else {
|
|
ar->initNumArgs(nparams);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void cleanupParamsAndActRec(Stack& stack,
|
|
ActRec* ar,
|
|
ExtraArgs* extraArgs) {
|
|
assert(stack.top() + (extraArgs ?
|
|
ar->m_func->numParams() :
|
|
ar->numArgs()) == (void*)ar);
|
|
if (extraArgs) {
|
|
const int numExtra = ar->numArgs() - ar->m_func->numParams();
|
|
ExtraArgs::deallocate(extraArgs, numExtra);
|
|
}
|
|
while (stack.top() != (void*)ar) {
|
|
stack.popTV();
|
|
}
|
|
stack.popAR();
|
|
}
|
|
|
|
bool VMExecutionContext::doFCallArray(PC& pc) {
|
|
ActRec* ar = (ActRec*)(m_stack.top() + 1);
|
|
assert(ar->numArgs() == 1);
|
|
|
|
Cell* c1 = m_stack.topC();
|
|
if (skipCufOnInvalidParams && UNLIKELY(c1->m_type != KindOfArray)) {
|
|
// task #1756122
|
|
// this is what we /should/ do, but our code base depends
|
|
// on the broken behavior of casting the second arg to an
|
|
// array.
|
|
cleanupParamsAndActRec(m_stack, ar, nullptr);
|
|
m_stack.pushNull();
|
|
raise_warning("call_user_func_array() expects parameter 2 to be array");
|
|
return false;
|
|
}
|
|
|
|
const Func* func = ar->m_func;
|
|
{
|
|
Array args(LIKELY(c1->m_type == KindOfArray) ? c1->m_data.parr :
|
|
tvAsVariant(c1).toArray().get());
|
|
m_stack.popTV();
|
|
checkStack(m_stack, func);
|
|
|
|
assert(ar->m_savedRbp == (uint64_t)m_fp);
|
|
assert(!ar->m_func->isGenerator());
|
|
ar->m_savedRip =
|
|
reinterpret_cast<uintptr_t>(tx()->getRetFromInterpretedFrame());
|
|
assert(isReturnHelper(ar->m_savedRip));
|
|
TRACE(3, "FCallArray: pc %p func %p base %d\n", m_pc,
|
|
m_fp->m_func->unit()->entry(),
|
|
int(m_fp->m_func->base()));
|
|
ar->m_soff = m_fp->m_func->unit()->offsetOf(pc)
|
|
- (uintptr_t)m_fp->m_func->base();
|
|
assert(pcOff() > m_fp->m_func->base());
|
|
|
|
if (UNLIKELY(!prepareArrayArgs(ar, args.get()))) return false;
|
|
}
|
|
|
|
if (UNLIKELY(!(prepareFuncEntry(ar, pc)))) {
|
|
return false;
|
|
}
|
|
SYNC();
|
|
if (UNLIKELY(!EventHook::FunctionEnter(ar, EventHook::NormalFunc))) {
|
|
pc = m_pc;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopFCallArray(PC& pc) {
|
|
NEXT();
|
|
(void)doFCallArray(pc);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCufSafeArray(PC& pc) {
|
|
NEXT();
|
|
Array ret;
|
|
ret.append(tvAsVariant(m_stack.top() + 1));
|
|
ret.appendWithRef(tvAsVariant(m_stack.top() + 0));
|
|
m_stack.popTV();
|
|
m_stack.popTV();
|
|
tvAsVariant(m_stack.top()) = ret;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCufSafeReturn(PC& pc) {
|
|
NEXT();
|
|
bool ok = tvAsVariant(m_stack.top() + 1).toBoolean();
|
|
tvRefcountedDecRef(m_stack.top() + 1);
|
|
tvRefcountedDecRef(m_stack.top() + (ok ? 2 : 0));
|
|
if (ok) m_stack.top()[2] = m_stack.top()[0];
|
|
m_stack.ndiscard(2);
|
|
}
|
|
|
|
inline bool VMExecutionContext::initIterator(PC& pc, PC& origPc, Iter* it,
|
|
Offset offset, Cell* c1) {
|
|
bool hasElems = it->init(c1);
|
|
if (!hasElems) {
|
|
ITER_SKIP(offset);
|
|
}
|
|
m_stack.popC();
|
|
return hasElems;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIterInit(PC& pc) {
|
|
PC origPc = pc;
|
|
NEXT();
|
|
DECODE_IA(itId);
|
|
DECODE(Offset, offset);
|
|
DECODE_HA(val);
|
|
Cell* c1 = m_stack.topC();
|
|
Iter* it = frame_iter(m_fp, itId);
|
|
TypedValue* tv1 = frame_local(m_fp, val);
|
|
if (initIterator(pc, origPc, it, offset, c1)) {
|
|
tvAsVariant(tv1) = it->arr().second();
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIterInitK(PC& pc) {
|
|
PC origPc = pc;
|
|
NEXT();
|
|
DECODE_IA(itId);
|
|
DECODE(Offset, offset);
|
|
DECODE_HA(val);
|
|
DECODE_HA(key);
|
|
Cell* c1 = m_stack.topC();
|
|
Iter* it = frame_iter(m_fp, itId);
|
|
TypedValue* tv1 = frame_local(m_fp, val);
|
|
TypedValue* tv2 = frame_local(m_fp, key);
|
|
if (initIterator(pc, origPc, it, offset, c1)) {
|
|
tvAsVariant(tv1) = it->arr().second();
|
|
tvAsVariant(tv2) = it->arr().first();
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopWIterInit(PC& pc) {
|
|
PC origPc = pc;
|
|
NEXT();
|
|
DECODE_IA(itId);
|
|
DECODE(Offset, offset);
|
|
DECODE_HA(val);
|
|
Cell* c1 = m_stack.topC();
|
|
Iter* it = frame_iter(m_fp, itId);
|
|
TypedValue* tv1 = frame_local(m_fp, val);
|
|
if (initIterator(pc, origPc, it, offset, c1)) {
|
|
tvAsVariant(tv1) = withRefBind(it->arr().secondRef());
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopWIterInitK(PC& pc) {
|
|
PC origPc = pc;
|
|
NEXT();
|
|
DECODE_IA(itId);
|
|
DECODE(Offset, offset);
|
|
DECODE_HA(val);
|
|
DECODE_HA(key);
|
|
Cell* c1 = m_stack.topC();
|
|
Iter* it = frame_iter(m_fp, itId);
|
|
TypedValue* tv1 = frame_local(m_fp, val);
|
|
TypedValue* tv2 = frame_local(m_fp, key);
|
|
if (initIterator(pc, origPc, it, offset, c1)) {
|
|
tvAsVariant(tv1) = withRefBind(it->arr().secondRef());
|
|
tvAsVariant(tv2) = it->arr().first();
|
|
}
|
|
}
|
|
|
|
|
|
inline bool VMExecutionContext::initIteratorM(PC& pc, PC& origPc, Iter* it,
|
|
Offset offset, Ref* r1,
|
|
TypedValue *val,
|
|
TypedValue *key) {
|
|
bool hasElems = false;
|
|
TypedValue* rtv = r1->m_data.pref->tv();
|
|
if (rtv->m_type == KindOfArray) {
|
|
hasElems = new_miter_array_key(it, r1->m_data.pref, val, key);
|
|
} else if (rtv->m_type == KindOfObject) {
|
|
Class* ctx = arGetContextClass(g_vmContext->getFP());
|
|
hasElems = new_miter_object(it, r1->m_data.pref, ctx, val, key);
|
|
} else {
|
|
hasElems = new_miter_other(it, r1->m_data.pref);
|
|
}
|
|
|
|
if (!hasElems) {
|
|
ITER_SKIP(offset);
|
|
}
|
|
|
|
m_stack.popV();
|
|
return hasElems;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopMIterInit(PC& pc) {
|
|
PC origPc = pc;
|
|
NEXT();
|
|
DECODE_IA(itId);
|
|
DECODE(Offset, offset);
|
|
DECODE_HA(val);
|
|
Ref* r1 = m_stack.topV();
|
|
assert(r1->m_type == KindOfRef);
|
|
Iter* it = frame_iter(m_fp, itId);
|
|
TypedValue* tv1 = frame_local(m_fp, val);
|
|
initIteratorM(pc, origPc, it, offset, r1, tv1, nullptr);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopMIterInitK(PC& pc) {
|
|
PC origPc = pc;
|
|
NEXT();
|
|
DECODE_IA(itId);
|
|
DECODE(Offset, offset);
|
|
DECODE_HA(val);
|
|
DECODE_HA(key);
|
|
Ref* r1 = m_stack.topV();
|
|
assert(r1->m_type == KindOfRef);
|
|
Iter* it = frame_iter(m_fp, itId);
|
|
TypedValue* tv1 = frame_local(m_fp, val);
|
|
TypedValue* tv2 = frame_local(m_fp, key);
|
|
initIteratorM(pc, origPc, it, offset, r1, tv1, tv2);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIterNext(PC& pc) {
|
|
PC origPc = pc;
|
|
NEXT();
|
|
DECODE_IA(itId);
|
|
DECODE(Offset, offset);
|
|
DECODE_HA(val);
|
|
Iter* it = frame_iter(m_fp, itId);
|
|
TypedValue* tv1 = frame_local(m_fp, val);
|
|
if (it->next()) {
|
|
ITER_SKIP(offset);
|
|
tvAsVariant(tv1) = it->arr().second();
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIterNextK(PC& pc) {
|
|
PC origPc = pc;
|
|
NEXT();
|
|
DECODE_IA(itId);
|
|
DECODE(Offset, offset);
|
|
DECODE_HA(val);
|
|
DECODE_HA(key);
|
|
Iter* it = frame_iter(m_fp, itId);
|
|
TypedValue* tv1 = frame_local(m_fp, val);
|
|
TypedValue* tv2 = frame_local(m_fp, key);
|
|
if (it->next()) {
|
|
ITER_SKIP(offset);
|
|
tvAsVariant(tv1) = it->arr().second();
|
|
tvAsVariant(tv2) = it->arr().first();
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopWIterNext(PC& pc) {
|
|
PC origPc = pc;
|
|
NEXT();
|
|
DECODE_IA(itId);
|
|
DECODE(Offset, offset);
|
|
DECODE_HA(val);
|
|
Iter* it = frame_iter(m_fp, itId);
|
|
TypedValue* tv1 = frame_local(m_fp, val);
|
|
if (it->next()) {
|
|
ITER_SKIP(offset);
|
|
tvAsVariant(tv1) = withRefBind(it->arr().secondRef());
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopWIterNextK(PC& pc) {
|
|
PC origPc = pc;
|
|
NEXT();
|
|
DECODE_IA(itId);
|
|
DECODE(Offset, offset);
|
|
DECODE_HA(val);
|
|
DECODE_HA(key);
|
|
Iter* it = frame_iter(m_fp, itId);
|
|
TypedValue* tv1 = frame_local(m_fp, val);
|
|
TypedValue* tv2 = frame_local(m_fp, key);
|
|
if (it->next()) {
|
|
ITER_SKIP(offset);
|
|
tvAsVariant(tv1) = withRefBind(it->arr().secondRef());
|
|
tvAsVariant(tv2) = it->arr().first();
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopMIterNext(PC& pc) {
|
|
PC origPc = pc;
|
|
NEXT();
|
|
DECODE_IA(itId);
|
|
DECODE(Offset, offset);
|
|
DECODE_HA(val);
|
|
Iter* it = frame_iter(m_fp, itId);
|
|
TypedValue* tv1 = frame_local(m_fp, val);
|
|
if (miter_next_key(it, tv1, nullptr)) {
|
|
ITER_SKIP(offset);
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopMIterNextK(PC& pc) {
|
|
PC origPc = pc;
|
|
NEXT();
|
|
DECODE_IA(itId);
|
|
DECODE(Offset, offset);
|
|
DECODE_HA(val);
|
|
DECODE_HA(key);
|
|
Iter* it = frame_iter(m_fp, itId);
|
|
TypedValue* tv1 = frame_local(m_fp, val);
|
|
TypedValue* tv2 = frame_local(m_fp, key);
|
|
if (miter_next_key(it, tv1, tv2)) {
|
|
ITER_SKIP(offset);
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIterFree(PC& pc) {
|
|
NEXT();
|
|
DECODE_IA(itId);
|
|
Iter* it = frame_iter(m_fp, itId);
|
|
it->free();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopMIterFree(PC& pc) {
|
|
NEXT();
|
|
DECODE_IA(itId);
|
|
Iter* it = frame_iter(m_fp, itId);
|
|
it->mfree();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCIterFree(PC& pc) {
|
|
NEXT();
|
|
DECODE_IA(itId);
|
|
Iter* it = frame_iter(m_fp, itId);
|
|
it->cfree();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE inclOp(VMExecutionContext *ec, PC &pc,
|
|
InclOpFlags flags) {
|
|
NEXT();
|
|
Cell* c1 = ec->m_stack.topC();
|
|
String path(prepareKey(c1));
|
|
bool initial;
|
|
TRACE(2, "inclOp %s %s %s %s \"%s\"\n",
|
|
flags & InclOpOnce ? "Once" : "",
|
|
flags & InclOpDocRoot ? "DocRoot" : "",
|
|
flags & InclOpRelative ? "Relative" : "",
|
|
flags & InclOpFatal ? "Fatal" : "",
|
|
path->data());
|
|
|
|
Unit* u = flags & (InclOpDocRoot|InclOpRelative) ?
|
|
ec->evalIncludeRoot(path.get(), flags, &initial) :
|
|
ec->evalInclude(path.get(), ec->m_fp->m_func->unit()->filepath(), &initial);
|
|
ec->m_stack.popC();
|
|
if (u == nullptr) {
|
|
((flags & InclOpFatal) ?
|
|
(void (*)(const char *, ...))raise_error :
|
|
(void (*)(const char *, ...))raise_warning)("File not found: %s",
|
|
path->data());
|
|
ec->m_stack.pushFalse();
|
|
} else {
|
|
if (!(flags & InclOpOnce) || initial) {
|
|
ec->evalUnit(u, pc, EventHook::PseudoMain);
|
|
} else {
|
|
Stats::inc(Stats::PseudoMain_Guarded);
|
|
ec->m_stack.pushTrue();
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIncl(PC& pc) {
|
|
inclOp(this, pc, InclOpDefault);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopInclOnce(PC& pc) {
|
|
inclOp(this, pc, InclOpOnce);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopReq(PC& pc) {
|
|
inclOp(this, pc, InclOpFatal);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopReqOnce(PC& pc) {
|
|
inclOp(this, pc, InclOpFatal | InclOpOnce);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopReqDoc(PC& pc) {
|
|
inclOp(this, pc, InclOpFatal | InclOpOnce | InclOpDocRoot);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopEval(PC& pc) {
|
|
NEXT();
|
|
Cell* c1 = m_stack.topC();
|
|
String code(prepareKey(c1));
|
|
String prefixedCode = concat("<?php ", code);
|
|
Unit* unit = compileEvalString(prefixedCode.get());
|
|
if (unit == nullptr) {
|
|
raise_error("Syntax error in eval()");
|
|
}
|
|
m_stack.popC();
|
|
evalUnit(unit, pc, EventHook::Eval);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopDefFunc(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(fid);
|
|
Func* f = m_fp->m_func->unit()->lookupFuncId(fid);
|
|
f->setCached();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopDefCls(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(cid);
|
|
PreClass* c = m_fp->m_func->unit()->lookupPreClassId(cid);
|
|
Unit::defClass(c);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopDefTypedef(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(tid);
|
|
m_fp->m_func->unit()->defTypedef(tid);
|
|
}
|
|
|
|
static inline void checkThis(ActRec* fp) {
|
|
if (!fp->hasThis()) {
|
|
raise_error(Strings::FATAL_NULL_THIS);
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopThis(PC& pc) {
|
|
NEXT();
|
|
checkThis(m_fp);
|
|
ObjectData* this_ = m_fp->getThis();
|
|
m_stack.pushObject(this_);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopBareThis(PC& pc) {
|
|
NEXT();
|
|
DECODE(unsigned char, notice);
|
|
if (m_fp->hasThis()) {
|
|
ObjectData* this_ = m_fp->getThis();
|
|
m_stack.pushObject(this_);
|
|
} else {
|
|
m_stack.pushNull();
|
|
if (notice) raise_notice(Strings::WARN_NULL_THIS);
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCheckThis(PC& pc) {
|
|
NEXT();
|
|
checkThis(m_fp);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopInitThisLoc(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(id);
|
|
TypedValue* thisLoc = frame_local(m_fp, id);
|
|
tvRefcountedDecRef(thisLoc);
|
|
if (m_fp->hasThis()) {
|
|
thisLoc->m_data.pobj = m_fp->getThis();
|
|
thisLoc->m_type = KindOfObject;
|
|
tvIncRef(thisLoc);
|
|
} else {
|
|
tvWriteUninit(thisLoc);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Helper for StaticLoc and StaticLocInit.
|
|
*/
|
|
static inline void
|
|
lookupStatic(StringData* name,
|
|
const ActRec* fp,
|
|
TypedValue*&val, bool& inited) {
|
|
HphpArray* map = get_static_locals(fp);
|
|
assert(map != nullptr);
|
|
val = map->nvGet(name);
|
|
if (val == nullptr) {
|
|
TypedValue tv;
|
|
tvWriteUninit(&tv);
|
|
map->set(name, tvAsCVarRef(&tv), false);
|
|
val = map->nvGet(name);
|
|
inited = false;
|
|
} else {
|
|
inited = true;
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopStaticLoc(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(localId);
|
|
DECODE_LITSTR(var);
|
|
TypedValue* fr = nullptr;
|
|
bool inited;
|
|
lookupStatic(var, m_fp, fr, inited);
|
|
assert(fr != nullptr);
|
|
if (fr->m_type != KindOfRef) {
|
|
assert(!inited);
|
|
tvBox(fr);
|
|
}
|
|
TypedValue* tvLocal = frame_local(m_fp, localId);
|
|
tvBind(fr, tvLocal);
|
|
if (inited) {
|
|
m_stack.pushTrue();
|
|
} else {
|
|
m_stack.pushFalse();
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopStaticLocInit(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(localId);
|
|
DECODE_LITSTR(var);
|
|
TypedValue* fr = nullptr;
|
|
bool inited;
|
|
lookupStatic(var, m_fp, fr, inited);
|
|
assert(fr != nullptr);
|
|
if (!inited) {
|
|
Cell* initVal = m_stack.topC();
|
|
tvDup(*initVal, *fr);
|
|
}
|
|
if (fr->m_type != KindOfRef) {
|
|
assert(!inited);
|
|
tvBox(fr);
|
|
}
|
|
TypedValue* tvLocal = frame_local(m_fp, localId);
|
|
tvBind(fr, tvLocal);
|
|
m_stack.discard();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCatch(PC& pc) {
|
|
NEXT();
|
|
assert(m_faults.size() > 0);
|
|
Fault fault = m_faults.back();
|
|
m_faults.pop_back();
|
|
assert(fault.m_faultType == Fault::Type::UserException);
|
|
m_stack.pushObjectNoRc(fault.m_userException);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopLateBoundCls(PC& pc) {
|
|
NEXT();
|
|
Class* cls = frameStaticClass(m_fp);
|
|
if (!cls) {
|
|
raise_error(HPHP::Strings::CANT_ACCESS_STATIC);
|
|
}
|
|
m_stack.pushClass(cls);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopVerifyParamType(PC& pc) {
|
|
SYNC(); // We might need m_pc to be updated to throw.
|
|
NEXT();
|
|
|
|
DECODE_IVA(param);
|
|
const Func *func = m_fp->m_func;
|
|
assert(param < func->numParams());
|
|
assert(func->numParams() == int(func->params().size()));
|
|
const TypeConstraint& tc = func->params()[param].typeConstraint();
|
|
assert(tc.hasConstraint());
|
|
if (UNLIKELY(!RuntimeOption::EvalCheckExtendedTypeHints &&
|
|
tc.isExtended())) {
|
|
return;
|
|
}
|
|
const TypedValue *tv = frame_local(m_fp, param);
|
|
tc.verify(tv, func, param);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopNativeImpl(PC& pc) {
|
|
NEXT();
|
|
uint soff = m_fp->m_soff;
|
|
BuiltinFunction func = m_fp->m_func->builtinFuncPtr();
|
|
assert(func);
|
|
// Actually call the native implementation. This will handle freeing the
|
|
// locals in the normal case. In the case of an exception, the VM unwinder
|
|
// will take care of it.
|
|
func(m_fp);
|
|
// Adjust the stack; the native implementation put the return value in the
|
|
// right place for us already
|
|
m_stack.ndiscard(m_fp->m_func->numSlotsInFrame());
|
|
ActRec* sfp = m_fp->arGetSfp();
|
|
if (LIKELY(sfp != m_fp)) {
|
|
// Restore caller's execution state.
|
|
m_fp = sfp;
|
|
pc = m_fp->m_func->unit()->entry() + m_fp->m_func->base() + soff;
|
|
m_stack.ret();
|
|
} else {
|
|
// No caller; terminate.
|
|
m_stack.ret();
|
|
#ifdef HPHP_TRACE
|
|
{
|
|
std::ostringstream os;
|
|
os << toStringElm(m_stack.topTV());
|
|
ONTRACE(1,
|
|
Trace::trace("Return %s from VMExecutionContext::dispatch("
|
|
"%p)\n", os.str().c_str(), m_fp));
|
|
}
|
|
#endif
|
|
pc = 0;
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopHighInvalid(PC& pc) {
|
|
fprintf(stderr, "invalid bytecode executed\n");
|
|
abort();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopSelf(PC& pc) {
|
|
NEXT();
|
|
Class* clss = arGetContextClass(m_fp);
|
|
if (!clss) {
|
|
raise_error(HPHP::Strings::CANT_ACCESS_SELF);
|
|
}
|
|
m_stack.pushClass(clss);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopParent(PC& pc) {
|
|
NEXT();
|
|
Class* clss = arGetContextClass(m_fp);
|
|
if (!clss) {
|
|
raise_error(HPHP::Strings::CANT_ACCESS_PARENT_WHEN_NO_CLASS);
|
|
}
|
|
Class* parent = clss->parent();
|
|
if (!parent) {
|
|
raise_error(HPHP::Strings::CANT_ACCESS_PARENT_WHEN_NO_PARENT);
|
|
}
|
|
m_stack.pushClass(parent);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCreateCl(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(numArgs);
|
|
DECODE_LITSTR(clsName);
|
|
Class* cls = Unit::loadClass(clsName);
|
|
c_Closure* cl = static_cast<c_Closure*>(newInstance(cls));
|
|
c_Closure* cl2 = cl->init(numArgs, m_fp, m_stack.top());
|
|
m_stack.ndiscard(numArgs);
|
|
assert(cl == cl2);
|
|
m_stack.pushObject(cl2);
|
|
}
|
|
|
|
static inline c_Continuation* createCont(const Func* origFunc,
|
|
const Func* genFunc) {
|
|
auto const cont = c_Continuation::alloc(origFunc, genFunc);
|
|
cont->incRefCount();
|
|
cont->setNoDestruct();
|
|
|
|
// The ActRec corresponding to the generator body lives as long as the object
|
|
// does. We set it up once, here, and then just change FP to point to it when
|
|
// we enter the generator body.
|
|
ActRec* ar = cont->actRec();
|
|
ar->m_func = genFunc;
|
|
ar->initNumArgs(0);
|
|
ar->setVarEnv(nullptr);
|
|
|
|
return cont;
|
|
}
|
|
|
|
c_Continuation*
|
|
VMExecutionContext::createContFunc(const Func* origFunc,
|
|
const Func* genFunc) {
|
|
auto cont = createCont(origFunc, genFunc);
|
|
cont->actRec()->setThis(nullptr);
|
|
return cont;
|
|
}
|
|
|
|
c_Continuation*
|
|
VMExecutionContext::createContMeth(const Func* origFunc,
|
|
const Func* genFunc,
|
|
void* objOrCls) {
|
|
if (origFunc->isClosureBody()) {
|
|
genFunc = genFunc->cloneAndSetClass(origFunc->cls());
|
|
}
|
|
|
|
auto cont = createCont(origFunc, genFunc);
|
|
auto ar = cont->actRec();
|
|
ar->setThisOrClass(objOrCls);
|
|
if (ar->hasThis()) {
|
|
ar->getThis()->incRefCount();
|
|
}
|
|
return cont;
|
|
}
|
|
|
|
static inline void setContVar(const Func* genFunc,
|
|
const StringData* name,
|
|
TypedValue* src,
|
|
c_Continuation* cont) {
|
|
Id destId = genFunc->lookupVarId(name);
|
|
if (destId != kInvalidId) {
|
|
// Copy the value of the local to the cont object and set the
|
|
// local to uninit so that we don't need to change refcounts.
|
|
tvCopy(*src, *frame_local(cont->actRec(), destId));
|
|
tvWriteUninit(src);
|
|
} else {
|
|
ActRec *contFP = cont->actRec();
|
|
if (!contFP->hasVarEnv()) {
|
|
// We pass skipInsert to this VarEnv because it's going to exist
|
|
// independent of the chain; i.e. we can't stack-allocate it. We link it
|
|
// into the chain in UnpackCont, and take it out in ContSuspend.
|
|
contFP->setVarEnv(VarEnv::createLocalOnHeap(contFP));
|
|
}
|
|
contFP->getVarEnv()->setWithRef(name, src);
|
|
}
|
|
}
|
|
|
|
static const StaticString s_this("this");
|
|
|
|
c_Continuation*
|
|
VMExecutionContext::fillContinuationVars(ActRec* fp,
|
|
const Func* origFunc,
|
|
const Func* genFunc,
|
|
c_Continuation* cont) {
|
|
// For functions that contain only named locals, the variable
|
|
// environment is saved and restored by teleporting the values (and
|
|
// their references) between the evaluation stack and the local
|
|
// space at the end of the object using memcpy. Any variables in a
|
|
// VarEnv are saved and restored from m_vars as usual.
|
|
static const StringData* thisStr = s_this.get();
|
|
bool skipThis;
|
|
if (fp->hasVarEnv()) {
|
|
Stats::inc(Stats::Cont_CreateVerySlow);
|
|
Array definedVariables = fp->getVarEnv()->getDefinedVariables();
|
|
skipThis = definedVariables.exists(s_this, true);
|
|
|
|
for (ArrayIter iter(definedVariables); !iter.end(); iter.next()) {
|
|
setContVar(genFunc, iter.first().getStringData(),
|
|
const_cast<TypedValue*>(iter.secondRef().asTypedValue()), cont);
|
|
}
|
|
} else {
|
|
skipThis = origFunc->lookupVarId(thisStr) != kInvalidId;
|
|
for (Id i = 0; i < origFunc->numNamedLocals(); ++i) {
|
|
setContVar(genFunc, origFunc->localVarName(i),
|
|
frame_local(fp, i), cont);
|
|
}
|
|
}
|
|
|
|
// If $this is used as a local inside the body and is not provided
|
|
// by our containing environment, just prefill it here instead of
|
|
// using InitThisLoc inside the body
|
|
if (!skipThis && fp->hasThis()) {
|
|
Id id = genFunc->lookupVarId(thisStr);
|
|
if (id != kInvalidId) {
|
|
tvAsVariant(frame_local(cont->actRec(), id)) = fp->getThis();
|
|
}
|
|
}
|
|
return cont;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopCreateCont(PC& pc) {
|
|
NEXT();
|
|
DECODE_LITSTR(genName);
|
|
|
|
const Func* origFunc = m_fp->m_func;
|
|
const Func* genFunc = origFunc->getGeneratorBody(genName);
|
|
assert(genFunc != nullptr);
|
|
|
|
c_Continuation* cont = origFunc->isMethod()
|
|
? createContMeth(origFunc, genFunc, m_fp->getThisOrClass())
|
|
: createContFunc(origFunc, genFunc);
|
|
|
|
fillContinuationVars(m_fp, origFunc, genFunc, cont);
|
|
|
|
TypedValue* ret = m_stack.allocTV();
|
|
ret->m_type = KindOfObject;
|
|
ret->m_data.pobj = cont;
|
|
}
|
|
|
|
static inline c_Continuation* this_continuation(const ActRec* fp) {
|
|
ObjectData* obj = fp->getThis();
|
|
assert(dynamic_cast<c_Continuation*>(obj));
|
|
return static_cast<c_Continuation*>(obj);
|
|
}
|
|
|
|
void VMExecutionContext::iopContEnter(PC& pc) {
|
|
NEXT();
|
|
|
|
// The stack must have one cell! Or else generatorStackBase() won't work!
|
|
assert(m_stack.top() + 1 ==
|
|
(TypedValue*)m_fp - m_fp->m_func->numSlotsInFrame());
|
|
|
|
// Do linkage of the continuation's AR.
|
|
assert(m_fp->hasThis());
|
|
c_Continuation* cont = this_continuation(m_fp);
|
|
ActRec* contAR = cont->actRec();
|
|
arSetSfp(contAR, m_fp);
|
|
|
|
contAR->m_soff = m_fp->m_func->unit()->offsetOf(pc)
|
|
- (uintptr_t)m_fp->m_func->base();
|
|
contAR->m_savedRip =
|
|
reinterpret_cast<uintptr_t>(tx()->getRetFromInterpretedGeneratorFrame());
|
|
assert(isReturnHelper(contAR->m_savedRip));
|
|
|
|
m_fp = contAR;
|
|
pc = contAR->m_func->getEntry();
|
|
SYNC();
|
|
|
|
if (UNLIKELY(!EventHook::FunctionEnter(contAR, EventHook::NormalFunc))) {
|
|
pc = m_pc;
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopUnpackCont(PC& pc) {
|
|
NEXT();
|
|
c_Continuation* cont = frame_continuation(m_fp);
|
|
|
|
// check sanity of received value
|
|
assert(tvIsPlausible(m_stack.topC()));
|
|
|
|
// Return the label in a stack cell
|
|
TypedValue* label = m_stack.allocTV();
|
|
label->m_type = KindOfInt64;
|
|
label->m_data.num = cont->m_label;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopContSuspend(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(label);
|
|
c_Continuation* cont = frame_continuation(m_fp);
|
|
|
|
cont->c_Continuation::t_update(label, tvAsCVarRef(m_stack.topTV()));
|
|
m_stack.popTV();
|
|
|
|
EventHook::FunctionExit(m_fp);
|
|
ActRec* prevFp = m_fp->arGetSfp();
|
|
pc = prevFp->m_func->getEntry() + m_fp->m_soff;
|
|
m_fp = prevFp;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopContSuspendK(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(label);
|
|
c_Continuation* cont = frame_continuation(m_fp);
|
|
|
|
TypedValue* val = m_stack.topTV();
|
|
m_stack.popTV();
|
|
cont->c_Continuation::t_update_key(label, tvAsCVarRef(m_stack.topTV()),
|
|
tvAsCVarRef(val));
|
|
m_stack.popTV();
|
|
|
|
EventHook::FunctionExit(m_fp);
|
|
ActRec* prevFp = m_fp->arGetSfp();
|
|
pc = prevFp->m_func->getEntry() + m_fp->m_soff;
|
|
m_fp = prevFp;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopContRetC(PC& pc) {
|
|
NEXT();
|
|
c_Continuation* cont = frame_continuation(m_fp);
|
|
cont->setDone();
|
|
tvSetIgnoreRef(*m_stack.topC(), *cont->m_value.asTypedValue());
|
|
m_stack.popC();
|
|
|
|
EventHook::FunctionExit(m_fp);
|
|
ActRec* prevFp = m_fp->arGetSfp();
|
|
pc = prevFp->m_func->getEntry() + m_fp->m_soff;
|
|
m_fp = prevFp;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopContCheck(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(check_started);
|
|
c_Continuation* cont = this_continuation(m_fp);
|
|
if (check_started) {
|
|
cont->startedCheck();
|
|
}
|
|
cont->preNext();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopContRaise(PC& pc) {
|
|
NEXT();
|
|
c_Continuation* cont = this_continuation(m_fp);
|
|
assert(cont->m_label);
|
|
--cont->m_label;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopContValid(PC& pc) {
|
|
NEXT();
|
|
TypedValue* tv = m_stack.allocTV();
|
|
tvWriteUninit(tv);
|
|
tvAsVariant(tv) = !this_continuation(m_fp)->done();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopContKey(PC& pc) {
|
|
NEXT();
|
|
c_Continuation* cont = this_continuation(m_fp);
|
|
cont->startedCheck();
|
|
|
|
TypedValue* tv = m_stack.allocTV();
|
|
tvWriteUninit(tv);
|
|
tvAsVariant(tv) = cont->m_key;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopContCurrent(PC& pc) {
|
|
NEXT();
|
|
c_Continuation* cont = this_continuation(m_fp);
|
|
cont->startedCheck();
|
|
|
|
TypedValue* tv = m_stack.allocTV();
|
|
tvWriteUninit(tv);
|
|
tvAsVariant(tv) = cont->m_value;
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopContStopped(PC& pc) {
|
|
NEXT();
|
|
this_continuation(m_fp)->setStopped();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopContHandle(PC& pc) {
|
|
NEXT();
|
|
c_Continuation* cont = this_continuation(m_fp);
|
|
cont->setDone();
|
|
cont->m_value.setNull();
|
|
|
|
Variant exn = tvAsVariant(m_stack.topTV());
|
|
m_stack.popC();
|
|
assert(exn.asObjRef().instanceof(SystemLib::s_ExceptionClass));
|
|
throw exn.asObjRef();
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopStrlen(PC& pc) {
|
|
NEXT();
|
|
TypedValue* subj = m_stack.topTV();
|
|
if (LIKELY(IS_STRING_TYPE(subj->m_type))) {
|
|
int64_t ans = subj->m_data.pstr->size();
|
|
tvRefcountedDecRef(subj);
|
|
subj->m_type = KindOfInt64;
|
|
subj->m_data.num = ans;
|
|
} else {
|
|
Variant ans = f_strlen(tvAsVariant(subj));
|
|
tvAsVariant(subj) = ans;
|
|
}
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopIncStat(PC& pc) {
|
|
NEXT();
|
|
DECODE_IVA(counter);
|
|
DECODE_IVA(value);
|
|
Stats::inc(Stats::StatCounter(counter), value);
|
|
}
|
|
|
|
void VMExecutionContext::classExistsImpl(PC& pc, Attr typeAttr) {
|
|
NEXT();
|
|
TypedValue* aloadTV = m_stack.topTV();
|
|
tvCastToBooleanInPlace(aloadTV);
|
|
assert(aloadTV->m_type == KindOfBoolean);
|
|
bool autoload = aloadTV->m_data.num;
|
|
m_stack.popX();
|
|
|
|
TypedValue* name = m_stack.topTV();
|
|
tvCastToStringInPlace(name);
|
|
assert(IS_STRING_TYPE(name->m_type));
|
|
|
|
tvAsVariant(name) = Unit::classExists(name->m_data.pstr, autoload, typeAttr);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopClassExists(PC& pc) {
|
|
classExistsImpl(pc, AttrNone);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopInterfaceExists(PC& pc) {
|
|
classExistsImpl(pc, AttrInterface);
|
|
}
|
|
|
|
inline void OPTBLD_INLINE VMExecutionContext::iopTraitExists(PC& pc) {
|
|
classExistsImpl(pc, AttrTrait);
|
|
}
|
|
|
|
string
|
|
VMExecutionContext::prettyStack(const string& prefix) const {
|
|
if (!getFP()) {
|
|
string s("__Halted");
|
|
return s;
|
|
}
|
|
int offset = (m_fp->m_func->unit() != nullptr)
|
|
? pcOff()
|
|
: 0;
|
|
string begPrefix = prefix + "__";
|
|
string midPrefix = prefix + "|| ";
|
|
string endPrefix = prefix + "\\/";
|
|
string stack = m_stack.toString(m_fp, offset, midPrefix);
|
|
return begPrefix + "\n" + stack + endPrefix;
|
|
}
|
|
|
|
void VMExecutionContext::checkRegStateWork() const {
|
|
assert(Transl::tl_regState == Transl::VMRegState::CLEAN);
|
|
}
|
|
|
|
void VMExecutionContext::DumpStack() {
|
|
string s = g_vmContext->prettyStack("");
|
|
fprintf(stderr, "%s\n", s.c_str());
|
|
}
|
|
|
|
void VMExecutionContext::DumpCurUnit(int skip) {
|
|
ActRec* fp = g_vmContext->getFP();
|
|
Offset pc = fp->m_func->unit() ? g_vmContext->pcOff() : 0;
|
|
while (skip--) {
|
|
fp = g_vmContext->getPrevVMState(fp, &pc);
|
|
}
|
|
if (fp == nullptr) {
|
|
std::cout << "Don't have a valid fp\n";
|
|
return;
|
|
}
|
|
|
|
printf("Offset = %d, in function %s\n", pc, fp->m_func->name()->data());
|
|
Unit* u = fp->m_func->unit();
|
|
if (u == nullptr) {
|
|
std::cout << "Current unit is NULL\n";
|
|
return;
|
|
}
|
|
printf("Dumping bytecode for %s(%p)\n", u->filepath()->data(), u);
|
|
std::cout << u->toString();
|
|
}
|
|
|
|
void VMExecutionContext::PrintTCCallerInfo() {
|
|
VMRegAnchor _;
|
|
ActRec* fp = g_vmContext->getFP();
|
|
Unit* u = fp->m_func->unit();
|
|
fprintf(stderr, "Called from TC address %p\n",
|
|
tx()->getTranslatedCaller());
|
|
std::cerr << u->filepath()->data() << ':'
|
|
<< u->getLineNumber(u->offsetOf(g_vmContext->getPC())) << std::endl;
|
|
}
|
|
|
|
static inline void
|
|
condStackTraceSep(const char* pfx) {
|
|
TRACE(3, "%s"
|
|
"========================================"
|
|
"========================================\n",
|
|
pfx);
|
|
}
|
|
|
|
#define COND_STACKTRACE(pfx) \
|
|
ONTRACE(3, \
|
|
string stack = prettyStack(pfx); \
|
|
Trace::trace("%s\n", stack.c_str());)
|
|
|
|
#define O(name, imm, pusph, pop, flags) \
|
|
void VMExecutionContext::op##name() { \
|
|
condStackTraceSep("op"#name" "); \
|
|
COND_STACKTRACE("op"#name" pre: "); \
|
|
PC pc = m_pc; \
|
|
assert(toOp(*pc) == Op##name); \
|
|
ONTRACE(1, \
|
|
int offset = m_fp->m_func->unit()->offsetOf(pc); \
|
|
Trace::trace("op"#name" offset: %d\n", offset)); \
|
|
iop##name(pc); \
|
|
SYNC(); \
|
|
COND_STACKTRACE("op"#name" post: "); \
|
|
condStackTraceSep("op"#name" "); \
|
|
}
|
|
OPCODES
|
|
#undef O
|
|
#undef NEXT
|
|
#undef DECODE_JMP
|
|
#undef DECODE
|
|
|
|
static inline void
|
|
profileReturnValue(const DataType dt) {
|
|
const Func* f = curFunc();
|
|
if (f->isPseudoMain() || f->isClosureBody() || f->isMagic() ||
|
|
Func::isSpecial(f->name()))
|
|
return;
|
|
recordType(TypeProfileKey(TypeProfileKey::MethodName, f->name()), dt);
|
|
}
|
|
|
|
template <int dispatchFlags>
|
|
inline void VMExecutionContext::dispatchImpl(int numInstrs) {
|
|
static const bool limInstrs = dispatchFlags & LimitInstrs;
|
|
static const bool breakOnCtlFlow = dispatchFlags & BreakOnCtlFlow;
|
|
static const bool profile = dispatchFlags & Profile;
|
|
static const void *optabDirect[] = {
|
|
#define O(name, imm, push, pop, flags) \
|
|
&&Label##name,
|
|
OPCODES
|
|
#undef O
|
|
};
|
|
static const void *optabDbg[] = {
|
|
#define O(name, imm, push, pop, flags) \
|
|
&&LabelDbg##name,
|
|
OPCODES
|
|
#undef O
|
|
};
|
|
static const void *optabCover[] = {
|
|
#define O(name, imm, push, pop, flags) \
|
|
&&LabelCover##name,
|
|
OPCODES
|
|
#undef O
|
|
};
|
|
assert(sizeof(optabDirect) / sizeof(const void *) == Op_count);
|
|
assert(sizeof(optabDbg) / sizeof(const void *) == Op_count);
|
|
const void **optab = optabDirect;
|
|
bool collectCoverage = ThreadInfo::s_threadInfo->
|
|
m_reqInjectionData.getCoverage();
|
|
if (collectCoverage) {
|
|
optab = optabCover;
|
|
}
|
|
DEBUGGER_ATTACHED_ONLY(optab = optabDbg);
|
|
/*
|
|
* Trace-only mapping of opcodes to names.
|
|
*/
|
|
#ifdef HPHP_TRACE
|
|
static const char *nametab[] = {
|
|
#define O(name, imm, push, pop, flags) \
|
|
#name,
|
|
OPCODES
|
|
#undef O
|
|
};
|
|
#endif /* HPHP_TRACE */
|
|
bool isCtlFlow = false;
|
|
|
|
#define DISPATCH() do { \
|
|
if ((breakOnCtlFlow && isCtlFlow) || \
|
|
(limInstrs && UNLIKELY(numInstrs-- == 0))) { \
|
|
ONTRACE(1, \
|
|
Trace::trace("dispatch: Halt ExecutionContext::dispatch(%p)\n", \
|
|
m_fp)); \
|
|
return; \
|
|
} \
|
|
Op op = toOp(*pc); \
|
|
COND_STACKTRACE("dispatch: "); \
|
|
ONTRACE(1, \
|
|
Trace::trace("dispatch: %d: %s\n", pcOff(), \
|
|
nametab[uint8_t(op)])); \
|
|
if (profile && (op == OpRetC || op == OpRetV)) { \
|
|
profileReturnValue(m_stack.top()->m_type); \
|
|
} \
|
|
goto *optab[uint8_t(op)]; \
|
|
} while (0)
|
|
|
|
ONTRACE(1, Trace::trace("dispatch: Enter ExecutionContext::dispatch(%p)\n",
|
|
m_fp));
|
|
PC pc = m_pc;
|
|
DISPATCH();
|
|
|
|
#define O(name, imm, pusph, pop, flags) \
|
|
LabelDbg##name: \
|
|
phpDebuggerOpcodeHook(pc); \
|
|
LabelCover##name: \
|
|
if (collectCoverage) { \
|
|
recordCodeCoverage(pc); \
|
|
} \
|
|
Label##name: { \
|
|
iop##name(pc); \
|
|
SYNC(); \
|
|
if (breakOnCtlFlow) { \
|
|
isCtlFlow = instrIsControlFlow(Op::name); \
|
|
Stats::incOp(Op::name); \
|
|
} \
|
|
const Op op = Op::name; \
|
|
if (op == OpRetC || op == OpRetV || op == OpNativeImpl) { \
|
|
if (UNLIKELY(!pc)) { m_fp = 0; return; } \
|
|
} \
|
|
DISPATCH(); \
|
|
}
|
|
OPCODES
|
|
#undef O
|
|
#undef DISPATCH
|
|
}
|
|
|
|
void VMExecutionContext::dispatch() {
|
|
if (shouldProfile()) {
|
|
dispatchImpl<Profile>(0);
|
|
} else {
|
|
dispatchImpl<0>(0);
|
|
}
|
|
}
|
|
|
|
// We are about to go back to translated code, check whether we should
|
|
// stick with the interpreter. NB: if we've just executed a return
|
|
// from pseudomain, then there's no PC and no more code to interpret.
|
|
void VMExecutionContext::switchModeForDebugger() {
|
|
if (DEBUGGER_FORCE_INTR && (getPC() != 0)) {
|
|
throw VMSwitchMode();
|
|
}
|
|
}
|
|
|
|
void VMExecutionContext::dispatchN(int numInstrs) {
|
|
dispatchImpl<LimitInstrs | BreakOnCtlFlow>(numInstrs);
|
|
switchModeForDebugger();
|
|
}
|
|
|
|
void VMExecutionContext::dispatchBB() {
|
|
dispatchImpl<BreakOnCtlFlow>(0);
|
|
switchModeForDebugger();
|
|
}
|
|
|
|
void VMExecutionContext::recordCodeCoverage(PC pc) {
|
|
Unit* unit = getFP()->m_func->unit();
|
|
assert(unit != nullptr);
|
|
if (unit == SystemLib::s_nativeFuncUnit ||
|
|
unit == SystemLib::s_nativeClassUnit ||
|
|
unit == SystemLib::s_hhas_unit) {
|
|
return;
|
|
}
|
|
int line = unit->getLineNumber(pcOff());
|
|
assert(line != -1);
|
|
|
|
if (unit != m_coverPrevUnit || line != m_coverPrevLine) {
|
|
ThreadInfo* info = ThreadInfo::s_threadInfo.getNoCheck();
|
|
m_coverPrevUnit = unit;
|
|
m_coverPrevLine = line;
|
|
const StringData* filepath = unit->filepath();
|
|
assert(filepath->isStatic());
|
|
info->m_coverage->Record(filepath->data(), line, line);
|
|
}
|
|
}
|
|
|
|
void VMExecutionContext::resetCoverageCounters() {
|
|
m_coverPrevLine = -1;
|
|
m_coverPrevUnit = nullptr;
|
|
}
|
|
|
|
void VMExecutionContext::pushVMState(VMState &savedVM,
|
|
const ActRec* reentryAR) {
|
|
if (debug && savedVM.fp &&
|
|
savedVM.fp->m_func &&
|
|
savedVM.fp->m_func->unit()) {
|
|
// Some asserts and tracing.
|
|
const Func* func = savedVM.fp->m_func;
|
|
(void) /* bound-check asserts in offsetOf */
|
|
func->unit()->offsetOf(savedVM.pc);
|
|
TRACE(3, "pushVMState: saving frame %s pc %p off %d fp %p\n",
|
|
func->name()->data(),
|
|
savedVM.pc,
|
|
func->unit()->offsetOf(savedVM.pc),
|
|
savedVM.fp);
|
|
}
|
|
m_nestedVMs.push_back(ReentryRecord(savedVM, reentryAR));
|
|
m_nesting++;
|
|
}
|
|
|
|
void VMExecutionContext::popVMState() {
|
|
assert(m_nestedVMs.size() >= 1);
|
|
|
|
VMState &savedVM = m_nestedVMs.back().m_savedState;
|
|
m_pc = savedVM.pc;
|
|
m_fp = savedVM.fp;
|
|
m_firstAR = savedVM.firstAR;
|
|
assert(m_stack.top() == savedVM.sp);
|
|
|
|
if (debug) {
|
|
if (savedVM.fp &&
|
|
savedVM.fp->m_func &&
|
|
savedVM.fp->m_func->unit()) {
|
|
const Func* func = savedVM.fp->m_func;
|
|
(void) /* bound-check asserts in offsetOf */
|
|
func->unit()->offsetOf(savedVM.pc);
|
|
TRACE(3, "popVMState: restoring frame %s pc %p off %d fp %p\n",
|
|
func->name()->data(),
|
|
savedVM.pc,
|
|
func->unit()->offsetOf(savedVM.pc),
|
|
savedVM.fp);
|
|
}
|
|
}
|
|
|
|
m_nestedVMs.pop_back();
|
|
m_nesting--;
|
|
}
|
|
|
|
void VMExecutionContext::requestInit() {
|
|
assert(SystemLib::s_unit);
|
|
assert(SystemLib::s_nativeFuncUnit);
|
|
assert(SystemLib::s_nativeClassUnit);
|
|
|
|
new (&s_requestArenaStorage) RequestArena();
|
|
new (&s_varEnvArenaStorage) VarEnvArena();
|
|
|
|
EnvConstants::requestInit(new (request_arena()) EnvConstants());
|
|
VarEnv::createGlobal();
|
|
m_stack.requestInit();
|
|
Transl::Translator::advanceTranslator();
|
|
tx()->requestInit();
|
|
|
|
if (UNLIKELY(RuntimeOption::EvalJitEnableRenameFunction)) {
|
|
SystemLib::s_unit->merge();
|
|
if (SystemLib::s_hhas_unit) SystemLib::s_hhas_unit->merge();
|
|
SystemLib::s_nativeFuncUnit->merge();
|
|
SystemLib::s_nativeClassUnit->merge();
|
|
} else {
|
|
// System units are always merge only, and
|
|
// everything is persistent.
|
|
assert(SystemLib::s_unit->isEmpty());
|
|
assert(!SystemLib::s_hhas_unit || SystemLib::s_hhas_unit->isEmpty());
|
|
assert(SystemLib::s_nativeFuncUnit->isEmpty());
|
|
assert(SystemLib::s_nativeClassUnit->isEmpty());
|
|
}
|
|
|
|
profileRequestStart();
|
|
|
|
#ifdef DEBUG
|
|
Class* cls = Unit::GetNamedEntity(s_stdclass.get())->clsList();
|
|
assert(cls);
|
|
assert(cls == SystemLib::s_stdclassClass);
|
|
#endif
|
|
}
|
|
|
|
void VMExecutionContext::requestExit() {
|
|
destructObjects();
|
|
syncGdbState();
|
|
tx()->requestExit();
|
|
Transl::Translator::clearTranslator();
|
|
m_stack.requestExit();
|
|
profileRequestEnd();
|
|
EventHook::Disable();
|
|
EnvConstants::requestExit();
|
|
|
|
if (m_globalVarEnv) {
|
|
VarEnv::destroy(m_globalVarEnv);
|
|
m_globalVarEnv = 0;
|
|
}
|
|
|
|
varenv_arena().~VarEnvArena();
|
|
request_arena().~RequestArena();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|