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.

14871 lines
460 KiB

/*
* Symisc PH7: An embeddable bytecode compiler and a virtual machine for the PHP(5) programming language.
* Copyright (C) 2011-2012, Symisc Systems http://ph7.symisc.net/
* Version 2.1.4
* For information on licensing,redistribution of this file,and for a DISCLAIMER OF ALL WARRANTIES
* please contact Symisc Systems via:
* legal@symisc.net
* licensing@symisc.net
* contact@symisc.net
* or visit:
* http://ph7.symisc.net/
*/
/* $SymiscID: vm.c v1.4 FreeBSD 2012-09-10 00:06 stable <chm@symisc.net> $ */
#include "ph7int.h"
/*
* The code in this file implements execution method of the PH7 Virtual Machine.
* The PH7 compiler (implemented in 'compiler.c' and 'parse.c') generates a bytecode program
* which is then executed by the virtual machine implemented here to do the work of the PHP
* statements.
* PH7 bytecode programs are similar in form to assembly language. The program consists
* of a linear sequence of operations .Each operation has an opcode and 3 operands.
* Operands P1 and P2 are integers where the first is signed while the second is unsigned.
* Operand P3 is an arbitrary pointer specific to each instruction. The P2 operand is usually
* the jump destination used by the OP_JMP,OP_JZ,OP_JNZ,... instructions.
* Opcodes will typically ignore one or more operands. Many opcodes ignore all three operands.
* Computation results are stored on a stack. Each entry on the stack is of type ph7_value.
* PH7 uses the ph7_value object to represent all values that can be stored in a PHP variable.
* Since PHP uses dynamic typing for the values it stores. Values stored in ph7_value objects
* can be integers,floating point values,strings,arrays,class instances (object in the PHP jargon)
* and so on.
* Internally,the PH7 virtual machine manipulates nearly all PHP values as ph7_values structures.
* Each ph7_value may cache multiple representations(string,integer etc.) of the same value.
* An implicit conversion from one type to the other occurs as necessary.
* Most of the code in this file is taken up by the [VmByteCodeExec()] function which does
* the work of interpreting a PH7 bytecode program. But other routines are also provided
* to help in building up a program instruction by instruction. Also note that sepcial
* functions that need access to the underlying virtual machine details such as [die()],
* [func_get_args()],[call_user_func()],[ob_start()] and many more are implemented here.
*/
/*
* Each active virtual machine frame is represented by an instance
* of the following structure.
* VM Frame hold local variables and other stuff related to function call.
*/
struct VmFrame
{
VmFrame *pParent; /* Parent frame or NULL if global scope */
void *pUserData; /* Upper layer private data associated with this frame */
ph7_class_instance *pThis; /* Current class instance [i.e: the '$this' variable].NULL otherwise */
SySet sLocal; /* Local variables container (VmSlot instance) */
ph7_vm *pVm; /* VM that own this frame */
SyHash hVar; /* Variable hashtable for fast lookup */
SySet sArg; /* Function arguments container */
SySet sRef; /* Local reference table (VmSlot instance) */
sxi32 iFlags; /* Frame configuration flags (See below)*/
sxu32 iExceptionJump; /* Exception jump destination */
};
#define VM_FRAME_EXCEPTION 0x01 /* Special Exception frame */
#define VM_FRAME_THROW 0x02 /* An exception was thrown */
#define VM_FRAME_CATCH 0x04 /* Catch frame */
/*
* When a user defined variable is released (via manual unset($x) or garbage collected)
* memory object index is stored in an instance of the following structure and put
* in the free object table so that it can be reused again without allocating
* a new memory object.
*/
typedef struct VmSlot VmSlot;
struct VmSlot
{
sxu32 nIdx; /* Index in pVm->aMemObj[] */
void *pUserData; /* Upper-layer private data */
};
/*
* An entry in the reference table is represented by an instance of the
* follwoing table.
* The implementation of the reference mechanism in the PH7 engine
* differ greatly from the one used by the zend engine. That is,
* the reference implementation is consistent,solid and it's
* behavior resemble the C++ reference mechanism.
* Refer to the official for more information on this powerful
* extension.
*/
struct VmRefObj
{
SySet aReference; /* Table of references to this memory object */
SySet aArrEntries; /* Foreign hashmap entries [i.e: array(&$a) ] */
sxu32 nIdx; /* Referenced object index */
sxi32 iFlags; /* Configuration flags */
VmRefObj *pNextCollide,*pPrevCollide; /* Collision link */
VmRefObj *pNext,*pPrev; /* List of all referenced objects */
};
#define VM_REF_IDX_KEEP 0x001 /* Do not restore the memory object to the free list */
/*
* Output control buffer entry.
* Refer to the implementation of [ob_start()] for more information.
*/
typedef struct VmObEntry VmObEntry;
struct VmObEntry
{
ph7_value sCallback; /* User defined callback */
SyBlob sOB; /* Output buffer consumer */
};
/*
* Each installed shutdown callback (registered using [register_shutdown_function()] )
* is stored in an instance of the following structure.
* Refer to the implementation of [register_shutdown_function(()] for more information.
*/
typedef struct VmShutdownCB VmShutdownCB;
struct VmShutdownCB
{
ph7_value sCallback; /* Shutdown callback */
ph7_value aArg[10]; /* Callback arguments (10 maximum arguments) */
int nArg; /* Total number of given arguments */
};
/* Uncaught exception code value */
#define PH7_EXCEPTION -255
/*
* 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_OTHR 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() */
)
{
ph7_constant *pCons;
SyHashEntry *pEntry;
char *zDupName;
sxi32 rc;
pEntry = SyHashGet(&pVm->hConstant,(const void *)pName->zString,pName->nByte);
if( pEntry ){
/* Overwrite the old definition and return immediately */
pCons = (ph7_constant *)pEntry->pUserData;
pCons->xExpand = xExpand;
pCons->pUserData = pUserData;
return SXRET_OK;
}
/* Allocate a new constant instance */
pCons = (ph7_constant *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(ph7_constant));
if( pCons == 0 ){
return 0;
}
/* Duplicate constant name */
zDupName = SyMemBackendStrDup(&pVm->sAllocator,pName->zString,pName->nByte);
if( zDupName == 0 ){
SyMemBackendPoolFree(&pVm->sAllocator,pCons);
return 0;
}
/* Install the constant */
SyStringInitFromBuf(&pCons->sName,zDupName,pName->nByte);
pCons->xExpand = xExpand;
pCons->pUserData = pUserData;
rc = SyHashInsert(&pVm->hConstant,(const void *)zDupName,SyStringLength(&pCons->sName),pCons);
if( rc != SXRET_OK ){
SyMemBackendFree(&pVm->sAllocator,zDupName);
SyMemBackendPoolFree(&pVm->sAllocator,pCons);
return rc;
}
/* 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 */
ProchHostFunction 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 */
ProchHostFunction 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 Fucntion */
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_class *pLink = (ph7_class *)pEntry->pUserData;
/* Link entry with the same name */
pClass->pNextName = pLink;
pEntry->pUserData = pClass;
return SXRET_OK;
}
pClass->pNextName = 0;
/* 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 */
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;
/* Fill the VM instruction */
sInstr.iOp = (sxu8)iOp;
sInstr.iP1 = iP1;
sInstr.iP2 = iP2;
sInstr.p3 = p3;
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->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;
}
/*
* Link a foreign variable with the TOP most active frame.
* Refer to the PH7_OP_UPLINK instruction implementation for more
* information.
*/
static sxi32 VmFrameLink(ph7_vm *pVm,SyString *pName)
{
VmFrame *pTarget,*pFrame;
SyHashEntry *pEntry = 0;
sxi32 rc;
/* Point to the upper frame */
pFrame = pVm->pFrame;
while( pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) ){
/* Safely ignore the exception frame */
pFrame = pFrame->pParent;
}
pTarget = pFrame;
pFrame = pTarget->pParent;
while( pFrame ){
if( (pFrame->iFlags & VM_FRAME_EXCEPTION) == 0 ){
/* Query the current frame */
pEntry = SyHashGet(&pFrame->hVar,(const void *)pName->zString,pName->nByte);
if( pEntry ){
/* Variable found */
break;
}
}
/* Point to the upper frame */
pFrame = pFrame->pParent;
}
if( pEntry == 0 ){
/* Inexistant variable */
return SXERR_NOTFOUND;
}
/* Link to the current frame */
rc = SyHashInsert(&pTarget->hVar,pEntry->pKey,pEntry->nKeyLen,pEntry->pUserData);
if( rc == SXRET_OK ){
sxu32 nIdx;
nIdx = SX_PTR_TO_INT(pEntry->pUserData);
PH7_VmRefObjInstall(&(*pVm),nIdx,SyHashLastEntry(&pTarget->hVar),0,0);
}
return rc;
}
/*
* 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->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);
}
/*
* 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,iCur,iMax;
ph7_vm_func *apSet[10]; /* Maximum number of candidates */
ph7_vm_func *pLink;
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;
}
if( (int)SySetUsed(&pLink->aArgs) == nArg ){
/* Candidate for overloading */
apSet[i++] = pLink;
}
/* Point to the next entry */
pLink = pLink->pNextName;
}
if( i < 1 ){
/* No candidates,return the head of the list */
return pList;
}
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_HASHMAP ){
/* Hashmap */
c = 'h';
}else if( aArg[j].iFlags & MEMOBJ_BOOL ){
/* bool */
c = 'b';
}else if( aArg[j].iFlags & MEMOBJ_INT ){
/* int */
c = 'i';
}else if( aArg[j].iFlags & MEMOBJ_STRING ){
/* String */
c = 's';
}else if( aArg[j].iFlags & MEMOBJ_REAL ){
/* Float */
c = 'f';
}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;
}
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];
}
/* Forward declaration */
static sxi32 VmLocalExec(ph7_vm *pVm,SySet *pByteCode,ph7_value *pResult);
static sxi32 VmErrorFormat(ph7_vm *pVm,sxi32 iErr,const char *zFormat,...);
/*
* Mount a compiled class into the freshly created vitual 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;
/* Reserve a memory object for this constant/static attribute */
pMemObj = PH7_ReserveMemObj(&(*pVm));
if( pMemObj == 0 ){
VmErrorFormat(&(*pVm),PH7_CTX_ERR,
"Cannot reserve a memory object for class attribute '%z->%z' due to a memory failure",
&pClass->sName,&pAttr->sName
);
return SXERR_MEM;
}
if( SySetUsed(&pAttr->aByteCode) > 0 ){
/* Initialize attribute default value (any complex expression) */
VmLocalExec(&(*pVm),&pAttr->aByteCode,pMemObj);
}
/* 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;
}
/* Create constructor alias if not yet done */
if( SyHashGet(&pClass->hMethod,"__construct",sizeof("__construct")-1) == 0 ){
/* User constructor with the same base class name */
pEntry = SyHashGet(&pClass->hMethod,SyStringData(&pClass->sName),SyStringLength(&pClass->sName));
if( pEntry ){
pMeth = (ph7_class_method *)pEntry->pUserData;
/* Create the alias */
SyHashInsert(&pClass->hMethod,"__construct",sizeof("__construct")-1,pMeth);
}
}
/* 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_ABSTRACT) == 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;
/* Reserve a memory object for this attribute */
pMemObj = PH7_ReserveMemObj(&(*pVm));
if( pMemObj == 0 ){
SyMemBackendPoolFree(&pVm->sAllocator,pVmAttr);
return SXERR_MEM;
}
pVmAttr->nIdx = pMemObj->nIdx;
if( SySetUsed(&pAttr->aByteCode) > 0 ){
/* Initialize attribute default value (any complex expression) */
VmLocalExec(&(*pVm),&pAttr->aByteCode,pMemObj);
}
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,int bTrueReturn);
/*
* Built-in classes/interfaces and some functions that cannot be implemented
* directly as foreign functions.
*/
#define PH7_BUILTIN_LIB \
"class Exception { "\
"protected $message = 'Unknown exception';"\
"protected $code = 0;"\
"protected $file;"\
"protected $line;"\
"protected $trace;"\
"protected $previous;"\
"public function __construct($message = null, $code = 0, Exception $previous = null){"\
" if( isset($message) ){"\
" $this->message = $message;"\
" }"\
" $this->code = $code;"\
" $this->file = __FILE__;"\
" $this->line = __LINE__;"\
" $this->trace = debug_backtrace();"\
" if( isset($previous) ){"\
" $this->previous = $previous;"\
" }"\
"}"\
"public function getMessage(){"\
" return $this->message;"\
"}"\
" public function getCode(){"\
" return $this->code;"\
"}"\
"public function getFile(){"\
" return $this->file;"\
"}"\
"public function getLine(){"\
" return $this->line;"\
"}"\
"public function getTrace(){"\
" return $this->trace;"\
"}"\
"public function getTraceAsString(){"\
" return debug_string_backtrace();"\
"}"\
"public function getPrevious(){"\
" return $this->previous;"\
"}"\
"public function __toString(){"\
" return $this->file.' '.$this->line.' '.$this->code.' '.$this->message;"\
"}"\
"}"\
"class ErrorException extends Exception { "\
"protected $severity;"\
"public function __construct(string $message = null,"\
"int $code = 0,int $severity = 1,string $filename = __FILE__ ,int $lineno = __LINE__ ,Exception $previous = null){"\
" if( isset($message) ){"\
" $this->message = $message;"\
" }"\
" $this->severity = $severity;"\
" $this->code = $code;"\
" $this->file = $filename;"\
" $this->line = $lineno;"\
" $this->trace = debug_backtrace();"\
" if( isset($previous) ){"\
" $this->previous = $previous;"\
" }"\
"}"\
"public function getSeverity(){"\
" return $this->severity;"\
"}"\
"}"\
"interface Iterator {"\
"public function current();"\
"public function key();"\
"public function next();"\
"public function rewind();"\
"public function valid();"\
"}"\
"interface IteratorAggregate {"\
"public function getIterator();"\
"}"\
"interface Serializable {"\
"public function serialize();"\
"public function unserialize(string $serialized);"\
"}"\
"/* Directory releated IO */"\
"class Directory {"\
"public $handle = null;"\
"public $path = null;"\
"public function __construct(string $path)"\
"{"\
" $this->handle = opendir($path);"\
" if( $this->handle !== FALSE ){"\
" $this->path = $path;"\
" }"\
"}"\
"public function __destruct()"\
"{"\
" if( $this->handle != null ){"\
" closedir($this->handle);"\
" }"\
"}"\
"public function read()"\
"{"\
" return readdir($this->handle);"\
"}"\
"public function rewind()"\
"{"\
" rewinddir($this->handle);"\
"}"\
"public function close()"\
"{"\
" closedir($this->handle);"\
" $this->handle = null;"\
"}"\
"}"\
"class stdClass{"\
" public $value;"\
" /* Magic methods */"\
" public function __toInt(){ return (int)$this->value; }"\
" public function __toBool(){ return (bool)$this->value; }"\
" public function __toFloat(){ return (float)$this->value; }"\
" public function __toString(){ return (string)$this->value; }"\
" function __construct($v){ $this->value = $v; }"\
"}"\
"function dir(string $path){"\
" return new Directory($path);"\
"}"\
"function Dir(string $path){"\
" return new Directory($path);"\
"}"\
"function scandir(string $directory,int $sort_order = SCANDIR_SORT_ASCENDING)"\
"{"\
" if( func_num_args() < 1 ){ return FALSE; }"\
" $aDir = array();"\
" $pHandle = opendir($directory);"\
" if( $pHandle == FALSE ){ return FALSE; }"\
" while(FALSE !== ($pEntry = readdir($pHandle)) ){"\
" $aDir[] = $pEntry;"\
" }"\
" closedir($pHandle);"\
" if( $sort_order == SCANDIR_SORT_DESCENDING ){"\
" rsort($aDir);"\
" }else if( $sort_order == SCANDIR_SORT_ASCENDING ){"\
" sort($aDir);"\
" }"\
" return $aDir;"\
"}"\
"function glob(string $pattern,int $iFlags = 0){"\
"/* Open the target directory */"\
"$zDir = dirname($pattern);"\
"if(!is_string($zDir) ){ $zDir = './'; }"\
"$pHandle = opendir($zDir);"\
"if( $pHandle == FALSE ){"\
" /* IO error while opening the current directory,return FALSE */"\
" return FALSE;"\
"}"\
"$pattern = basename($pattern);"\
"$pArray = array(); /* Empty array */"\
"/* Loop throw available entries */"\
"while( FALSE !== ($pEntry = readdir($pHandle)) ){"\
" /* Use the built-in strglob function which is a Symisc eXtension for wildcard comparison*/"\
" $rc = strglob($pattern,$pEntry);"\
" if( $rc ){"\
" if( is_dir($pEntry) ){"\
" if( $iFlags & GLOB_MARK ){"\
" /* Adds a slash to each directory returned */"\
" $pEntry .= DIRECTORY_SEPARATOR;"\
" }"\
" }else if( $iFlags & GLOB_ONLYDIR ){"\
" /* Not a directory,ignore */"\
" continue;"\
" }"\
" /* Add the entry */"\
" $pArray[] = $pEntry;"\
" }"\
" }"\
"/* Close the handle */"\
"closedir($pHandle);"\
"if( ($iFlags & GLOB_NOSORT) == 0 ){"\
" /* Sort the array */"\
" sort($pArray);"\
"}"\
"if( ($iFlags & GLOB_NOCHECK) && sizeof($pArray) < 1 ){"\
" /* Return the search pattern if no files matching were found */"\
" $pArray[] = $pattern;"\
"}"\
"/* Return the created array */"\
"return $pArray;"\
"}"\
"/* Creates a temporary file */"\
"function tmpfile(){"\
" /* Extract the temp directory */"\
" $zTempDir = sys_get_temp_dir();"\
" if( strlen($zTempDir) < 1 ){"\
" /* Use the current dir */"\
" $zTempDir = '.';"\
" }"\
" /* Create the file */"\
" $pHandle = fopen($zTempDir.DIRECTORY_SEPARATOR.'PH7'.rand_str(12),'w+');"\
" return $pHandle;"\
"}"\
"/* Creates a temporary filename */"\
"function tempnam(string $zDir = sys_get_temp_dir() /* Symisc eXtension */,string $zPrefix = 'PH7')"\
"{"\
" return $zDir.DIRECTORY_SEPARATOR.$zPrefix.rand_str(12);"\
"}"\
"function array_unshift(&$pArray ){"\
" if( func_num_args() < 1 || !is_array($pArray) ){ return 0; }"\
"/* Copy arguments */"\
"$nArgs = func_num_args();"\
"$pNew = array();"\
"for( $i = 1 ; $i < $nArgs ; ++$i ){"\
" $pNew[] = func_get_arg($i);"\
"}"\
"/* Make a copy of the old entries */"\
"$pOld = array_copy($pArray);"\
"/* Erase */"\
"array_erase($pArray);"\
"/* Unshift */"\
"$pArray = array_merge($pNew,$pOld);"\
"return sizeof($pArray);"\
"}"\
"function array_merge_recursive($array1, $array2){"\
"if( func_num_args() < 1 ){ return NULL; }"\
"$arrays = func_get_args();"\
"$narrays = count($arrays);"\
"$ret = $arrays[0];"\
"for ($i = 1; $i < $narrays; $i++) {"\
" if( array_same($ret,$arrays[$i]) ){ /* Same instance */continue;}"\
" foreach ($arrays[$i] as $key => $value) {"\
" if (((string) $key) === ((string) intval($key))) {"\
" $ret[] = $value;"\
" }else{"\
" if (is_array($value) && isset($ret[$key]) ) {"\
" $ret[$key] = array_merge_recursive($ret[$key], $value);"\
" }else {"\
" $ret[$key] = $value;"\
" }"\
" }"\
" }"\
"}"\
" return $ret;"\
"}"\
"function max(){"\
" $pArgs = func_get_args();"\
" if( sizeof($pArgs) < 1 ){"\
" return null;"\
" }"\
" if( sizeof($pArgs) < 2 ){"\
" $pArg = $pArgs[0];"\
" if( !is_array($pArg) ){"\
" return $pArg; "\
" }"\
" if( sizeof($pArg) < 1 ){"\
" return null;"\