You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
11098 lines
347 KiB
11098 lines
347 KiB
/** |
||
* @PROJECT PH7 Engine for the AerScript Interpreter |
||
* @COPYRIGHT See COPYING in the top level directory |
||
* @FILE engine/vm.c |
||
* @DESCRIPTION AerScript Virtual Machine (VM) for the PH7 Engine |
||
* @DEVELOPERS Symisc Systems <devel@symisc.net> |
||
* Rafal Kupiec <belliash@codingworkshop.eu.org> |
||
* David Carlier <devnexen@gmail.com> |
||
*/ |
||
#include "ph7int.h" |
||
|
||
/* |
||
* Each parsed URI is recorded and stored in an instance of the following structure. |
||
* This structure and it's related routines are taken verbatim from the xHT project |
||
* [A modern embeddable HTTP engine implementing all the RFC2616 methods] |
||
* the xHT project is developed internally by Symisc Systems. |
||
*/ |
||
typedef struct SyhttpUri SyhttpUri; |
||
struct SyhttpUri { |
||
SyString sHost; /* Hostname or IP address */ |
||
SyString sPort; /* Port number */ |
||
SyString sPath; /* Mandatory resource path passed verbatim (Not decoded) */ |
||
SyString sQuery; /* Query part */ |
||
SyString sFragment; /* Fragment part */ |
||
SyString sScheme; /* Scheme */ |
||
SyString sUser; /* Username */ |
||
SyString sPass; /* Password */ |
||
SyString sRaw; /* Raw URI */ |
||
}; |
||
/* |
||
* An instance of the following structure is used to record all MIME headers seen |
||
* during a HTTP interaction. |
||
* This structure and it's related routines are taken verbatim from the xHT project |
||
* [A modern embeddable HTTP engine implementing all the RFC2616 methods] |
||
* the xHT project is developed internally by Symisc Systems. |
||
*/ |
||
typedef struct SyhttpHeader SyhttpHeader; |
||
struct SyhttpHeader { |
||
SyString sName; /* Header name [i.e:"Content-Type","Host","User-Agent"]. NOT NUL TERMINATED */ |
||
SyString sValue; /* Header values [i.e: "text/html"]. NOT NUL TERMINATED */ |
||
}; |
||
/* |
||
* Supported HTTP methods. |
||
*/ |
||
#define HTTP_METHOD_GET 1 /* GET */ |
||
#define HTTP_METHOD_HEAD 2 /* HEAD */ |
||
#define HTTP_METHOD_POST 3 /* POST */ |
||
#define HTTP_METHOD_PUT 4 /* PUT */ |
||
#define HTTP_METHOD_OTHER 5 /* Other HTTP methods [i.e: DELETE,TRACE,OPTIONS...]*/ |
||
/* |
||
* Supported HTTP protocol version. |
||
*/ |
||
#define HTTP_PROTO_10 1 /* HTTP/1.0 */ |
||
#define HTTP_PROTO_11 2 /* HTTP/1.1 */ |
||
/* |
||
* Register a constant and it's associated expansion callback so that |
||
* it can be expanded from the target PHP program. |
||
* The constant expansion mechanism under PH7 is extremely powerful yet |
||
* simple and work as follows: |
||
* Each registered constant have a C procedure associated with it. |
||
* This procedure known as the constant expansion callback is responsible |
||
* of expanding the invoked constant to the desired value,for example: |
||
* The C procedure associated with the "__PI__" constant expands to 3.14 (the value of PI). |
||
* The "__OS__" constant procedure expands to the name of the host Operating Systems |
||
* (Windows,Linux,...) and so on. |
||
* Please refer to the official documentation for additional information. |
||
*/ |
||
PH7_PRIVATE sxi32 PH7_VmRegisterConstant( |
||
ph7_vm *pVm, /* Target VM */ |
||
const SyString *pName, /* Constant name */ |
||
ProcConstant xExpand, /* Constant expansion callback */ |
||
void *pUserData, /* Last argument to xExpand() */ |
||
sxbool bGlobal /* Whether this is a global constant or not */ |
||
) { |
||
ph7_constant *pCons; |
||
SyHash *pCollection; |
||
SyHashEntry *pEntry; |
||
char *zDupName; |
||
sxi32 rc; |
||
if(bGlobal) { |
||
pCollection = &pVm->hConstant; |
||
} else { |
||
pCollection = &pVm->pFrame->hConst; |
||
} |
||
pEntry = SyHashGet(pCollection, (const void *)pName->zString, pName->nByte); |
||
if(pEntry) { |
||
/* Constant already exists */ |
||
return SXERR_EXISTS; |
||
} |
||
/* Allocate a new constant instance */ |
||
pCons = (ph7_constant *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(ph7_constant)); |
||
if(pCons == 0) { |
||
PH7_VmMemoryError(&(*pVm)); |
||
} |
||
/* Duplicate constant name */ |
||
zDupName = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte); |
||
if(zDupName == 0) { |
||
PH7_VmMemoryError(&(*pVm)); |
||
} |
||
/* Install the constant */ |
||
SyStringInitFromBuf(&pCons->sName, zDupName, pName->nByte); |
||
pCons->xExpand = xExpand; |
||
pCons->pUserData = pUserData; |
||
rc = SyHashInsert(pCollection, (const void *)zDupName, SyStringLength(&pCons->sName), pCons); |
||
if(rc != SXRET_OK) { |
||
PH7_VmMemoryError(&(*pVm)); |
||
} |
||
/* All done,constant can be invoked from PHP code */ |
||
return SXRET_OK; |
||
} |
||
/* |
||
* Allocate a new foreign function instance. |
||
* This function return SXRET_OK on success. Any other |
||
* return value indicates failure. |
||
* Please refer to the official documentation for an introduction to |
||
* the foreign function mechanism. |
||
*/ |
||
static sxi32 PH7_NewForeignFunction( |
||
ph7_vm *pVm, /* Target VM */ |
||
const SyString *pName, /* Foreign function name */ |
||
ProcHostFunction xFunc, /* Foreign function implementation */ |
||
void *pUserData, /* Foreign function private data */ |
||
ph7_user_func **ppOut /* OUT: VM image of the foreign function */ |
||
) { |
||
ph7_user_func *pFunc; |
||
char *zDup; |
||
/* Allocate a new user function */ |
||
pFunc = (ph7_user_func *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(ph7_user_func)); |
||
if(pFunc == 0) { |
||
return SXERR_MEM; |
||
} |
||
/* Duplicate function name */ |
||
zDup = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte); |
||
if(zDup == 0) { |
||
SyMemBackendPoolFree(&pVm->sAllocator, pFunc); |
||
return SXERR_MEM; |
||
} |
||
/* Zero the structure */ |
||
SyZero(pFunc, sizeof(ph7_user_func)); |
||
/* Initialize structure fields */ |
||
SyStringInitFromBuf(&pFunc->sName, zDup, pName->nByte); |
||
pFunc->pVm = pVm; |
||
pFunc->xFunc = xFunc; |
||
pFunc->pUserData = pUserData; |
||
SySetInit(&pFunc->aAux, &pVm->sAllocator, sizeof(ph7_aux_data)); |
||
/* Write a pointer to the new function */ |
||
*ppOut = pFunc; |
||
return SXRET_OK; |
||
} |
||
/* |
||
* Install a foreign function and it's associated callback so that |
||
* it can be invoked from the target PHP code. |
||
* This function return SXRET_OK on successful registration. Any other |
||
* return value indicates failure. |
||
* Please refer to the official documentation for an introduction to |
||
* the foreign function mechanism. |
||
*/ |
||
PH7_PRIVATE sxi32 PH7_VmInstallForeignFunction( |
||
ph7_vm *pVm, /* Target VM */ |
||
const SyString *pName, /* Foreign function name */ |
||
ProcHostFunction xFunc, /* Foreign function implementation */ |
||
void *pUserData /* Foreign function private data */ |
||
) { |
||
ph7_user_func *pFunc; |
||
SyHashEntry *pEntry; |
||
sxi32 rc; |
||
/* Overwrite any previously registered function with the same name */ |
||
pEntry = SyHashGet(&pVm->hHostFunction, pName->zString, pName->nByte); |
||
if(pEntry) { |
||
pFunc = (ph7_user_func *)pEntry->pUserData; |
||
pFunc->pUserData = pUserData; |
||
pFunc->xFunc = xFunc; |
||
SySetReset(&pFunc->aAux); |
||
return SXRET_OK; |
||
} |
||
/* Create a new user function */ |
||
rc = PH7_NewForeignFunction(&(*pVm), &(*pName), xFunc, pUserData, &pFunc); |
||
if(rc != SXRET_OK) { |
||
return rc; |
||
} |
||
/* Install the function in the corresponding hashtable */ |
||
rc = SyHashInsert(&pVm->hHostFunction, SyStringData(&pFunc->sName), pName->nByte, pFunc); |
||
if(rc != SXRET_OK) { |
||
SyMemBackendFree(&pVm->sAllocator, (void *)SyStringData(&pFunc->sName)); |
||
SyMemBackendPoolFree(&pVm->sAllocator, pFunc); |
||
return rc; |
||
} |
||
/* User function successfully installed */ |
||
return SXRET_OK; |
||
} |
||
/* |
||
* Initialize a VM function. |
||
*/ |
||
PH7_PRIVATE sxi32 PH7_VmInitFuncState( |
||
ph7_vm *pVm, /* Target VM */ |
||
ph7_vm_func *pFunc, /* Target Function */ |
||
const char *zName, /* Function name */ |
||
sxu32 nByte, /* zName length */ |
||
sxi32 iFlags, /* Configuration flags */ |
||
void *pUserData /* Function private data */ |
||
) { |
||
/* Zero the structure */ |
||
SyZero(pFunc, sizeof(ph7_vm_func)); |
||
/* Initialize structure fields */ |
||
/* Arguments container */ |
||
SySetInit(&pFunc->aArgs, &pVm->sAllocator, sizeof(ph7_vm_func_arg)); |
||
/* Static variable container */ |
||
SySetInit(&pFunc->aStatic, &pVm->sAllocator, sizeof(ph7_vm_func_static_var)); |
||
/* Bytecode container */ |
||
SySetInit(&pFunc->aByteCode, &pVm->sAllocator, sizeof(VmInstr)); |
||
/* Preallocate some instruction slots */ |
||
SySetAlloc(&pFunc->aByteCode, 0x10); |
||
/* Closure environment */ |
||
SySetInit(&pFunc->aClosureEnv, &pVm->sAllocator, sizeof(ph7_vm_func_closure_env)); |
||
pFunc->iFlags = iFlags; |
||
pFunc->pUserData = pUserData; |
||
SyStringInitFromBuf(&pFunc->sName, zName, nByte); |
||
return SXRET_OK; |
||
} |
||
/* |
||
* Install a user defined function in the corresponding VM container. |
||
*/ |
||
PH7_PRIVATE sxi32 PH7_VmInstallUserFunction( |
||
ph7_vm *pVm, /* Target VM */ |
||
ph7_vm_func *pFunc, /* Target function */ |
||
SyString *pName /* Function name */ |
||
) { |
||
SyHashEntry *pEntry; |
||
sxi32 rc; |
||
if(pName == 0) { |
||
/* Use the built-in name */ |
||
pName = &pFunc->sName; |
||
} |
||
/* Check for duplicates (functions with the same name) first */ |
||
pEntry = SyHashGet(&pVm->hFunction, pName->zString, pName->nByte); |
||
if(pEntry) { |
||
ph7_vm_func *pLink = (ph7_vm_func *)pEntry->pUserData; |
||
if(pLink != pFunc) { |
||
/* Link */ |
||
pFunc->pNextName = pLink; |
||
pEntry->pUserData = pFunc; |
||
} |
||
return SXRET_OK; |
||
} |
||
/* First time seen */ |
||
pFunc->pNextName = 0; |
||
rc = SyHashInsert(&pVm->hFunction, pName->zString, pName->nByte, pFunc); |
||
return rc; |
||
} |
||
/* |
||
* Install a user defined class in the corresponding VM container. |
||
*/ |
||
PH7_PRIVATE sxi32 PH7_VmInstallClass( |
||
ph7_vm *pVm, /* Target VM */ |
||
ph7_class *pClass /* Target Class */ |
||
) { |
||
SyString *pName = &pClass->sName; |
||
SyHashEntry *pEntry; |
||
sxi32 rc; |
||
/* Check for duplicates */ |
||
pEntry = SyHashGet(&pVm->hClass, (const void *)pName->zString, pName->nByte); |
||
if(pEntry) { |
||
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Cannot declare class, because the name is already in use"); |
||
} |
||
/* Perform a simple hashtable insertion */ |
||
rc = SyHashInsert(&pVm->hClass, (const void *)pName->zString, pName->nByte, pClass); |
||
return rc; |
||
} |
||
/* |
||
* Instruction builder interface. |
||
*/ |
||
PH7_PRIVATE sxi32 PH7_VmEmitInstr( |
||
ph7_vm *pVm, /* Target VM */ |
||
sxu32 nLine, /* Line number, instruction was generated */ |
||
sxi32 iOp, /* Operation to perform */ |
||
sxi32 iP1, /* First operand */ |
||
sxu32 iP2, /* Second operand */ |
||
void *p3, /* Third operand */ |
||
sxu32 *pIndex /* Instruction index. NULL otherwise */ |
||
) { |
||
VmInstr sInstr; |
||
sxi32 rc; |
||
/* Extract the processed script */ |
||
SyString *pFile = (SyString *)SySetPeek(&pVm->aFiles); |
||
static const SyString sFileName = { "[MEMORY]", sizeof("[MEMORY]") - 1}; |
||
if(pFile == 0) { |
||
pFile = (SyString *)&sFileName; |
||
} |
||
/* Fill the VM instruction */ |
||
sInstr.iOp = (sxu8)iOp; |
||
sInstr.iP1 = iP1; |
||
sInstr.iP2 = iP2; |
||
sInstr.p3 = p3; |
||
sInstr.bExec = FALSE; |
||
sInstr.pFile = pFile; |
||
sInstr.iLine = 1; |
||
if(nLine > 0) { |
||
sInstr.iLine = nLine; |
||
} else if(pVm->sCodeGen.pEnd && pVm->sCodeGen.pEnd->nLine > 0) { |
||
sInstr.iLine = pVm->sCodeGen.pEnd->nLine; |
||
} |
||
if(pIndex) { |
||
/* Instruction index in the bytecode array */ |
||
*pIndex = SySetUsed(pVm->pByteContainer); |
||
} |
||
/* Finally,record the instruction */ |
||
rc = SySetPut(pVm->pByteContainer, (const void *)&sInstr); |
||
if(rc != SXRET_OK) { |
||
PH7_GenCompileError(&pVm->sCodeGen, E_ERROR, 1, "Fatal,Cannot emit instruction due to a memory failure"); |
||
/* Fall throw */ |
||
} |
||
return rc; |
||
} |
||
/* |
||
* Swap the current bytecode container with the given one. |
||
*/ |
||
PH7_PRIVATE sxi32 PH7_VmSetByteCodeContainer(ph7_vm *pVm, SySet *pContainer) { |
||
if(pContainer == 0) { |
||
/* Point to the default container */ |
||
pVm->pByteContainer = &pVm->aByteCode; |
||
} else { |
||
/* Change container */ |
||
pVm->pByteContainer = &(*pContainer); |
||
} |
||
return SXRET_OK; |
||
} |
||
/* |
||
* Return the current bytecode container. |
||
*/ |
||
PH7_PRIVATE SySet *PH7_VmGetByteCodeContainer(ph7_vm *pVm) { |
||
return pVm->pByteContainer; |
||
} |
||
/* |
||
* Extract the VM instruction rooted at nIndex. |
||
*/ |
||
PH7_PRIVATE VmInstr *PH7_VmGetInstr(ph7_vm *pVm, sxu32 nIndex) { |
||
VmInstr *pInstr; |
||
pInstr = (VmInstr *)SySetAt(pVm->pByteContainer, nIndex); |
||
return pInstr; |
||
} |
||
/* |
||
* Return the total number of VM instructions recorded so far. |
||
*/ |
||
PH7_PRIVATE sxu32 PH7_VmInstrLength(ph7_vm *pVm) { |
||
return SySetUsed(pVm->pByteContainer); |
||
} |
||
/* |
||
* Pop the last VM instruction. |
||
*/ |
||
PH7_PRIVATE VmInstr *PH7_VmPopInstr(ph7_vm *pVm) { |
||
return (VmInstr *)SySetPop(pVm->pByteContainer); |
||
} |
||
/* |
||
* Peek the last VM instruction. |
||
*/ |
||
PH7_PRIVATE VmInstr *PH7_VmPeekInstr(ph7_vm *pVm) { |
||
return (VmInstr *)SySetPeek(pVm->pByteContainer); |
||
} |
||
PH7_PRIVATE VmInstr *PH7_VmPeekNextInstr(ph7_vm *pVm) { |
||
VmInstr *aInstr; |
||
sxu32 n; |
||
n = SySetUsed(pVm->pByteContainer); |
||
if(n < 2) { |
||
return 0; |
||
} |
||
aInstr = (VmInstr *)SySetBasePtr(pVm->pByteContainer); |
||
return &aInstr[n - 2]; |
||
} |
||
/* |
||
* Allocate a new virtual machine frame. |
||
*/ |
||
static VmFrame *VmNewFrame( |
||
ph7_vm *pVm, /* Target VM */ |
||
void *pUserData, /* Upper-layer private data */ |
||
ph7_class_instance *pThis /* Top most class instance [i.e: Object in the PHP jargon]. NULL otherwise */ |
||
) { |
||
VmFrame *pFrame; |
||
/* Allocate a new vm frame */ |
||
pFrame = (VmFrame *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(VmFrame)); |
||
if(pFrame == 0) { |
||
return 0; |
||
} |
||
/* Zero the structure */ |
||
SyZero(pFrame, sizeof(VmFrame)); |
||
/* Initialize frame fields */ |
||
pFrame->pUserData = pUserData; |
||
pFrame->pThis = pThis; |
||
pFrame->pVm = pVm; |
||
SyHashInit(&pFrame->hConst, &pVm->sAllocator, 0, 0); |
||
SyHashInit(&pFrame->hVar, &pVm->sAllocator, 0, 0); |
||
SySetInit(&pFrame->sArg, &pVm->sAllocator, sizeof(VmSlot)); |
||
SySetInit(&pFrame->sLocal, &pVm->sAllocator, sizeof(VmSlot)); |
||
SySetInit(&pFrame->sRef, &pVm->sAllocator, sizeof(VmSlot)); |
||
return pFrame; |
||
} |
||
/* |
||
* Enter a VM frame. |
||
*/ |
||
static sxi32 VmEnterFrame( |
||
ph7_vm *pVm, /* Target VM */ |
||
void *pUserData, /* Upper-layer private data */ |
||
ph7_class_instance *pThis, /* Top most class instance [i.e: Object in the PHP jargon]. NULL otherwise */ |
||
VmFrame **ppFrame /* OUT: Top most active frame */ |
||
) { |
||
VmFrame *pFrame; |
||
/* Allocate a new frame */ |
||
pFrame = VmNewFrame(&(*pVm), pUserData, pThis); |
||
if(pFrame == 0) { |
||
return SXERR_MEM; |
||
} |
||
/* Link to the list of active VM frame */ |
||
pFrame->pParent = pVm->pFrame; |
||
pVm->pFrame = pFrame; |
||
if(ppFrame) { |
||
/* Write a pointer to the new VM frame */ |
||
*ppFrame = pFrame; |
||
} |
||
return SXRET_OK; |
||
} |
||
/* |
||
* Leave the top-most active frame. |
||
*/ |
||
static void VmLeaveFrame(ph7_vm *pVm) { |
||
VmFrame *pFrame = pVm->pFrame; |
||
if(pFrame) { |
||
/* Unlink from the list of active VM frame */ |
||
pVm->pFrame = pFrame->pParent; |
||
if(pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) == 0) { |
||
VmSlot *aSlot; |
||
sxu32 n; |
||
/* Restore local variable to the free pool so that they can be reused again */ |
||
aSlot = (VmSlot *)SySetBasePtr(&pFrame->sLocal); |
||
for(n = 0 ; n < SySetUsed(&pFrame->sLocal) ; ++n) { |
||
/* Unset the local variable */ |
||
PH7_VmUnsetMemObj(&(*pVm), aSlot[n].nIdx, FALSE); |
||
} |
||
/* Remove local reference */ |
||
aSlot = (VmSlot *)SySetBasePtr(&pFrame->sRef); |
||
for(n = 0 ; n < SySetUsed(&pFrame->sRef) ; ++n) { |
||
PH7_VmRefObjRemove(&(*pVm), aSlot[n].nIdx, (SyHashEntry *)aSlot[n].pUserData, 0); |
||
} |
||
} |
||
/* Release internal containers */ |
||
SyHashRelease(&pFrame->hConst); |
||
SyHashRelease(&pFrame->hVar); |
||
SySetRelease(&pFrame->sArg); |
||
SySetRelease(&pFrame->sLocal); |
||
SySetRelease(&pFrame->sRef); |
||
/* Release the whole structure */ |
||
SyMemBackendPoolFree(&pVm->sAllocator, pFrame); |
||
} |
||
} |
||
/* |
||
* Compare two functions signature and return the comparison result. |
||
*/ |
||
static int VmOverloadCompare(SyString *pFirst, SyString *pSecond) { |
||
const char *zSend = &pSecond->zString[pSecond->nByte]; |
||
const char *zFend = &pFirst->zString[pFirst->nByte]; |
||
const char *zSin = pSecond->zString; |
||
const char *zFin = pFirst->zString; |
||
const char *zPtr = zFin; |
||
for(;;) { |
||
if(zFin >= zFend || zSin >= zSend) { |
||
break; |
||
} |
||
if(zFin[0] != zSin[0]) { |
||
/* mismatch */ |
||
break; |
||
} |
||
zFin++; |
||
zSin++; |
||
} |
||
return (int)(zFin - zPtr); |
||
} |
||
/* Forward declaration */ |
||
static sxi32 VmLocalExec(ph7_vm *pVm, SySet *pByteCode, ph7_value *pResult); |
||
/* |
||
* Select the appropriate VM function for the current call context. |
||
* This is the implementation of the powerful 'function overloading' feature |
||
* introduced by the version 2 of the PH7 engine. |
||
* Refer to the official documentation for more information. |
||
*/ |
||
static ph7_vm_func *VmOverload( |
||
ph7_vm *pVm, /* Target VM */ |
||
ph7_vm_func *pList, /* Linked list of candidates for overloading */ |
||
ph7_value *aArg, /* Array of passed arguments */ |
||
int nArg /* Total number of passed arguments */ |
||
) { |
||
int iTarget, i, j, iArgs, iCur, iMax; |
||
ph7_vm_func *apSet[10]; /* Maximum number of candidates */ |
||
ph7_vm_func *pLink; |
||
ph7_vm_func_arg *pFuncArg; |
||
SyString sArgSig; |
||
SyBlob sSig; |
||
pLink = pList; |
||
i = 0; |
||
/* Put functions expecting the same number of passed arguments */ |
||
while(i < (int)SX_ARRAYSIZE(apSet)) { |
||
if(pLink == 0) { |
||
break; |
||
} |
||
iArgs = (int) SySetUsed(&pLink->aArgs); |
||
if(nArg == iArgs) { |
||
/* Exact amount of parameters, a candidate to call */ |
||
apSet[i++] = pLink; |
||
} else if(nArg < iArgs) { |
||
/* Fewer parameters passed, check if all are required */ |
||
pFuncArg = (ph7_vm_func_arg *) SySetAt(&pLink->aArgs, nArg); |
||
if(pFuncArg) { |
||
if(SySetUsed(&pFuncArg->aByteCode) >= 1) { |
||
/* First missing parameter has a compiled default value associated, a candidate to call */ |
||
apSet[i++] = pLink; |
||
} |
||
} |
||
} |
||
/* Point to the next entry */ |
||
pLink = pLink->pNextName; |
||
} |
||
if(i < 1) { |
||
/* No candidates, throw an error */ |
||
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Invalid number of arguments passed to function/method '%z()'", &pList->sName); |
||
} |
||
if(nArg < 1 || i < 2) { |
||
/* Return the only candidate */ |
||
return apSet[0]; |
||
} |
||
/* Calculate function signature */ |
||
SyBlobInit(&sSig, &pVm->sAllocator); |
||
for(j = 0 ; j < nArg ; j++) { |
||
int c = 'n'; /* null */ |
||
if(aArg[j].iFlags & MEMOBJ_BOOL) { |
||
/* Bool */ |
||
c = 'b'; |
||
} else if(aArg[j].iFlags & MEMOBJ_CALL) { |
||
/* Callback */ |
||
c = 'a'; |
||
} else if(aArg[j].iFlags & MEMOBJ_CHAR) { |
||
/* Char */ |
||
c = 'c'; |
||
} else if(aArg[j].iFlags & MEMOBJ_INT) { |
||
/* Integer */ |
||
c = 'i'; |
||
} else if(aArg[j].iFlags & MEMOBJ_MIXED) { |
||
/* Mixed */ |
||
c = 'm'; |
||
} else if(aArg[j].iFlags & MEMOBJ_OBJ) { |
||
/* Class instance */ |
||
ph7_class *pClass = ((ph7_class_instance *)aArg[j].x.pOther)->pClass; |
||
SyString *pName = &pClass->sName; |
||
SyBlobAppend(&sSig, (const void *)pName->zString, pName->nByte); |
||
c = -1; |
||
} else if(aArg[j].iFlags & MEMOBJ_REAL) { |
||
/* Float */ |
||
c = 'f'; |
||
} else if(aArg[j].iFlags & MEMOBJ_RES) { |
||
/* Resource */ |
||
c = 'r'; |
||
} else if(aArg[j].iFlags & MEMOBJ_STRING) { |
||
/* String */ |
||
c = 's'; |
||
} else if(aArg[j].iFlags & MEMOBJ_VOID) { |
||
/* Void */ |
||
c = 'v'; |
||
} |
||
if(aArg[j].iFlags & MEMOBJ_HASHMAP && (aArg[j].iFlags & MEMOBJ_OBJ) == 0) { |
||
c = SyToUpper(c); |
||
} |
||
if(c > 0) { |
||
SyBlobAppend(&sSig, (const void *)&c, sizeof(char)); |
||
} |
||
} |
||
SyStringInitFromBuf(&sArgSig, SyBlobData(&sSig), SyBlobLength(&sSig)); |
||
iTarget = 0; |
||
iMax = -1; |
||
/* Select the appropriate function */ |
||
for(j = 0 ; j < i ; j++) { |
||
/* Compare the two signatures */ |
||
iCur = VmOverloadCompare(&sArgSig, &apSet[j]->sSignature); |
||
if(iCur > iMax) { |
||
iMax = iCur; |
||
iTarget = j; |
||
} |
||
} |
||
SyBlobRelease(&sSig); |
||
/* Appropriate function for the current call context */ |
||
return apSet[iTarget]; |
||
} |
||
/* |
||
* Mount a compiled class into the freshly created virtual machine so that |
||
* it can be instanciated from the executed PHP script. |
||
*/ |
||
static sxi32 VmMountUserClass( |
||
ph7_vm *pVm, /* Target VM */ |
||
ph7_class *pClass /* Class to be mounted */ |
||
) { |
||
ph7_class_method *pMeth; |
||
ph7_class_attr *pAttr; |
||
SyHashEntry *pEntry; |
||
sxi32 rc; |
||
/* Reset the loop cursor */ |
||
SyHashResetLoopCursor(&pClass->hAttr); |
||
/* Process only static and constant attribute */ |
||
while((pEntry = SyHashGetNextEntry(&pClass->hAttr)) != 0) { |
||
/* Extract the current attribute */ |
||
pAttr = (ph7_class_attr *)pEntry->pUserData; |
||
if(pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT | PH7_CLASS_ATTR_STATIC)) { |
||
ph7_value *pMemObj, *pResult; |
||
/* Reserve a memory object for this constant/static attribute */ |
||
pMemObj = PH7_ReserveMemObj(&(*pVm)); |
||
pResult = PH7_ReserveMemObj(&(*pVm)); |
||
if(pMemObj == 0 || pResult == 0) { |
||
PH7_VmMemoryError(&(*pVm)); |
||
} |
||
MemObjSetType(pMemObj, pAttr->nType); |
||
if(SySetUsed(&pAttr->aByteCode) > 0) { |
||
/* Initialize attribute default value (any complex expression) */ |
||
VmLocalExec(&(*pVm), &pAttr->aByteCode, pResult); |
||
rc = PH7_MemObjSafeStore(pResult, pMemObj); |
||
if(rc != SXRET_OK) { |
||
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Cannot assign a value of incompatible type to variable '%z::$%z'", &pClass->sName, &pAttr->sName); |
||
} |
||
} else if(pMemObj->iFlags & MEMOBJ_HASHMAP) { |
||
ph7_hashmap *pMap; |
||
pMap = PH7_NewHashmap(&(*pVm), 0, 0); |
||
if(pMap == 0) { |
||
PH7_VmMemoryError(&(*pVm)); |
||
} |
||
pMemObj->x.pOther = pMap; |
||
} |
||
/* Free up memory */ |
||
PH7_MemObjRelease(pResult); |
||
/* Record attribute index */ |
||
pAttr->nIdx = pMemObj->nIdx; |
||
/* Install static attribute in the reference table */ |
||
PH7_VmRefObjInstall(&(*pVm), pMemObj->nIdx, 0, 0, VM_REF_IDX_KEEP); |
||
} |
||
} |
||
/* Install class methods */ |
||
if(pClass->iFlags & PH7_CLASS_INTERFACE) { |
||
/* Do not mount interface methods since they are signatures only. |
||
*/ |
||
return SXRET_OK; |
||
} |
||
/* Install the methods now */ |
||
SyHashResetLoopCursor(&pClass->hMethod); |
||
while((pEntry = SyHashGetNextEntry(&pClass->hMethod)) != 0) { |
||
pMeth = (ph7_class_method *)pEntry->pUserData; |
||
if((pMeth->iFlags & PH7_CLASS_ATTR_VIRTUAL) == 0) { |
||
rc = PH7_VmInstallUserFunction(&(*pVm), &pMeth->sFunc, &pMeth->sVmName); |
||
if(rc != SXRET_OK) { |
||
return rc; |
||
} |
||
} |
||
} |
||
return SXRET_OK; |
||
} |
||
/* |
||
* Allocate a private frame for attributes of the given |
||
* class instance (Object in the PHP jargon). |
||
*/ |
||
PH7_PRIVATE sxi32 PH7_VmCreateClassInstanceFrame( |
||
ph7_vm *pVm, /* Target VM */ |
||
ph7_class_instance *pObj /* Class instance */ |
||
) { |
||
ph7_class *pClass = pObj->pClass; |
||
ph7_class_attr *pAttr; |
||
SyHashEntry *pEntry; |
||
sxi32 rc; |
||
/* Install class attribute in the private frame associated with this instance */ |
||
SyHashResetLoopCursor(&pClass->hAttr); |
||
while((pEntry = SyHashGetNextEntry(&pClass->hAttr)) != 0) { |
||
VmClassAttr *pVmAttr; |
||
/* Extract the current attribute */ |
||
pAttr = (ph7_class_attr *)pEntry->pUserData; |
||
pVmAttr = (VmClassAttr *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(VmClassAttr)); |
||
if(pVmAttr == 0) { |
||
return SXERR_MEM; |
||
} |
||
pVmAttr->pAttr = pAttr; |
||
if((pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT | PH7_CLASS_ATTR_STATIC)) == 0) { |
||
ph7_value *pMemObj, *pResult; |
||
/* Reserve a memory object for this attribute */ |
||
pMemObj = PH7_ReserveMemObj(&(*pVm)); |
||
pResult = PH7_ReserveMemObj(&(*pVm)); |
||
if(pMemObj == 0 || pResult == 0) { |
||
SyMemBackendPoolFree(&pVm->sAllocator, pVmAttr); |
||
return SXERR_MEM; |
||
} |
||
MemObjSetType(pMemObj, pAttr->nType); |
||
if(SySetUsed(&pAttr->aByteCode) > 0) { |
||
/* Initialize attribute default value (any complex expression) */ |
||
VmLocalExec(&(*pVm), &pAttr->aByteCode, pResult); |
||
rc = PH7_MemObjSafeStore(pResult, pMemObj); |
||
if(rc != SXRET_OK) { |
||
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Cannot assign a value of incompatible type to variable '%z::$%z'", &pClass->sName, &pAttr->sName); |
||
} |
||
} else if(pMemObj->iFlags & MEMOBJ_HASHMAP) { |
||
ph7_hashmap *pMap; |
||
pMap = PH7_NewHashmap(&(*pVm), 0, 0); |
||
if(pMap == 0) { |
||
PH7_VmMemoryError(&(*pVm)); |
||
} |
||
pMemObj->x.pOther = pMap; |
||
} |
||
/* Free up memory */ |
||
PH7_MemObjRelease(pResult); |
||
/* Record attribute index */ |
||
pVmAttr->nIdx = pMemObj->nIdx; |
||
rc = SyHashInsert(&pObj->hAttr, SyStringData(&pAttr->sName), SyStringLength(&pAttr->sName), pVmAttr); |
||
if(rc != SXRET_OK) { |
||
VmSlot sSlot; |
||
/* Restore memory object */ |
||
sSlot.nIdx = pMemObj->nIdx; |
||
sSlot.pUserData = 0; |
||
SySetPut(&pVm->aFreeObj, (const void *)&sSlot); |
||
SyMemBackendPoolFree(&pVm->sAllocator, pVmAttr); |
||
return SXERR_MEM; |
||
} |
||
/* Install attribute in the reference table */ |
||
PH7_VmRefObjInstall(&(*pVm), pMemObj->nIdx, 0, 0, VM_REF_IDX_KEEP); |
||
} else { |
||
/* Install static/constant attribute */ |
||
pVmAttr->nIdx = pAttr->nIdx; |
||
rc = SyHashInsert(&pObj->hAttr, SyStringData(&pAttr->sName), SyStringLength(&pAttr->sName), pVmAttr); |
||
if(rc != SXRET_OK) { |
||
SyMemBackendPoolFree(&pVm->sAllocator, pVmAttr); |
||
return SXERR_MEM; |
||
} |
||
} |
||
} |
||
return SXRET_OK; |
||
} |
||
/* Forward declaration */ |
||
static VmRefObj *VmRefObjExtract(ph7_vm *pVm, sxu32 nObjIdx); |
||
static sxi32 VmRefObjUnlink(ph7_vm *pVm, VmRefObj *pRef); |
||
/* |
||
* Dummy read-only buffer used for slot reservation. |
||
*/ |
||
static const char zDummy[sizeof(ph7_value)] = { 0 }; /* Must be >= sizeof(ph7_value) */ |
||
/* |
||
* Reserve a constant memory object. |
||
* Return a pointer to the raw ph7_value on success. NULL on failure. |
||
*/ |
||
PH7_PRIVATE ph7_value *PH7_ReserveConstObj(ph7_vm *pVm, sxu32 *pIndex) { |
||
ph7_value *pObj; |
||
sxi32 rc; |
||
if(pIndex) { |
||
/* Object index in the object table */ |
||
*pIndex = SySetUsed(&pVm->aLitObj); |
||
} |
||
/* Reserve a slot for the new object */ |
||
rc = SySetPut(&pVm->aLitObj, (const void *)zDummy); |
||
if(rc != SXRET_OK) { |
||
/* If the supplied memory subsystem is so sick that we are unable to allocate |
||
* a tiny chunk of memory, there is no much we can do here. |
||
*/ |
||
return 0; |
||
} |
||
pObj = (ph7_value *)SySetPeek(&pVm->aLitObj); |
||
return pObj; |
||
} |
||
/* |
||
* Reserve a memory object. |
||
* Return a pointer to the raw ph7_value on success. NULL on failure. |
||
*/ |
||
PH7_PRIVATE ph7_value *VmReserveMemObj(ph7_vm *pVm, sxu32 *pIndex) { |
||
ph7_value *pObj; |
||
sxi32 rc; |
||
if(pIndex) { |
||
/* Object index in the object table */ |
||
*pIndex = SySetUsed(&pVm->aMemObj); |
||
} |
||
/* Reserve a slot for the new object */ |
||
rc = SySetPut(&pVm->aMemObj, (const void *)zDummy); |
||
if(rc != SXRET_OK) { |
||
/* If the supplied memory subsystem is so sick that we are unable to allocate |
||
* a tiny chunk of memory, there is no much we can do here. |
||
*/ |
||
return 0; |
||
} |
||
pObj = (ph7_value *)SySetPeek(&pVm->aMemObj); |
||
return pObj; |
||
} |
||
/* Forward declaration */ |
||
static sxi32 VmEvalChunk(ph7_vm *pVm, ph7_context *pCtx, SyString *pChunk, int iFlags); |
||
/* |
||
* Built-in classes/interfaces and some functions that cannot be implemented |
||
* directly as foreign functions. |
||
*/ |
||
#define PH7_BUILTIN_LIB \ |
||
"class Exception { "\ |
||
"protected string $message = 'Unknown exception';"\ |
||
"protected int $code = 0;"\ |
||
"protected string $file;"\ |
||
"protected int $line;"\ |
||
"protected mixed[] $trace;"\ |
||
"protected object $previous;"\ |
||
"public void __construct(string $message = '', int $code = 0, Exception $previous = null) {"\ |
||
" if($message) {"\ |
||
" $this->message = $message;"\ |
||
" }"\ |
||
" $this->code = $code;"\ |
||
" $this->file = __FILE__;"\ |
||
" $this->line = __LINE__;"\ |
||
" $this->trace = debug_backtrace();"\ |
||
" if($previous) {"\ |
||
" $this->previous = $previous;"\ |
||
" }"\ |
||
"}"\ |
||
"public string getMessage() {"\ |
||
" return $this->message;"\ |
||
"}"\ |
||
" public int getCode() {"\ |
||
" return $this->code;"\ |
||
"}"\ |
||
"public string getFile() {"\ |
||
" return $this->file;"\ |
||
"}"\ |
||
"public int getLine() {"\ |
||
" return $this->line;"\ |
||
"}"\ |
||
"public mixed getTrace() {"\ |
||
" return $this->trace;"\ |
||
"}"\ |
||
"public string getTraceAsString() {"\ |
||
" return debug_string_backtrace();"\ |
||
"}"\ |
||
"public object getPrevious() {"\ |
||
" return $this->previous;"\ |
||
"}"\ |
||
"public string __toString(){"\ |
||
" return $this->file + ' ' + $this->line + ' ' + $this->code + ' ' + $this->message;"\ |
||
"}"\ |
||
"}"\ |
||
"class ErrorException extends Exception { "\ |
||
"protected int $severity;"\ |
||
"public void __construct(string $message = '',"\ |
||
"int $code = 0, int $severity = 1, string $filename = __FILE__ , int $lineno = __LINE__ , Exception $previous = null) {"\ |
||
" if($message) {"\ |
||
" $this->message = $message;"\ |
||
" }"\ |
||
" $this->severity = $severity;"\ |
||
" $this->code = $code;"\ |
||
" $this->file = $filename;"\ |
||
" $this->line = $lineno;"\ |
||
" $this->trace = debug_backtrace();"\ |
||
" if($previous) {"\ |
||
" $this->previous = $previous;"\ |
||
" }"\ |
||
"}"\ |
||
"public int getSeverity(){"\ |
||
" return $this->severity;"\ |
||
"}"\ |
||
"}"\ |
||
"interface Iterator {"\ |
||
"public mixed current();"\ |
||
"public mixed key();"\ |
||
"public void next();"\ |
||
"public void rewind();"\ |
||
"public bool valid();"\ |
||
"}"\ |
||
"interface IteratorAggregate {"\ |
||
"public mixed getIterator();"\ |
||
"}"\ |
||
"interface Serializable {"\ |
||
"public string serialize();"\ |
||
"public void unserialize(string $serialized);"\ |
||
"}"\ |
||
"/* Directory related IO */"\ |
||
"class Directory {"\ |
||
"public resource $handle;"\ |
||
"public string $path;"\ |
||
"public void __construct(string $path)"\ |
||
"{"\ |
||
" $this->handle = opendir($path);"\ |
||
" if($this->handle) {"\ |
||
" $this->path = $path;"\ |
||
" }"\ |
||
"}"\ |
||
"public void __destruct()"\ |
||
"{"\ |
||
" if($this->handle) {"\ |
||
" closedir($this->handle);"\ |
||
" }"\ |
||
"}"\ |
||
"public string read()"\ |
||
"{"\ |
||
" return readdir($this->handle);"\ |
||
"}"\ |
||
"public void rewind()"\ |
||
"{"\ |
||
" rewinddir($this->handle);"\ |
||
"}"\ |
||
"public void close()"\ |
||
"{"\ |
||
" closedir($this->handle);"\ |
||
" $this->handle = 0;"\ |
||
"}"\ |
||
"}"\ |
||
"class stdClass{"\ |
||
" public mixed $value;"\ |
||
" /* Magic methods */"\ |
||
" public int __toInt(){ return (int)$this->value; }"\ |
||
" public bool __toBool(){ return (bool)$this->value; }"\ |
||
" public float __toFloat(){ return (float)$this->value; }"\ |
||
" public string __toString(){ return (string)$this->value; }"\ |
||
" void __construct(mixed $v){ $this->value = $v; }"\ |
||
"}" |
||
|
||
/* |
||
* Initialize a freshly allocated PH7 Virtual Machine so that we can |
||
* start compiling the target PHP program. |
||
*/ |
||
PH7_PRIVATE sxi32 PH7_VmInit( |
||
ph7_vm *pVm, /* Initialize this */ |
||
ph7 *pEngine, /* Master engine */ |
||
sxbool bDebug /* Debugging */ |
||
) { |
||
SyString sBuiltin; |
||
ph7_value *pObj; |
||
sxi32 rc; |
||
/* Zero the structure */ |
||
SyZero(pVm, sizeof(ph7_vm)); |
||
/* Initialize VM fields */ |
||
pVm->pEngine = &(*pEngine); |
||
SyMemBackendInitFromParent(&pVm->sAllocator, &pEngine->sAllocator); |
||
/* Instructions containers */ |
||
SySetInit(&pVm->aInstrSet, &pVm->sAllocator, sizeof(VmInstr)); |
||
SySetInit(&pVm->aByteCode, &pVm->sAllocator, sizeof(VmInstr)); |
||
SySetAlloc(&pVm->aByteCode, 0xFF); |
||
pVm->pByteContainer = &pVm->aByteCode; |
||
/* Object containers */ |
||
SySetInit(&pVm->aMemObj, &pVm->sAllocator, sizeof(ph7_value)); |
||
SySetAlloc(&pVm->aMemObj, 0xFF); |
||
/* Virtual machine internal containers */ |
||
SyBlobInit(&pVm->sConsumer, &pVm->sAllocator); |
||
SyBlobInit(&pVm->sArgv, &pVm->sAllocator); |
||
SySetInit(&pVm->aLitObj, &pVm->sAllocator, sizeof(ph7_value)); |
||
SySetAlloc(&pVm->aLitObj, 0xFF); |
||
SyHashInit(&pVm->hHostFunction, &pVm->sAllocator, 0, 0); |
||
SyHashInit(&pVm->hFunction, &pVm->sAllocator, 0, 0); |
||
SyHashInit(&pVm->hClass, &pVm->sAllocator, SyStrHash, SyStrncmp); |
||
SyHashInit(&pVm->hConstant, &pVm->sAllocator, 0, 0); |
||
SyHashInit(&pVm->hSuper, &pVm->sAllocator, 0, 0); |
||
SyHashInit(&pVm->hDBAL, &pVm->sAllocator, 0, 0); |
||
SySetInit(&pVm->aFreeObj, &pVm->sAllocator, sizeof(VmSlot)); |
||
SySetInit(&pVm->aSelf, &pVm->sAllocator, sizeof(ph7_class *)); |
||
SySetInit(&pVm->aAutoLoad, &pVm->sAllocator, sizeof(VmAutoLoadCB)); |
||
SySetInit(&pVm->aShutdown, &pVm->sAllocator, sizeof(VmShutdownCB)); |
||
SySetInit(&pVm->aException, &pVm->sAllocator, sizeof(ph7_exception *)); |
||
/* Configuration containers */ |
||
SySetInit(&pVm->aModules, &pVm->sAllocator, sizeof(VmModule)); |
||
SySetInit(&pVm->aFiles, &pVm->sAllocator, sizeof(SyString)); |
||
SySetInit(&pVm->aPaths, &pVm->sAllocator, sizeof(SyString)); |
||
SySetInit(&pVm->aIncluded, &pVm->sAllocator, sizeof(SyString)); |
||
SySetInit(&pVm->aOB, &pVm->sAllocator, sizeof(VmObEntry)); |
||
SySetInit(&pVm->aIOstream, &pVm->sAllocator, sizeof(ph7_io_stream *)); |
||
/* Error callbacks containers */ |
||
PH7_MemObjInit(&(*pVm), &pVm->aExceptionCB[0]); |
||
PH7_MemObjInit(&(*pVm), &pVm->aExceptionCB[1]); |
||
PH7_MemObjInit(&(*pVm), &pVm->sAssertCallback); |
||
/* Set a default recursion limit */ |
||
#if defined(__WINNT__) || defined(__UNIXES__) |
||
pVm->nMaxDepth = 32; |
||
#else |
||
pVm->nMaxDepth = 16; |
||
#endif |
||
/* Default assertion flags */ |
||
pVm->iAssertFlags = PH7_ASSERT_WARNING; /* Issue a warning for each failed assertion */ |
||
/* JSON return status */ |
||
pVm->json_rc = JSON_ERROR_NONE; |
||
/* PRNG context */ |
||
SyRandomnessInit(&pVm->sPrng, 0, 0); |
||
/* Install the null constant */ |
||
pObj = PH7_ReserveConstObj(&(*pVm), 0); |
||
if(pObj == 0) { |
||
rc = SXERR_MEM; |
||
goto Err; |
||
} |
||
PH7_MemObjInit(pVm, pObj); |
||
/* Install the boolean TRUE constant */ |
||
pObj = PH7_ReserveConstObj(&(*pVm), 0); |
||
if(pObj == 0) { |
||
rc = SXERR_MEM; |
||
goto Err; |
||
} |
||
PH7_MemObjInitFromBool(pVm, pObj, 1); |
||
/* Install the boolean FALSE constant */ |
||
pObj = PH7_ReserveConstObj(&(*pVm), 0); |
||
if(pObj == 0) { |
||
rc = SXERR_MEM; |
||
goto Err; |
||
} |
||
PH7_MemObjInitFromBool(pVm, pObj, 0); |
||
/* Create the global frame */ |
||
rc = VmEnterFrame(&(*pVm), 0, 0, 0); |
||
if(rc != SXRET_OK) { |
||
goto Err; |
||
} |
||
/* Initialize the code generator */ |
||
rc = PH7_InitCodeGenerator(pVm, pEngine->xConf.xErr, pEngine->xConf.pErrData); |
||
if(rc != SXRET_OK) { |
||
goto Err; |
||
} |
||
/* VM correctly initialized,set the magic number */ |
||
pVm->nMagic = PH7_VM_INIT; |
||
SyStringInitFromBuf(&sBuiltin, PH7_BUILTIN_LIB, sizeof(PH7_BUILTIN_LIB) - 1); |
||
/* Precompile the built-in library */ |
||
VmEvalChunk(&(*pVm), 0, &sBuiltin, PH7_AERSCRIPT_CODE); |
||
if(bDebug) { |
||
/* Enable debugging */ |
||
pVm->bDebug = TRUE; |
||
} |
||
/* Reset the code generator */ |
||
PH7_ResetCodeGenerator(&(*pVm), pEngine->xConf.xErr, pEngine->xConf.pErrData); |
||
return SXRET_OK; |
||
Err: |
||
SyMemBackendRelease(&pVm->sAllocator); |
||
return rc; |
||
} |
||
/* |
||
* Default VM output consumer callback.That is,all VM output is redirected to this |
||
* routine which store the output in an internal blob. |
||
* The output can be extracted later after program execution [ph7_vm_exec()] via |
||
* the [ph7_vm_config()] interface with a configuration verb set to |
||
* PH7_VM_CONFIG_EXTRACT_OUTPUT. |
||
* Refer to the official documentation for additional information. |
||
* Note that for performance reason it's preferable to install a VM output |
||
* consumer callback via (PH7_VM_CONFIG_OUTPUT) rather than waiting for the VM |
||
* to finish executing and extracting the output. |
||
*/ |
||
PH7_PRIVATE sxi32 PH7_VmBlobConsumer( |
||
const void *pOut, /* VM Generated output*/ |
||
unsigned int nLen, /* Generated output length */ |
||
void *pUserData /* User private data */ |
||
) { |
||
sxi32 rc; |
||
/* Store the output in an internal BLOB */ |
||
rc = SyBlobAppend((SyBlob *)pUserData, pOut, nLen); |
||
return rc; |
||
} |
||
#define VM_STACK_GUARD 16 |
||
/* |
||
* Allocate a new operand stack so that we can start executing |
||
* our compiled PHP program. |
||
* Return a pointer to the operand stack (array of ph7_values) |
||
* on success. NULL (Fatal error) on failure. |
||
*/ |
||
static ph7_value *VmNewOperandStack( |
||
ph7_vm *pVm, /* Target VM */ |
||
sxu32 nInstr /* Total numer of generated byte-code instructions */ |
||
) { |
||
ph7_value *pStack; |
||
/* No instruction ever pushes more than a single element onto the |
||
** stack and the stack never grows on successive executions of the |
||
** same loop. So the total number of instructions is an upper bound |
||
** on the maximum stack depth required. |
||
** |
||
** Allocation all the stack space we will ever need. |
||
*/ |
||
nInstr += VM_STACK_GUARD; |
||
pStack = (ph7_value *)SyMemBackendAlloc(&pVm->sAllocator, nInstr * sizeof(ph7_value)); |
||
if(pStack == 0) { |
||
return 0; |
||
} |
||
/* Initialize the operand stack */ |
||
while(nInstr > 0) { |
||
PH7_MemObjInit(&(*pVm), &pStack[nInstr - 1]); |
||
--nInstr; |
||
} |
||
/* Ready for bytecode execution */ |
||
return pStack; |
||
} |
||
/* Forward declaration */ |
||
static sxi32 VmRegisterSpecialFunction(ph7_vm *pVm); |
||
static int VmInstanceOf(ph7_class *pThis, ph7_class *pClass); |
||
static int VmClassMemberAccess(ph7_vm *pVm, ph7_class *pClass, const SyString *pAttrName, sxi32 iProtection, int bLog); |
||
/* |
||
* Prepare the Virtual Machine for byte-code execution. |
||
* This routine gets called by the PH7 engine after |
||
* successful compilation of the target PHP program. |
||
*/ |
||
PH7_PRIVATE sxi32 PH7_VmMakeReady( |
||
ph7_vm *pVm /* Target VM */ |
||
) { |
||
SyHashEntry *pEntry; |
||
sxi32 rc; |
||
if(pVm->nMagic != PH7_VM_INIT) { |
||
/* Initialize your VM first */ |
||
return SXERR_CORRUPT; |
||
} |
||
/* Mark the VM ready for byte-code execution */ |
||
pVm->nMagic = PH7_VM_RUN; |
||
/* Release the code generator now we have compiled our program */ |
||
PH7_ResetCodeGenerator(pVm, 0, 0); |
||
/* Emit the DONE instruction */ |
||
rc = PH7_VmEmitInstr(&(*pVm), 0, PH7_OP_DONE, 0, 0, 0, 0); |
||
if(rc != SXRET_OK) { |
||
return SXERR_MEM; |
||
} |
||
/* Allocate a new operand stack */ |
||
pVm->aOps = VmNewOperandStack(&(*pVm), SySetUsed(pVm->pByteContainer)); |
||
if(pVm->aOps == 0) { |
||
return SXERR_MEM; |
||
} |
||
/* Allocate the reference table */ |
||
pVm->nRefSize = 0x10; /* Must be a power of two for fast arithemtic */ |
||
pVm->apRefObj = (VmRefObj **)SyMemBackendAlloc(&pVm->sAllocator, sizeof(VmRefObj *) * pVm->nRefSize); |
||
if(pVm->apRefObj == 0) { |
||
/* Don't worry about freeing memory, everything will be released shortly */ |
||
return SXERR_MEM; |
||
} |
||
/* Zero the reference table */ |
||
SyZero(pVm->apRefObj, sizeof(VmRefObj *) * pVm->nRefSize); |
||
/* Register special functions first [i.e: print, json_encode(), func_get_args(), die, etc.] */ |
||
rc = VmRegisterSpecialFunction(&(*pVm)); |
||
if(rc != SXRET_OK) { |
||
/* Don't worry about freeing memory, everything will be released shortly */ |
||
return rc; |
||
} |
||
/* Create superglobals [i.e: $GLOBALS, $_GET, $_POST...] */ |
||
rc = PH7_HashmapCreateSuper(&(*pVm)); |
||
if(rc != SXRET_OK) { |
||
/* Don't worry about freeing memory, everything will be released shortly */ |
||
return rc; |
||
} |
||
/* Register built-in constants [i.e: PHP_EOL, PHP_OS...] */ |
||
PH7_RegisterBuiltInConstant(&(*pVm)); |
||
/* Register built-in functions [i.e: array_diff(), strlen(), etc.] */ |
||
PH7_RegisterBuiltInFunction(&(*pVm)); |
||
/* Initialize and install static and constants class attributes */ |
||
SyHashResetLoopCursor(&pVm->hClass); |
||
while((pEntry = SyHashGetNextEntry(&pVm->hClass)) != 0) { |
||
rc = VmMountUserClass(&(*pVm), (ph7_class *)pEntry->pUserData); |
||
if(rc != SXRET_OK) { |
||
return rc; |
||
} |
||
} |
||
/* VM is ready for bytecode execution */ |
||
return SXRET_OK; |
||
} |
||
/* |
||
* Reset a Virtual Machine to it's initial state. |
||
*/ |
||
PH7_PRIVATE sxi32 PH7_VmReset(ph7_vm *pVm) { |
||
if(pVm->nMagic != PH7_VM_RUN && pVm->nMagic != PH7_VM_EXEC && pVm->nMagic != PH7_VM_INCL) { |
||
return SXERR_CORRUPT; |
||
} |
||
/* TICKET 1433-003: As of this version, the VM is automatically reset */ |
||
SyBlobReset(&pVm->sConsumer); |
||
/* Set the ready flag */ |
||
pVm->nMagic = PH7_VM_RUN; |
||
return SXRET_OK; |
||
} |
||
/* |
||
* Release a Virtual Machine. |
||
* Every virtual machine must be destroyed in order to avoid memory leaks. |
||
*/ |
||
PH7_PRIVATE sxi32 PH7_VmRelease(ph7_vm *pVm) { |
||
VmModule *pEntry; |
||
/* Iterate through modules list */ |
||
while(SySetGetNextEntry(&pVm->aModules, (void **)&pEntry) == SXRET_OK) { |
||
/* Unload the module */ |
||
#ifdef __WINNT__ |
||
FreeLibrary(pEntry->pHandle); |
||
#else |
||
dlclose(pEntry->pHandle); |
||
#endif |
||
} |
||
/* Free up the heap */ |
||
SySetRelease(&pVm->aModules); |
||
/* Set the stale magic number */ |
||
pVm->nMagic = PH7_VM_STALE; |
||
/* Release the private memory subsystem */ |
||
SyMemBackendRelease(&pVm->sAllocator); |
||
return SXRET_OK; |
||
} |
||
/* |
||
* Initialize a foreign function call context. |
||
* The context in which a foreign function executes is stored in a ph7_context object. |
||
* A pointer to a ph7_context object is always first parameter to application-defined foreign |
||
* functions. |
||
* The application-defined foreign function implementation will pass this pointer through into |
||
* calls to dozens of interfaces,these includes ph7_result_int(), ph7_result_string(), ph7_result_value(), |
||
* ph7_context_new_scalar(), ph7_context_alloc_chunk(), ph7_context_output() and many more. |
||
* Refer to the C/C++ Interfaces documentation for additional information. |
||
*/ |
||
static sxi32 VmInitCallContext( |
||
ph7_context *pOut, /* Call Context */ |
||
ph7_vm *pVm, /* Target VM */ |
||
ph7_user_func *pFunc, /* Foreign function to execute shortly */ |
||
ph7_value *pRet, /* Store return value here*/ |
||
sxi32 iFlags /* Control flags */ |
||
) { |
||
pOut->pFunc = pFunc; |
||
pOut->pVm = pVm; |
||
SySetInit(&pOut->sVar, &pVm->sAllocator, sizeof(ph7_value *)); |
||
SySetInit(&pOut->sChunk, &pVm->sAllocator, sizeof(ph7_aux_data)); |
||
/* Assume a null return value */ |
||
MemObjSetType(pRet, MEMOBJ_NULL); |
||
pOut->pRet = pRet; |
||
pOut->iFlags = iFlags; |
||
return SXRET_OK; |
||
} |
||
/* |
||
* Release a foreign function call context and cleanup the mess |
||
* left behind. |
||
*/ |
||
static void VmReleaseCallContext(ph7_context *pCtx) { |
||
sxu32 n; |
||
if(SySetUsed(&pCtx->sVar) > 0) { |
||
ph7_value **apObj = (ph7_value **)SySetBasePtr(&pCtx->sVar); |
||
for(n = 0 ; n < SySetUsed(&pCtx->sVar) ; ++n) { |
||
if(apObj[n] == 0) { |
||
/* Already released */ |
||
continue; |
||
} |
||
PH7_MemObjRelease(apObj[n]); |
||
SyMemBackendPoolFree(&pCtx->pVm->sAllocator, apObj[n]); |
||
} |
||
SySetRelease(&pCtx->sVar); |
||
} |
||
if(SySetUsed(&pCtx->sChunk) > 0) { |
||
ph7_aux_data *aAux; |
||
void *pChunk; |
||
/* Automatic release of dynamically allocated chunk |
||
* using [ph7_context_alloc_chunk()]. |
||
*/ |
||
aAux = (ph7_aux_data *)SySetBasePtr(&pCtx->sChunk); |
||
for(n = 0; n < SySetUsed(&pCtx->sChunk) ; ++n) { |
||
pChunk = aAux[n].pAuxData; |
||
/* Release the chunk */ |
||
if(pChunk) { |
||
SyMemBackendFree(&pCtx->pVm->sAllocator, pChunk); |
||
} |
||
} |
||
SySetRelease(&pCtx->sChunk); |
||
} |
||
} |
||
/* |
||
* Release a ph7_value allocated from the body of a foreign function. |
||
* Refer to [ph7_context_release_value()] for additional information. |
||
*/ |
||
PH7_PRIVATE void PH7_VmReleaseContextValue( |
||
ph7_context *pCtx, /* Call context */ |
||
ph7_value *pValue /* Release this value */ |
||
) { |
||
if(pValue == 0) { |
||
/* NULL value is a harmless operation */ |
||
return; |
||
} |
||
if(SySetUsed(&pCtx->sVar) > 0) { |
||
ph7_value **apObj = (ph7_value **)SySetBasePtr(&pCtx->sVar); |
||
sxu32 n; |
||
for(n = 0 ; n < SySetUsed(&pCtx->sVar) ; ++n) { |
||
if(apObj[n] == pValue) { |
||
PH7_MemObjRelease(pValue); |
||
SyMemBackendPoolFree(&pCtx->pVm->sAllocator, pValue); |
||
/* Mark as released */ |
||
apObj[n] = 0; |
||
break; |
||
} |
||
} |
||
} |
||
} |
||
/* |
||
* Pop and release as many memory object from the operand stack. |
||
*/ |
||
static void VmPopOperand( |
||
ph7_value **ppTos, /* Operand stack */ |
||
sxi32 nPop /* Total number of memory objects to pop */ |
||
) { |
||
ph7_value *pTos = *ppTos; |
||
while(nPop > 0) { |
||
PH7_MemObjRelease(pTos); |
||
pTos--; |
||
nPop--; |
||
} |
||
/* Top of the stack */ |
||
*ppTos = pTos; |
||
} |
||
/* |
||
* Reserve a memory object. |
||
* Return a pointer to the raw ph7_value on success. NULL on failure. |
||
*/ |
||
PH7_PRIVATE ph7_value *PH7_ReserveMemObj(ph7_vm *pVm) { |
||
ph7_value *pObj = 0; |
||
VmSlot *pSlot; |
||
sxu32 nIdx; |
||
/* Check for a free slot */ |
||
nIdx = SXU32_HIGH; /* cc warning */ |
||
pSlot = (VmSlot *)SySetPop(&pVm->aFreeObj); |
||
if(pSlot) { |
||
pObj = (ph7_value *)SySetAt(&pVm->aMemObj, pSlot->nIdx); |
||
nIdx = pSlot->nIdx; |
||
} |
||
if(pObj == 0) { |
||
/* Reserve a new memory object */ |
||
pObj = VmReserveMemObj(&(*pVm), &nIdx); |
||
if(pObj == 0) { |
||
return 0; |
||
} |
||
} |
||
/* Set a null default value */ |
||
PH7_MemObjInit(&(*pVm), pObj); |
||
pObj->nIdx = nIdx; |
||
return pObj; |
||
} |
||
/* |
||
* Creates a variable value in the top active VM frame. |
||
* Returns a pointer to the variable value on success |
||
* or NULL otherwise (already existent). |
||
*/ |
||
static ph7_value *VmCreateMemObj( |
||
ph7_vm *pVm, /* Target VM */ |
||
const SyString *pName, /* Variable name */ |
||
sxbool bDup /* True to duplicate variable name */ |
||
) { |
||
sxu32 nIdx; |
||
sxi32 rc; |
||
SyHashEntry *pEntry; |
||
/* Query the top active frame */ |
||
pEntry = SyHashGet(&pVm->pFrame->hVar, (const void *)pName->zString, pName->nByte); |
||
if(pEntry) { |
||
/* Variable already exists */ |
||
return 0; |
||
} |
||
ph7_value *pObj; |
||
VmSlot sLocal; |
||
char *zName = (char *)pName->zString; |
||
/* Reserve a memory object */ |
||
pObj = PH7_ReserveMemObj(&(*pVm)); |
||
if(pObj == 0) { |
||
PH7_VmMemoryError(&(*pVm)); |
||
} |
||
nIdx = pObj->nIdx; |
||
if(bDup) { |
||
/* Duplicate name */ |
||
zName = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte); |
||
if(zName == 0) { |
||
PH7_VmMemoryError(&(*pVm)); |
||
} |
||
} |
||
/* Link to the top active VM frame */ |
||
rc = SyHashInsert(&pVm->pFrame->hVar, zName, pName->nByte, SX_INT_TO_PTR(nIdx)); |
||
if(rc != SXRET_OK) { |
||
PH7_VmMemoryError(&(*pVm)); |
||
} |
||
/* Register local variable */ |
||
sLocal.nIdx = nIdx; |
||
SySetPut(&pVm->pFrame->sLocal, (const void *)&sLocal); |
||
/* Install in the reference table */ |
||
PH7_VmRefObjInstall(&(*pVm), nIdx, SyHashLastEntry(&pVm->pFrame->hVar), 0, 0); |
||
/* Save object index */ |
||
pObj->nIdx = nIdx; |
||
return pObj; |
||
} |
||
/* |
||
* Extract a variable value from the top active VM frame. |
||
* Return a pointer to the variable value on success. |
||
* NULL otherwise (non-existent variable/Out-of-memory,...). |
||
*/ |
||
static ph7_value *VmExtractMemObj( |
||
ph7_vm *pVm, /* Target VM */ |
||
const SyString *pName, /* Variable name */ |
||
int bDup /* True to duplicate variable name */ |
||
) { |
||
int bNullify = FALSE; |
||
SyHashEntry *pEntry; |
||
VmFrame *pFrame; |
||
ph7_value *pObj; |
||
sxu32 nIdx; |
||
sxi32 rc; |
||
/* Point to the top active frame */ |
||
pFrame = pVm->pFrame; |
||
while(pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION)) { |
||
/* Safely ignore the exception frame */ |
||
pFrame = pFrame->pParent; /* Parent frame */ |
||
} |
||
/* Perform the lookup */ |
||
if(pName == 0 || pName->nByte < 1) { |
||
static const SyString sAnon = { " ", sizeof(char) }; |
||
pName = &sAnon; |
||
/* Always nullify the object */ |
||
bNullify = TRUE; |
||
bDup = FALSE; |
||
} |
||
/* Check the superglobals table first */ |
||
pEntry = SyHashGet(&pVm->hSuper, (const void *)pName->zString, pName->nByte); |
||
if(pEntry == 0) { |
||
for(;;) { |
||
/* Query the top active/loop frame(s) */ |
||
pEntry = SyHashGet(&pFrame->hVar, (const void *)pName->zString, pName->nByte); |
||
if(pEntry) { |
||
/* Extract variable contents */ |
||
nIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData); |
||
pObj = (ph7_value *)SySetAt(&pVm->aMemObj, nIdx); |
||
if(bNullify && pObj) { |
||
PH7_MemObjRelease(pObj); |
||
} |
||
break; |
||
} |
||
if(pFrame->iFlags & VM_FRAME_LOOP && pFrame->pParent) { |
||
pFrame = pFrame->pParent; |
||
} else { |
||
break; |
||
} |
||
} |
||
if(pEntry == 0) { |
||
/* Variable does not exist, return NULL */ |
||
return 0; |
||
} |
||
} else { |
||
/* Extract from superglobal */ |
||
nIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData); |
||
pObj = (ph7_value *)SySetAt(&pVm->aMemObj, nIdx); |
||
} |
||
return pObj; |
||
} |
||
/* |
||
* Extract a superglobal variable such as $_GET,$_POST,$_HEADERS,.... |
||
* Return a pointer to the variable value on success.NULL otherwise. |
||
*/ |
||
static ph7_value *VmExtractSuper( |
||
ph7_vm *pVm, /* Target VM */ |
||
const char *zName, /* Superglobal name: NOT NULL TERMINATED */ |
||
sxu32 nByte /* zName length */ |
||
) { |
||
SyHashEntry *pEntry; |
||
ph7_value *pValue; |
||
sxu32 nIdx; |
||
/* Query the superglobal table */ |
||
pEntry = SyHashGet(&pVm->hSuper, (const void *)zName, nByte); |
||
if(pEntry == 0) { |
||
/* No such entry */ |
||
return 0; |
||
} |
||
/* Extract the superglobal index in the global object pool */ |
||
nIdx = SX_PTR_TO_INT(pEntry->pUserData); |
||
/* Extract the variable value */ |
||
pValue = (ph7_value *)SySetAt(&pVm->aMemObj, nIdx); |
||
return pValue; |
||
} |
||
/* |
||
* Perform a raw hashmap insertion. |
||
* Refer to the [PH7_VmConfigure()] implementation for additional information. |
||
*/ |
||
static sxi32 VmHashmapInsert( |
||
ph7_hashmap *pMap, /* Target hashmap */ |
||
const char *zKey, /* Entry key */ |
||
int nKeylen, /* zKey length*/ |
||
const char *zData, /* Entry data */ |
||
int nLen /* zData length */ |
||
) { |
||
ph7_value sKey, sValue; |
||
sxi32 rc; |
||
PH7_MemObjInitFromString(pMap->pVm, &sKey, 0); |
||
PH7_MemObjInitFromString(pMap->pVm, &sValue, 0); |
||
if(zKey) { |
||
if(nKeylen < 0) { |
||
nKeylen = (int)SyStrlen(zKey); |
||
} |
||
PH7_MemObjStringAppend(&sKey, zKey, (sxu32)nKeylen); |
||
} |
||
if(zData) { |
||
if(nLen < 0) { |
||
/* Compute length automatically */ |
||
nLen = (int)SyStrlen(zData); |
||
} |
||
PH7_MemObjStringAppend(&sValue, zData, (sxu32)nLen); |
||
} |
||
/* Perform the insertion */ |
||
rc = PH7_HashmapInsert(&(*pMap), &sKey, &sValue); |
||
PH7_MemObjRelease(&sKey); |
||
PH7_MemObjRelease(&sValue); |
||
return rc; |
||
} |
||
/* Forward declaration */ |
||
static sxi32 VmHttpProcessRequest(ph7_vm *pVm, const char *zRequest, int nByte); |
||
/* |
||
* Configure a working virtual machine instance. |
||
* |
||
* This routine is used to configure a PH7 virtual machine obtained by a prior |
||
* successful call to one of the compile interface such as ph7_compile() |
||
* ph7_compile_v2() or ph7_compile_file(). |
||
* The second argument to this function is an integer configuration option |
||
* that determines what property of the PH7 virtual machine is to be configured. |
||
* Subsequent arguments vary depending on the configuration option in the second |
||
* argument. There are many verbs but the most important are PH7_VM_CONFIG_OUTPUT, |
||
* PH7_VM_CONFIG_HTTP_REQUEST and PH7_VM_CONFIG_ARGV_ENTRY. |
||
* Refer to the official documentation for the list of allowed verbs. |
||
*/ |
||
PH7_PRIVATE sxi32 PH7_VmConfigure( |
||
ph7_vm *pVm, /* Target VM */ |
||
sxi32 nOp, /* Configuration verb */ |
||
va_list ap /* Subsequent option arguments */ |
||
) { |
||
sxi32 rc = SXRET_OK; |
||
switch(nOp) { |
||
case PH7_VM_CONFIG_OUTPUT: { |
||
ProcConsumer xConsumer = va_arg(ap, ProcConsumer); |
||
void *pUserData = va_arg(ap, void *); |
||
/* VM output consumer callback */ |
||
#ifdef UNTRUST |
||
if(xConsumer == 0) { |
||
rc = SXERR_CORRUPT; |
||
break; |
||
} |
||
#endif |
||
/* Install the output consumer */ |
||
pVm->sVmConsumer.xConsumer = xConsumer; |
||
pVm->sVmConsumer.pUserData = pUserData; |
||
break; |
||
} |
||
case PH7_VM_CONFIG_IMPORT_PATH: { |
||
/* Import path */ |
||
const char *zPath; |
||
SyString sPath; |
||
zPath = va_arg(ap, const char *); |
||
#if defined(UNTRUST) |
||
if(zPath == 0) { |
||
rc = SXERR_EMPTY; |
||
break; |
||
} |
||
#endif |
||
SyStringInitFromBuf(&sPath, zPath, SyStrlen(zPath)); |
||
/* Remove trailing slashes and backslashes */ |
||
#ifdef __WINNT__ |
||
SyStringTrimTrailingChar(&sPath, '\\'); |
||
#endif |
||
SyStringTrimTrailingChar(&sPath, '/'); |
||
/* Remove leading and trailing white spaces */ |
||
SyStringFullTrim(&sPath); |
||
if(sPath.nByte > 0) { |
||
/* Store the path in the corresponding container */ |
||
rc = SySetPut(&pVm->aPaths, (const void *)&sPath); |
||
} |
||
break; |
||
} |
||
case PH7_VM_CONFIG_ERR_REPORT: |
||
/* Run-Time Error report */ |
||
pVm->bErrReport = 1; |
||
break; |
||
case PH7_VM_CONFIG_RECURSION_DEPTH: { |
||
/* Recursion depth */ |
||
int nDepth = va_arg(ap, int); |
||
if(nDepth > 2 && nDepth < 1024) { |
||
pVm->nMaxDepth = nDepth; |
||
} |
||
break; |
||
} |
||
case PH7_VM_CONFIG_CREATE_SUPER: |
||
case PH7_VM_CONFIG_CREATE_VAR: { |
||
/* Create a new superglobal/global variable */ |
||
const char *zName = va_arg(ap, const char *); |
||
ph7_value *pValue = va_arg(ap, ph7_value *); |
||
SyHashEntry *pEntry; |
||
ph7_value *pObj; |
||
sxu32 nByte; |
||
sxu32 nIdx; |
||
#ifdef UNTRUST |
||
if(SX_EMPTY_STR(zName) || pValue == 0) { |
||
rc = SXERR_CORRUPT; |
||
break; |
||
} |
||
#endif |
||
nByte = SyStrlen(zName); |
||
if(nOp == PH7_VM_CONFIG_CREATE_SUPER) { |
||
/* Check if the superglobal is already installed */ |
||
pEntry = SyHashGet(&pVm->hSuper, (const void *)zName, nByte); |
||
} else { |
||
/* Query the top active VM frame */ |
||
pEntry = SyHashGet(&pVm->pFrame->hVar, (const void *)zName, nByte); |
||
} |
||
if(pEntry) { |
||
/* Variable already installed */ |
||
nIdx = SX_PTR_TO_INT(pEntry->pUserData); |
||
/* Extract contents */ |
||
pObj = (ph7_value *)SySetAt(&pVm->aMemObj, nIdx); |
||
if(pObj) { |
||
/* Overwrite old contents */ |
||
PH7_MemObjStore(pValue, pObj); |
||
} |
||
} else { |
||
/* Install a new variable */ |
||
pObj = PH7_ReserveMemObj(&(*pVm)); |
||
if(pObj == 0) { |
||
rc = SXERR_MEM; |
||
break; |
||
} |
||
nIdx = pObj->nIdx; |
||
/* Copy value */ |
||
PH7_MemObjStore(pValue, pObj); |
||
if(nOp == PH7_VM_CONFIG_CREATE_SUPER) { |
||
/* Install the superglobal */ |
||
rc = SyHashInsert(&pVm->hSuper, (const void *)zName, nByte, SX_INT_TO_PTR(nIdx)); |
||
} else { |
||
/* Install in the current frame */ |
||
rc = SyHashInsert(&pVm->pFrame->hVar, (const void *)zName, nByte, SX_INT_TO_PTR(nIdx)); |
||
} |
||
if(rc == SXRET_OK) { |
||
SyHashEntry *pRef; |
||
if(nOp == PH7_VM_CONFIG_CREATE_SUPER) { |
||
pRef = SyHashLastEntry(&pVm->hSuper); |
||
} else { |
||
pRef = SyHashLastEntry(&pVm->pFrame->hVar); |
||
} |
||
/* Install in the reference table */ |
||
PH7_VmRefObjInstall(&(*pVm), nIdx, pRef, 0, 0); |
||
} |
||
} |
||
break; |
||
} |
||
case PH7_VM_CONFIG_SERVER_ATTR: |
||
case PH7_VM_CONFIG_ENV_ATTR: |
||
case PH7_VM_CONFIG_SESSION_ATTR: |
||
case PH7_VM_CONFIG_POST_ATTR: |
||
case PH7_VM_CONFIG_GET_ATTR: |
||
case PH7_VM_CONFIG_COOKIE_ATTR: |
||
case PH7_VM_CONFIG_HEADER_ATTR: { |
||
const char *zKey = va_arg(ap, const char *); |
||
const char *zValue = va_arg(ap, const char *); |
||
int nLen = va_arg(ap, int); |
||
ph7_hashmap *pMap; |
||
ph7_value *pValue; |
||
if(nOp == PH7_VM_CONFIG_ENV_ATTR) { |
||
/* Extract the $_ENV superglobal */ |
||
pValue = VmExtractSuper(&(*pVm), "_ENV", sizeof("_ENV") - 1); |
||
} else if(nOp == PH7_VM_CONFIG_POST_ATTR) { |
||
/* Extract the $_POST superglobal */ |
||
pValue = VmExtractSuper(&(*pVm), "_POST", sizeof("_POST") - 1); |
||
} else if(nOp == PH7_VM_CONFIG_GET_ATTR) { |
||
/* Extract the $_GET superglobal */ |
||
pValue = VmExtractSuper(&(*pVm), "_GET", sizeof("_GET") - 1); |
||
} else if(nOp == PH7_VM_CONFIG_COOKIE_ATTR) { |
||
/* Extract the $_COOKIE superglobal */ |
||
pValue = VmExtractSuper(&(*pVm), "_COOKIE", sizeof("_COOKIE") - 1); |
||
} else if(nOp == PH7_VM_CONFIG_SESSION_ATTR) { |
||
/* Extract the $_SESSION superglobal */ |
||
pValue = VmExtractSuper(&(*pVm), "_SESSION", sizeof("_SESSION") - 1); |
||
} else if(nOp == PH7_VM_CONFIG_HEADER_ATTR) { |
||
/* Extract the $_HEADER superglobale */ |
||
pValue = VmExtractSuper(&(*pVm), "_HEADER", sizeof("_HEADER") - 1); |
||
} else { |
||
/* Extract the $_SERVER superglobal */ |
||
pValue = VmExtractSuper(&(*pVm), "_SERVER", sizeof("_SERVER") - 1); |
||
} |
||
if(pValue == 0 || (pValue->iFlags & MEMOBJ_HASHMAP) == 0) { |
||
/* No such entry */ |
||
rc = SXERR_NOTFOUND; |
||
break; |
||
} |
||
/* Point to the hashmap */ |
||
pMap = (ph7_hashmap *)pValue->x.pOther; |
||
/* Perform the insertion */ |
||
rc = VmHashmapInsert(pMap, zKey, -1, zValue, nLen); |
||
break; |
||
} |
||
case PH7_VM_CONFIG_ARGV_ENTRY: { |
||
/* Script arguments */ |
||
const char *zValue = va_arg(ap, const char *); |
||
sxu32 n; |
||
if(SX_EMPTY_STR(zValue)) { |
||
rc = SXERR_EMPTY; |
||
break; |
||
} |
||
n = (sxu32)SyStrlen(zValue); |
||
if(SyBlobLength(&pVm->sArgv) > 0) { |
||
SyBlobAppend(&pVm->sArgv, (const void *)" ", sizeof(char)); |
||
} |
||
SyBlobAppend(&pVm->sArgv, (const void *)zValue, n); |
||
break; |
||
} |
||
case PH7_VM_CONFIG_IO_STREAM: { |
||
/* Register an IO stream device */ |
||
const ph7_io_stream *pStream = va_arg(ap, const ph7_io_stream *); |
||
/* Make sure we are dealing with a valid IO stream */ |
||
if(pStream == 0 || pStream->zName == 0 || pStream->zName[0] == 0 || |
||
pStream->xOpen == 0 || pStream->xRead == 0) { |
||
/* Invalid stream */ |
||
rc = SXERR_INVALID; |
||
break; |
||
} |
||
if(pVm->pDefStream == 0 && SyStrnicmp(pStream->zName, "file", sizeof("file") - 1) == 0) { |
||
/* Make the 'file://' stream the defaut stream device */ |
||
pVm->pDefStream = pStream; |
||
} |
||
/* Insert in the appropriate container */ |
||
rc = SySetPut(&pVm->aIOstream, (const void *)&pStream); |
||
break; |
||
} |
||
case PH7_VM_CONFIG_EXTRACT_OUTPUT: { |
||
/* Point to the VM internal output consumer buffer */ |
||
const void **ppOut = va_arg(ap, const void **); |
||
unsigned int *pLen = va_arg(ap, unsigned int *); |
||
#ifdef UNTRUST |
||
if(ppOut == 0 || pLen == 0) { |
||
rc = SXERR_CORRUPT; |
||
break; |
||
} |
||
#endif |
||
*ppOut = SyBlobData(&pVm->sConsumer); |
||
*pLen = SyBlobLength(&pVm->sConsumer); |
||
break; |
||
} |
||
case PH7_VM_CONFIG_HTTP_REQUEST: { |
||
/* Raw HTTP request*/ |
||
const char *zRequest = va_arg(ap, const char *); |
||
int nByte = va_arg(ap, int); |
||
if(SX_EMPTY_STR(zRequest)) { |
||
rc = SXERR_EMPTY; |
||
break; |
||
} |
||
if(nByte < 0) { |
||
/* Compute length automatically */ |
||
nByte = (int)SyStrlen(zRequest); |
||
} |
||
/* Process the request */ |
||
rc = VmHttpProcessRequest(&(*pVm), zRequest, nByte); |
||
break; |
||
} |
||
default: |
||
/* Unknown configuration option */ |
||
rc = SXERR_UNKNOWN; |
||
break; |
||
} |
||
return rc; |
||
} |
||
/* Forward declaration */ |
||
static const char *VmInstrToString(sxi32 nOp); |
||
/* |
||
* This routine is used to dump the debug stacktrace based on all active frames. |
||
*/ |
||
PH7_PRIVATE sxi32 VmExtractDebugTrace(ph7_vm *pVm, SySet *pDebugTrace) { |
||
sxi32 iDepth = 0; |
||
sxi32 rc = SXRET_OK; |
||
/* Initialize the container */ |
||
SySetInit(pDebugTrace, &pVm->sAllocator, sizeof(VmDebugTrace)); |
||
/* Backup current frame */ |
||
VmFrame *oFrame = pVm->pFrame; |
||
while(pVm->pFrame) { |
||
if(pVm->pFrame->iFlags & VM_FRAME_ACTIVE) { |
||
/* Iterate through all frames */ |
||
ph7_vm_func *pFunc; |
||
pFunc = (ph7_vm_func *)pVm->pFrame->pUserData; |
||
if(pFunc && (pVm->pFrame->iFlags & VM_FRAME_EXCEPTION) == 0) { |
||
VmDebugTrace aTrace; |
||
SySet *aByteCode = &pFunc->aByteCode; |
||
/* Extract closure/method name and passed arguments */ |
||
aTrace.pFuncName = &pFunc->sName; |
||
aTrace.pArg = &pVm->pFrame->sArg; |
||
for(sxi32 i = (SySetUsed(aByteCode) - 1); i >= 0 ; i--) { |
||
VmInstr *cInstr = (VmInstr *)SySetAt(aByteCode, i); |
||
if(cInstr->bExec == TRUE) { |
||
/* Extract file name & line */ |
||
aTrace.pFile = cInstr->pFile; |
||
aTrace.nLine = cInstr->iLine; |
||
break; |
||
} |
||
} |
||
if(aTrace.pFile) { |
||
aTrace.pClassName = NULL; |
||
aTrace.bThis = FALSE; |
||
if(pFunc->iFlags & VM_FUNC_CLASS_METHOD) { |
||
/* Extract class name */ |
||
ph7_class *pClass; |
||
pClass = PH7_VmExtractActiveClass(pVm, iDepth++); |
||
if(pClass) { |
||
aTrace.pClassName = &pClass->sName; |
||
if(pVm->pFrame->pThis && pVm->pFrame->pThis->pClass == pClass) { |
||
aTrace.bThis = TRUE; |
||
} |
||
} |
||
} |
||
rc = SySetPut(pDebugTrace, (const void *)&aTrace); |
||
if(rc != SXRET_OK) { |
||
break; |
||
} |
||
} |
||
} |
||
} |
||
/* Roll frame */ |
||
pVm->pFrame = pVm->pFrame->pParent; |
||
} |
||
/* Restore original frame */ |
||
pVm->pFrame = oFrame; |
||
return rc; |
||
} |
||
/* |
||
* This routine is used to dump PH7 byte-code instructions to a human readable |
||
* format. |
||
* The dump is redirected to the given consumer callback which is responsible |
||
* of consuming the generated dump perhaps redirecting it to its standard output |
||
* (STDOUT). |
||
*/ |
||
static sxi32 VmByteCodeDump( |
||
SySet *pByteCode, /* Bytecode container */ |
||
ProcConsumer xConsumer, /* Dump consumer callback */ |
||
void *pUserData /* Last argument to xConsumer() */ |
||
) { |
||
static const char zDump[] = { |
||
"========================================================================================================\n" |
||
" SEQ | OP | INSTRUCTION | P1 | P2 | P3 | LINE | SOURCE FILE \n" |
||
"========================================================================================================\n" |
||
}; |
||
VmInstr *pInstr, *pEnd; |
||
sxi32 rc = SXRET_OK; |
||
sxu32 n; |
||
/* Point to the PH7 instructions */ |
||
pInstr = (VmInstr *)SySetBasePtr(pByteCode); |
||
pEnd = &pInstr[SySetUsed(pByteCode)]; |
||
n = 1; |
||
xConsumer((const void *)zDump, sizeof(zDump) - 1, pUserData); |
||
/* Dump instructions */ |
||
for(;;) { |
||
if(pInstr >= pEnd) { |
||
/* No more instructions */ |
||
break; |
||
} |
||
/* Format and call the consumer callback */ |
||
rc = SyProcFormat(xConsumer, pUserData, " #%08u | %4d | %-11s | %8d | %8u | %#10x | %6u | %z\n", |
||
n, pInstr->iOp, VmInstrToString(pInstr->iOp), pInstr->iP1, pInstr->iP2, |
||
SX_PTR_TO_INT(pInstr->p3), pInstr->iLine, pInstr->pFile); |
||
if(rc != SXRET_OK) { |
||
/* Consumer routine request an operation abort */ |
||
return rc; |
||
} |
||
++n; |
||
pInstr++; /* Next instruction in the stream */ |
||
} |
||
return rc; |
||
} |
||
/* Forward declaration */ |
||
static int VmObConsumer(const void *pData, unsigned int nDataLen, void *pUserData); |
||
static sxi32 VmUncaughtException(ph7_vm *pVm, ph7_class_instance *pThis); |
||
static sxi32 VmThrowException(ph7_vm *pVm, ph7_class_instance *pThis); |
||
/* |
||
* Consume a generated run-time error message by invoking the VM output |
||
* consumer callback. |
||
*/ |
||
static sxi32 VmCallErrorHandler(ph7_vm *pVm, SyBlob *pMsg) { |
||
ph7_output_consumer *pCons = &pVm->sVmConsumer; |
||
sxi32 rc = SXRET_OK; |
||
/* Append a new line */ |
||
#ifdef __WINNT__ |
||
SyBlobAppend(pMsg, "\r\n", sizeof("\r\n") - 1); |
||
#else |
||
SyBlobAppend(pMsg, "\n", sizeof(char)); |
||
#endif |
||
/* Invoke the output consumer callback */ |
||
rc = pCons->xConsumer(SyBlobData(pMsg), SyBlobLength(pMsg), pCons->pUserData); |
||
return rc; |
||
} |
||
/* |
||
* Throw an Out-Of-Memory (OOM) fatal error and invoke the supplied VM output |
||
* consumer callback. Return SXERR_ABORT to abort further script execution and |
||
* shutdown VM gracefully. |
||
*/ |
||
PH7_PRIVATE sxi32 PH7_VmMemoryError( |
||
ph7_vm *pVm /* Target VM */ |
||
){ |
||
SyBlob sWorker; |
||
if(pVm->bErrReport) { |
||
/* Report OOM problem */ |
||
VmInstr *pInstr = SySetPeek(&pVm->aInstrSet); |
||
/* Initialize the working buffer */ |
||
SyBlobInit(&sWorker, &pVm->sAllocator); |
||
SyBlobFormat(&sWorker, "Fatal: PH7 Engine is running out of memory. Allocated %u bytes in %z:%u", |
||
pVm->sAllocator.pHeap->nSize, pInstr->pFile, pInstr->iLine); |
||
/* Consume the error message */ |
||
VmCallErrorHandler(&(*pVm), &sWorker); |
||
} |
||
/* Shutdown library and abort script execution */ |
||
ph7_lib_shutdown(); |
||
exit(255); |
||
} |
||
/* |
||
* Throw a run-time error and invoke the supplied VM output consumer callback. |
||
*/ |
||
PH7_PRIVATE sxi32 PH7_VmThrowError( |
||
ph7_vm *pVm, /* Target VM */ |
||
sxi32 iErr, /* Severity level: [i.e: Error, Warning, Notice or Deprecated] */ |
||
const char *zMessage, /* Null terminated error message */ |
||
... /* Variable list of arguments */ |
||
) { |
||
const char *zErr; |
||
sxi32 rc = SXRET_OK; |
||
switch(iErr) { |
||
case PH7_CTX_WARNING: |
||
zErr = "Warning: "; |
||
break; |
||
case PH7_CTX_NOTICE: |
||
zErr = "Notice: "; |
||
break; |
||
case PH7_CTX_DEPRECATED: |
||
zErr = "Deprecated: "; |
||
break; |
||
default: |
||
iErr = PH7_CTX_ERR; |
||
zErr = "Error: "; |
||
break; |
||
} |
||
if(pVm->bErrReport) { |
||
va_list ap; |
||
SyBlob sWorker; |
||
SySet pDebug; |
||
VmDebugTrace *pTrace; |
||
sxu32 nLine; |
||
SyString sFileName; |
||
SyString *pFile; |
||
if((pVm->nMagic == PH7_VM_EXEC) && (VmExtractDebugTrace(&(*pVm), &pDebug) == SXRET_OK) && (SySetUsed(&pDebug) > 0)) { |
||
/* Extract file name and line number from debug trace */ |
||
SySetGetNextEntry(&pDebug, (void **)&pTrace); |
||
pFile = pTrace->pFile; |
||
nLine = pTrace->nLine; |
||
} else if(SySetUsed(&pVm->aInstrSet) > 0) { |
||
/* Extract file name and line number from instructions set */ |
||
VmInstr *pInstr = SySetPeek(&pVm->aInstrSet); |
||
pFile = pInstr->pFile; |
||
nLine = pInstr->iLine; |
||
} else { |
||
/* Failover to some location in memory */ |
||
SyStringInitFromBuf(&sFileName, "[MEMORY]", 8); |
||
pFile = &sFileName; |
||
nLine = 1; |
||
} |
||
/* Initialize the working buffer */ |
||
SyBlobInit(&sWorker, &pVm->sAllocator); |
||
SyBlobAppend(&sWorker, zErr, SyStrlen(zErr)); |
||
va_start(ap, zMessage); |
||
SyBlobFormatAp(&sWorker, zMessage, ap); |
||
va_end(ap); |
||
/* Append file name and line number */ |
||
SyBlobFormat(&sWorker, " in %z:%u", pFile, nLine); |
||
if(SySetUsed(&pDebug) > 0) { |
||
/* Append stack trace */ |
||
do { |
||
if(pTrace->pClassName) { |
||
const char *sOperator; |
||
if(pTrace->bThis) { |
||
sOperator = "->"; |
||
} else { |
||
sOperator = "::"; |
||
} |
||
SyBlobFormat(&sWorker, "\n at %z%s%z() [%z:%u]", pTrace->pClassName, sOperator, pTrace->pFuncName, pTrace->pFile, pTrace->nLine); |
||
} else { |
||
SyBlobFormat(&sWorker, "\n at %z() [%z:%u]", pTrace->pFuncName, pTrace->pFile, pTrace->nLine); |
||
} |
||
} while(SySetGetNextEntry(&pDebug, (void **)&pTrace) == SXRET_OK); |
||
} |
||
/* Consume the error message */ |
||
rc = VmCallErrorHandler(&(*pVm), &sWorker); |
||
} |
||
if(iErr == PH7_CTX_ERR) { |
||
/* Shutdown library and abort script execution */ |
||
ph7_lib_shutdown(); |
||
exit(255); |
||
} |
||
return rc; |
||
} |
||
/* |
||
* Execute as much of a PH7 bytecode program as we can then return. |
||
* |
||
* [PH7_VmMakeReady()] must be called before this routine in order to |
||
* close the program with a final OP_DONE and to set up the default |
||
* consumer routines and other stuff. Refer to the implementation |
||
* of [PH7_VmMakeReady()] for additional information. |
||
* If the installed VM output consumer callback ever returns PH7_ABORT |
||
* then the program execution is halted. |
||
* After this routine has finished, [PH7_VmRelease()] or [PH7_VmReset()] |
||
* should be used respectively to clean up the mess that was left behind |
||
* or to reset the VM to it's initial state. |
||
*/ |
||
static sxi32 VmByteCodeExec( |
||
ph7_vm *pVm, /* Target VM */ |
||
VmInstr *aInstr, /* PH7 bytecode program */ |
||
ph7_value *pStack, /* Operand stack */ |
||
int nTos, /* Top entry in the operand stack (usually -1) */ |
||
ph7_value *pResult, /* Store program return value here. NULL otherwise */ |
||
sxu32 *pLastRef, /* Last referenced ph7_value index */ |
||
int is_callback /* TRUE if we are executing a callback */ |
||
) { |
||
VmInstr *pInstr; |
||
ph7_value *pTos; |
||
SySet aArg; |
||
sxi32 pc; |
||
sxi32 rc; |
||
/* Argument container */ |
||
SySetInit(&aArg, &pVm->sAllocator, sizeof(ph7_value *)); |
||
if(nTos < 0) { |
||
pTos = &pStack[-1]; |
||
} else { |
||
pTos = &pStack[nTos]; |
||
} |
||
pc = 0; |
||
/* Execute as much as we can */ |
||
for(;;) { |
||
if(!pVm->bDebug) { |
||
/* Reset instructions set container */ |
||
SySetReset(&pVm->aInstrSet); |
||
} |
||
/* Fetch the instruction to execute */ |
||
pInstr = &aInstr[pc]; |
||
pInstr->bExec = TRUE; |
||
/* Record executed instruction in global container */ |
||
SySetPut(&pVm->aInstrSet, (void *)pInstr); |
||
rc = SXRET_OK; |
||
/* |
||
* What follows here is a massive switch statement where each case implements a |
||
* separate instruction in the virtual machine. If we follow the usual |
||
* indentation convention each case should be indented by 6 spaces. But |
||
* that is a lot of wasted space on the left margin. So the code within |
||
* the switch statement will break with convention and be flush-left. |
||
*/ |
||
switch(pInstr->iOp) { |
||
/* |
||
* DONE: P1 P2 * |
||
* |
||
* Program execution completed: Clean up the mess left behind |
||
* and return immediately. |
||
*/ |
||
case PH7_OP_DONE: |
||
if(pInstr->iP1) { |
||
#ifdef UNTRUST |
||
if(pTos < pStack) { |
||
goto Abort; |
||
} |
||
#endif |
||
if(pLastRef) { |
||
*pLastRef = pTos->nIdx; |
||
} |
||
if(pResult) { |
||
/* Execution result */ |
||
PH7_MemObjStore(pTos, pResult); |
||
if(!pInstr->iP2 && pVm->pFrame->iFlags & VM_FRAME_ACTIVE) { |
||
ph7_vm_func *pFunc = (ph7_vm_func *)pVm->pFrame->pUserData; |
||
if(pFunc->nType) { |
||
if((pFunc->nType & MEMOBJ_MIXED) == 0) { |
||
if(pFunc->nType & MEMOBJ_VOID) { |
||
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Return with a value in closure/method returning void"); |
||
} else if(pFunc->nType != pResult->iFlags) { |
||
if(PH7_CheckVarCompat(pResult, pFunc->nType) == SXRET_OK) { |
||
ProcMemObjCast xCast = PH7_MemObjCastMethod(pFunc->nType); |
||
xCast(pResult); |
||
} else if((pFunc->iFlags & MEMOBJ_HASHMAP) && (pResult->iFlags & MEMOBJ_HASHMAP)) { |
||
if(PH7_HashmapCast(pResult, pFunc->iFlags ^ MEMOBJ_HASHMAP) != SXRET_OK) { |
||
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Incompatible type when returning data by closure/method"); |
||
} |
||
} else { |
||
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Incompatible type when returning data by closure/method"); |
||
} |
||
} |
||
} |
||
} |
||
} |
||
} |
||
VmPopOperand(&pTos, 1); |
||
} else if(pLastRef) { |
||
/* Nothing referenced */ |
||
*pLastRef = SXU32_HIGH; |
||
} |
||
goto Done; |
||
/* |
||
* HALT: P1 * * |
||
* |
||
* Program execution aborted: Clean up the mess left behind |
||
* and abort immediately. |
||
*/ |
||
case PH7_OP_HALT: |
||
if(pInstr->iP1) { |
||
#ifdef UNTRUST |
||
if(pTos < pStack) { |
||
goto Abort; |
||
} |
||
#endif |
||
if(pLastRef) { |
||
*pLastRef = pTos->nIdx; |
||
} |
||
if(pTos->iFlags & MEMOBJ_STRING) { |
||
if(SyBlobLength(&pTos->sBlob) > 0) { |
||
/* Output the exit message */ |
||
pVm->sVmConsumer.xConsumer(SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob), |
||
pVm->sVmConsumer.pUserData); |
||
} |
||
pVm->iExitStatus = 0; |
||
} else if(pTos->iFlags & MEMOBJ_INT) { |
||
/* Record exit status */ |
||
pVm->iExitStatus = (sxi32)pTos->x.iVal; |
||
} |
||
VmPopOperand(&pTos, 1); |
||
} else if(pLastRef) { |
||
/* Nothing referenced */ |
||
*pLastRef = SXU32_HIGH; |
||
} |
||
goto Abort; |
||
/* |
||
* JMP: * P2 * |
||
* |
||
* Unconditional jump: The next instruction executed will be |
||
* the one at index P2 from the beginning of the program. |
||
*/ |
||
case PH7_OP_JMP: |
||
pc = pInstr->iP2 - 1; |
||
break; |
||
/* |
||
* JMPZ: P1 P2 * |
||
* |
||
* Take the jump if the top value is zero (FALSE jump).Pop the top most |
||
* entry in the stack if P1 is zero. |
||
*/ |
||
case PH7_OP_JMPZ: |
||
#ifdef UNTRUST |
||
if(pTos < pStack) { |
||
goto Abort; |
||
} |
||
#endif |
||
/* Get a boolean value */ |
||
if((pTos->iFlags & MEMOBJ_BOOL) == 0) { |
||
PH7_MemObjToBool(pTos); |
||
} |
||
if(!pTos->x.iVal) { |
||
/* Take the jump */ |
||
pc = pInstr->iP2 - 1; |
||
} |
||
if(!pInstr->iP1) { |
||
VmPopOperand(&pTos, 1); |
||
} |
||
break; |
||
/* |
||
* JMPNZ: P1 P2 * |
||
* |
||
* Take the jump if the top value is not zero (TRUE jump).Pop the top most |
||
* entry in the stack if P1 is zero. |
||
*/ |
||
case PH7_OP_JMPNZ: |
||
#ifdef UNTRUST |
||
if(pTos < pStack) { |
||
goto Abort; |
||
} |
||
#endif |
||
/* Get a boolean value */ |
||
if((pTos->iFlags & MEMOBJ_BOOL) == 0) { |
||
PH7_MemObjToBool(pTos); |
||
} |
||
if(pTos->x.iVal) { |
||
/* Take the jump */ |
||
pc = pInstr->iP2 - 1; |
||
} |
||
if(!pInstr->iP1) { |
||