Aer Interpreter Source
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
346 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].nType & MEMOBJ_BOOL) {
/* Bool */
c = 'b';
} else if(aArg[j].nType & MEMOBJ_CALL) {
/* Callback */
c = 'a';
} else if(aArg[j].nType & MEMOBJ_CHAR) {
/* Char */
c = 'c';
} else if(aArg[j].nType & MEMOBJ_INT) {
/* Integer */
c = 'i';
} else if(aArg[j].nType & MEMOBJ_MIXED) {
/* Mixed */
c = 'm';
} else if(aArg[j].nType & 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].nType & MEMOBJ_REAL) {
/* Float */
c = 'f';
} else if(aArg[j].nType & MEMOBJ_RES) {
/* Resource */
c = 'r';
} else if(aArg[j].nType & MEMOBJ_STRING) {
/* String */
c = 's';
} else if(aArg[j].nType & MEMOBJ_VOID) {
/* Void */
c = 'v';
}
if(aArg[j].nType & MEMOBJ_HASHMAP && (aArg[j].nType & 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->nType & 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->nType & 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->nType & 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->nType) {
if(PH7_CheckVarCompat(pResult, pFunc->nType) == SXRET_OK) {
ProcMemObjCast xCast = PH7_MemObjCastMethod(pFunc->nType);
xCast(pResult);
} else if((pFunc->iFlags & MEMOBJ_HASHMAP) && (pResult->nType & 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->nType & 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->nType & 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->nType & 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->nType & MEMOBJ_BOOL) == 0) {
PH7_MemObjToBool(pTos);
}
if(pTos->x.iVal) {
/* Take the jump */
pc = pInstr->iP2 - 1;
}
if(!pInstr->iP1) {
VmPopOperand(&pTos, 1);
}
break;
/*
* JMPLFB: * * *
*
* Creates and enters the jump loop frame on the beginning of each iteration.
*/
case PH7_OP_JMPLFB: {
VmFrame *pFrame;
/* Enter the jump loop frame */
rc = VmEnterFrame(&(*pVm), pVm->pFrame->pUserData, pVm->pFrame->pThis, &pFrame);
if(rc != SXRET_OK) {
PH7_VmMemoryError(&(*pVm));
}
pFrame->iFlags = VM_FRAME_LOOP;
break;
}
/*
* Leaves and destroys the jump loop frame at the end of each iteration
* as well as on 'break' and 'continue' instructions.
*/
case PH7_OP_JMPLFE: {
/* Leave the jump loop frame */
if(pVm->pFrame->iFlags & VM_FRAME_LOOP) {
VmLeaveFrame(&(*pVm));
}
break;
}
/*
* NOOP: * * *
*
* Do nothing. This instruction is often useful as a jump
* destination.
*/
case PH7_OP_NOOP:
break;
/*
* POP: P1 * *
*
* Pop P1 elements from the operand stack.
*/
case PH7_OP_POP: {
sxi32 n = pInstr->iP1;
if(&pTos[-n + 1] < pStack) {
/* TICKET 1433-51 Stack underflow must be handled at run-time */
n = (sxi32)(pTos - pStack);
}
VmPopOperand(&pTos, n);
break;
}
/*
* CVT_INT: * * *
*
* Force the top of the stack to be an integer.
*/
case PH7_OP_CVT_INT:
#ifdef UNTRUST
if(pTos < pStack) {
goto Abort;
}
#endif
if((pTos->nType & MEMOBJ_INT) == 0) {
PH7_MemObjToInteger(pTos);
}
/* Invalidate any prior representation */
MemObjSetType(pTos, MEMOBJ_INT);
break;
/*
* CVT_REAL: * * *
*
* Force the top of the stack to be a real.
*/
case PH7_OP_CVT_REAL:
#ifdef UNTRUST
if(pTos < pStack) {
goto Abort;
}
#endif
if((pTos->nType & MEMOBJ_REAL) == 0) {
PH7_MemObjToReal(pTos);
}
/* Invalidate any prior representation */
MemObjSetType(pTos, MEMOBJ_REAL);
break;
/*
* CVT_STR: * * *
*
* Force the top of the stack to be a string.
*/
case PH7_OP_CVT_STR:
#ifdef UNTRUST
if(pTos < pStack) {
goto Abort;
}
#endif
if((pTos->nType & MEMOBJ_STRING) == 0) {
PH7_MemObjToString(pTos);
}
break;
/*
* CVT_BOOL: * * *
*
* Force the top of the stack to be a boolean.
*/
case PH7_OP_CVT_BOOL:
#ifdef UNTRUST
if(pTos < pStack) {
goto Abort;
}
#endif
if((pTos->nType & MEMOBJ_BOOL) == 0) {
PH7_MemObjToBool(pTos);
}
break;
/*
* CVT_CHAR: * * *
*
* Force the top of the stack to be a char.
*/
case PH7_OP_CVT_CHAR:
#ifdef UNTRUST
if(pTos < pStack) {
goto Abort;
}
#endif
if((pTos->nType & MEMOBJ_CHAR) == 0) {
PH7_MemObjToChar(pTos);
}
/* Invalidate any prior representation */
MemObjSetType(pTos, MEMOBJ_CHAR);
break;
/*
* CVT_OBJ: * * *
*
* Force the top of the stack to be a class instance (Object in the PHP jargon).
*/
case PH7_OP_CVT_OBJ:
#ifdef UNTRUST
if(pTos < pStack) {
goto Abort;
}
#endif
if((pTos->nType & MEMOBJ_OBJ) == 0) {
/* Force a 'stdClass()' cast */
PH7_MemObjToObject(pTos);
}
break;
/*
* CVT_CALL: * * *
*
* Force the top of the stack to be a callback
*/
case PH7_OP_CVT_CALL:
#ifdef UNTRUST
if(pTos < pStack) {
goto Abort;
}
#endif
PH7_MemObjToCallback(pTos);
break;
/*
* CVT_RES: * * *
*
* Force the top of the stack to be a resource
*/
case PH7_OP_CVT_RES:
#ifdef UNTRUST
if(pTos < pStack) {
goto Abort;
}
#endif
PH7_MemObjToResource(pTos);
break;
/*
* CVT_VOID: * * *
*
* Force the top of the stack to be a void type.
*/
case PH7_OP_CVT_VOID:
#ifdef UNTRUST
if(pTos < pStack) {
goto Abort;
}
#endif
PH7_MemObjToVoid(pTos);
break;
/*
* IS_A * * *
*
* Pop the top two operands from the stack and check whether the first operand
* is an object and is an instance of the second operand (which must be a string
* holding a class name or an object).
* Push TRUE on success. FALSE otherwise.
*/
case PH7_OP_IS_A: {
ph7_value *pNos = &pTos[-1];
sxi32 iRes = 0; /* assume false by default */
#ifdef UNTRUST
if(pNos < pStack) {
goto Abort;
}
#endif
if(pNos->nType & MEMOBJ_OBJ) {
ph7_class_instance *pThis = (ph7_class_instance *)pNos->x.pOther;
ph7_class *pClass = 0;
/* Extract the target class */
if(pTos->nType & MEMOBJ_OBJ) {
/* Instance already loaded */
pClass = ((ph7_class_instance *)pTos->x.pOther)->pClass;
} else if(pTos->nType & MEMOBJ_STRING && SyBlobLength(&pTos->sBlob) > 0) {
/* Perform the query */
pClass = PH7_VmExtractClass(&(*pVm), (const char *)SyBlobData(&pTos->sBlob),
SyBlobLength(&pTos->sBlob), FALSE, 0);
}
if(pClass) {
/* Perform the query */
iRes = VmInstanceOf(pThis->pClass, pClass);
}
}
/* Push result */
VmPopOperand(&pTos, 1);
PH7_MemObjRelease(pTos);
pTos->x.iVal = iRes;
MemObjSetType(pTos, MEMOBJ_BOOL);
break;
}
/*
* DECLARE: P1 P2 P3
*
* Create a constant if P1 is set, or variable otherwise. It takes the constant/variable name
* from the the P3 operand. P2 operand is used to provide a variable type.
*/
case PH7_OP_DECLARE: {
if(pInstr->iP1) {
/* Constant declaration */
ph7_constant_info *pConstInfo = (ph7_constant_info *) pInstr->p3;
rc = PH7_VmRegisterConstant(&(*pVm), &pConstInfo->pName, PH7_VmExpandConstantValue, pConstInfo->pConsCode, FALSE);
if(rc == SXERR_EXISTS) {
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR,
"Redeclaration of ‘%z’ constant", &pConstInfo->pName);
}
} else {
/* Variable declaration */
ph7_value *pObj;
SyString sName;
SyStringInitFromBuf(&sName, pInstr->p3, SyStrlen((const char *)pInstr->p3));
/* Reserve a room for the target object */
pTos++;
/* Create a new variable */
pObj = VmCreateMemObj(&(*pVm), &sName, FALSE);
if(!pObj) {
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR,
"Redeclaration of ‘$%z’ variable", &sName);
}
if(pInstr->iP2 & MEMOBJ_MIXED && (pInstr->iP2 & MEMOBJ_HASHMAP) == 0) {
pObj->nType = MEMOBJ_MIXED | MEMOBJ_VOID;
} else {
if(pInstr->iP2 & MEMOBJ_HASHMAP) {
ph7_hashmap *pMap;
pMap = PH7_NewHashmap(&(*pVm), 0, 0);
if(pMap == 0) {
PH7_VmMemoryError(&(*pVm));
}
pObj->x.pOther = pMap;
}
MemObjSetType(pObj, pInstr->iP2);
}
pTos->nIdx = SXU32_HIGH; /* Mark as constant */
}
break;
}
/*
* LOADC P1 P2 *
*
* Load a constant [i.e: PHP_EOL,PHP_OS,__TIME__,...] indexed at P2 in the constant pool.
* If P1 is set,then this constant is candidate for expansion via user installable callbacks.
*/
case PH7_OP_LOADC: {
ph7_value *pObj;
/* Reserve a room */
pTos++;
if((pObj = (ph7_value *)SySetAt(&pVm->aLitObj, pInstr->iP2)) != 0) {
if(pInstr->iP1 == 1 && SyBlobLength(&pObj->sBlob) <= 64) {
if(pInstr[1].iOp != PH7_OP_MEMBER && pInstr[1].iOp != PH7_OP_NEW) {
/* Point to the top active frame */
VmFrame *pFrame = pVm->pFrame;
while(pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION)) {
/* Safely ignore the exception frame */
pFrame = pFrame->pParent; /* Parent frame */
}
SyHashEntry *pEntry;
/* Candidate for expansion via user defined callbacks */
for(;;) {
pEntry = SyHashGet(&pVm->pFrame->hConst, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob));
if(pEntry == 0 && pFrame->iFlags & VM_FRAME_LOOP && pFrame->pParent) {
pFrame = pFrame->pParent;
} else {
break;
}
}
if(pEntry == 0) {
pEntry = SyHashGet(&pVm->hConstant, SyBlobData(&pObj->sBlob), SyBlobLength(&pObj->sBlob));
}
if(pEntry) {
ph7_constant *pCons = (ph7_constant *)pEntry->pUserData;
/* Set a NULL default value */
MemObjSetType(pTos, MEMOBJ_NULL);
SyBlobReset(&pTos->sBlob);
/* Invoke the callback and deal with the expanded value */
pCons->xExpand(pTos, pCons->pUserData);
/* Mark as constant */
pTos->nIdx = SXU32_HIGH;
break;
} else if(pInstr[2].iOp != PH7_OP_MEMBER && pInstr[2].iOp != PH7_OP_NEW) {
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR,
"Call to undefined constant ‘%s’", SyBlobData(&pObj->sBlob));
}
}
}
PH7_MemObjLoad(pObj, pTos);
} else {
/* Set a NULL value */
MemObjSetType(pTos, MEMOBJ_NULL);
}
/* Mark as constant */
pTos->nIdx = SXU32_HIGH;
break;
}
/*
* LOADV: * * P3
*
* Load a variable where it's name is taken from the top of the stack or
* from the P3 operand.
*/
case PH7_OP_LOADV: {
ph7_value *pObj;
SyString sName;
if(pInstr->p3 == 0) {
/* Take the variable name from the top of the stack */
#ifdef UNTRUST
if(pTos < pStack) {
goto Abort;
}
#endif
/* Force a string cast */
if((pTos->nType & MEMOBJ_STRING) == 0) {
PH7_MemObjToString(pTos);
}
SyStringInitFromBuf(&sName, SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob));
} else {
SyStringInitFromBuf(&sName, pInstr->p3, SyStrlen((const char *)pInstr->p3));
/* Reserve a room for the target object */
pTos++;
}
/* Extract the requested memory object */
pObj = VmExtractMemObj(&(*pVm), &sName, pInstr->p3 ? FALSE : TRUE);
if(pObj == 0) {
/* Fatal error */
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Variable '$%z' undeclared (first use in this method/closure)", &sName);
}
/* Load variable contents */
PH7_MemObjLoad(pObj, pTos);
pTos->nIdx = pObj->nIdx;
break;
}
/*
* LOAD_MAP P1 * *
*
* Allocate a new empty hashmap (array in the PHP jargon) and push it on the stack.
* If the P1 operand is greater than zero then pop P1 elements from the
* stack and insert them (key => value pair) in the new hashmap.
*/
case PH7_OP_LOAD_MAP: {
sxi32 nType, pType;
ph7_hashmap *pMap;
/* Allocate a new hashmap instance */
pMap = PH7_NewHashmap(&(*pVm), 0, 0);
if(pMap == 0) {
PH7_VmMemoryError(&(*pVm));
}
if(pInstr->iP1 > 0) {
ph7_value *pEntry = &pTos[-pInstr->iP1 + 1]; /* Point to the first entry */
nType = pEntry[1].nType; /* Save the type of value */
/* Perform the insertion */
while(pEntry < pTos) {
/* Standard insertion */
PH7_HashmapInsert(pMap,
(pEntry->nType & MEMOBJ_NULL) ? 0 /* Automatic index assign */ : pEntry,
&pEntry[1]
);
/* Set the proper type of array */
if((nType & MEMOBJ_MIXED) == 0) {
pType = pEntry[1].nType;
if(nType != pType && nType != (pType ^ MEMOBJ_HASHMAP)) {
nType = MEMOBJ_MIXED;
}
}
/* Next pair on the stack */
pEntry += 2;
}
/* Pop P1 elements */
VmPopOperand(&pTos, pInstr->iP1);