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.
14988 lines
479 KiB
14988 lines
479 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 */ |
||
}; |
||
/* |
||
* Information about each module library (loaded using [import()] ) |
||
* is stored in an instance of the following structure. |
||
*/ |
||
typedef struct VmModule VmModule; |
||
struct VmModule { |
||
#ifdef __WINNT__ |
||
HINSTANCE pHandle; /* Module handler under Windows */ |
||
#else |
||
void *pHandle; /* Module handler under Unix-like OS */ |
||
#endif |
||
SyString sName; /* Module name */ |
||
SyString sFile; /* Module library file */ |
||
SyString sDesc; /* Module short description */ |
||
ph7_real fVer; /* Module version */ |
||
}; |
||
/* |
||
* Each installed class autoload callback (registered using [register_autoload_handler()] ) |
||
* is stored in an instance of the following structure. |
||
*/ |
||
typedef struct VmAutoLoadCB VmAutoLoadCB; |
||
struct VmAutoLoadCB { |
||
ph7_value sCallback; /* autoload callback */ |
||
ph7_value aArg[1]; /* Callback argument (should really take just a class name */ |
||
}; |
||
/* |
||
* 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; |
||
} |
||
/* 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 = sizeof($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;"\ |
||
" }"\ |
||
" $pArg = array_copy($pArgs[0]);"\ |
||
" reset($pArg);"\ |
||
" $max = current($pArg);"\ |
||
" while( FALSE !== ($val = next($pArg)) ){"\ |
||
" if( $val > $max ){"\ |
||
" $max = $val;"\ |
||
" }"\ |
||
" }"\ |
||
" return $max;"\ |
||
" }"\ |
||
" $max = $pArgs[0];"\ |
||
" for( $i = 1; $i < sizeof($pArgs) ; ++$i ){"\ |
||
" $val = $pArgs[$i];"\ |
||
"if( $val > $max ){"\ |
||
" $max = $val;"\ |
||
"}"\ |
||
" }"\ |
||
" return $max;"\ |
||
"}"\ |
||
"function min(){"\ |
||
" $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;"\ |
||
" }"\ |
||
" $pArg = array_copy($pArgs[0]);"\ |
||
" reset($pArg);"\ |
||
" $min = current($pArg);"\ |
||
" while( FALSE !== ($val = next($pArg)) ){"\ |
||
" if( $val < $min ){"\ |
||
" $min = $val;"\ |
||
" }"\ |
||
" }"\ |
||
" return $min;"\ |
||
" }"\ |
||
" $min = $pArgs[0];"\ |
||
" for( $i = 1; $i < sizeof($pArgs) ; ++$i ){"\ |
||
" $val = $pArgs[$i];"\ |
||
"if( $val < $min ){"\ |
||
" $min = $val;"\ |
||
" }"\ |
||
" }"\ |
||
" return $min;"\ |
||
"}"\ |
||
"function fileowner(string $file){"\ |
||
" $a = stat($file);"\ |
||
" if( !is_array($a) ){"\ |
||
" return false;"\ |
||
" }"\ |
||
" return $a['uid'];"\ |
||
"}"\ |
||
"function filegroup(string $file){"\ |
||
" $a = stat($file);"\ |
||
" if( !is_array($a) ){"\ |
||
" return false;"\ |
||
" }"\ |
||
" return $a['gid'];"\ |
||
"}"\ |
||
"function fileinode(string $file){"\ |
||
" $a = stat($file);"\ |
||
" if( !is_array($a) ){"\ |
||
" return false;"\ |
||
" }"\ |
||
" return $a['ino'];"\ |
||
"}" |
||
|
||
/* |
||
* 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 */ |
||
) { |
||
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->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->sWorker, &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, SyStrnmicmp); |
||
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->aErrCB[0]); |
||
PH7_MemObjInit(&(*pVm), &pVm->aErrCB[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); |
||
/* Compile the built-in library */ |
||
VmEvalChunk(&(*pVm), 0, &sBuiltin, PH7_PHP_ONLY, FALSE); |
||
/* 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 docurmentation 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), PH7_OP_DONE, 0, 0, 0, 0); |
||
if(rc != SXRET_OK) { |
||
return SXERR_MEM; |
||
} |
||
/* Script return value */ |
||
PH7_MemObjInit(&(*pVm), &pVm->sExec); /* Assume a NULL return value */ |
||
/* Allocate a new operand stack */ |
||
pVm->aOps = VmNewOperandStack(&(*pVm), SySetUsed(pVm->pByteContainer)); |
||
if(pVm->aOps == 0) { |
||
return SXERR_MEM; |
||
} |
||
/* Set the default VM output consumer callback and it's |
||
* private data. */ |
||
pVm->sVmConsumer.xConsumer = PH7_VmBlobConsumer; |
||
pVm->sVmConsumer.pUserData = &pVm->sConsumer; |
||
/* 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: is_null(), 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; |
||
} |
||
} |
||
/* Random number betwwen 0 and 1023 used to generate unique ID */ |
||
pVm->unique_id = PH7_VmRandomNum(&(*pVm)) & 1023; |
||
/* 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) { |
||
return SXERR_CORRUPT; |
||
} |
||
/* TICKET 1433-003: As of this version, the VM is automatically reset */ |
||
SyBlobReset(&pVm->sConsumer); |
||
PH7_MemObjRelease(&pVm->sExec); |
||
/* 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(), ph7_context_throw_error() |
||
* 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; |
||
} |
||
/* |
||
* Insert an entry by reference (not copy) in the given hashmap. |
||
*/ |
||
static sxi32 VmHashmapRefInsert( |
||
ph7_hashmap *pMap, /* Target hashmap */ |
||
const char *zKey, /* Entry key */ |
||
sxu32 nByte, /* Key length */ |
||
sxu32 nRefIdx /* Entry index in the object pool */ |
||
) { |
||
ph7_value sKey; |
||
sxi32 rc; |
||
PH7_MemObjInitFromString(pMap->pVm, &sKey, 0); |
||
PH7_MemObjStringAppend(&sKey, zKey, nByte); |
||
/* Perform the insertion */ |
||
rc = PH7_HashmapInsertByRef(&(*pMap), &sKey, nRefIdx); |
||
PH7_MemObjRelease(&sKey); |
||
return rc; |
||
} |
||
/* |
||
* 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 bCreate /* True to create the variable if non-existent */ |
||
) { |
||
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 sAnnon = { " ", sizeof(char) }; |
||
pName = &sAnnon; |
||
/* 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) { |
||
/* Query the top active frame */ |
||
pEntry = SyHashGet(&pFrame->hVar, (const void *)pName->zString, pName->nByte); |
||
if(pEntry == 0) { |
||
char *zName = (char *)pName->zString; |
||
VmSlot sLocal; |
||
if(!bCreate) { |
||
/* Do not create the variable,return NULL instead */ |
||
return 0; |
||
} |
||
/* No such variable,automatically create a new one and install |
||
* it in the current frame. |
||
*/ |
||
pObj = PH7_ReserveMemObj(&(*pVm)); |
||
if(pObj == 0) { |
||
return 0; |
||
} |
||
nIdx = pObj->nIdx; |
||
if(bDup) { |
||
/* Duplicate name */ |
||
zName = SyMemBackendStrDup(&pVm->sAllocator, pName->zString, pName->nByte); |
||
if(zName == 0) { |
||
return 0; |
||
} |
||
} |
||
/* Link to the top active VM frame */ |
||
rc = SyHashInsert(&pFrame->hVar, zName, pName->nByte, SX_INT_TO_PTR(nIdx)); |
||
if(rc != SXRET_OK) { |
||
/* Return the slot to the free pool */ |
||
sLocal.nIdx = nIdx; |
||
sLocal.pUserData = 0; |
||
SySetPut(&pVm->aFreeObj, (const void *)&sLocal); |
||
return 0; |
||
} |
||
if(pFrame->pParent != 0) { |
||
/* Local variable */ |
||
sLocal.nIdx = nIdx; |
||
SySetPut(&pFrame->sLocal, (const void *)&sLocal); |
||
} else { |
||
/* Register in the $GLOBALS array */ |
||
VmHashmapRefInsert(pVm->pGlobal, pName->zString, pName->nByte, nIdx); |
||
} |
||
/* Install in the reference table */ |
||
PH7_VmRefObjInstall(&(*pVm), nIdx, SyHashLastEntry(&pFrame->hVar), 0, 0); |
||
/* Save object index */ |
||
pObj->nIdx = nIdx; |
||
} else { |
||
/* Extract variable contents */ |
||
nIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData); |
||
pObj = (ph7_value *)SySetAt(&pVm->aMemObj, nIdx); |
||
if(bNullify && pObj) { |
||
PH7_MemObjRelease(pObj); |
||
} |
||
} |
||
} else { |
||
/* 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 conatiner */ |
||
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_OUTPUT_LENGTH: { |
||
/* VM output length in bytes */ |
||
sxu32 *pOut = va_arg(ap, sxu32 *); |
||
#ifdef UNTRUST |
||
if(pOut == 0) { |
||
rc = SXERR_CORRUPT; |
||
break; |
||
} |
||
#endif |
||
*pOut = pVm->nOutputLen; |
||
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); |
||
if(nOp == PH7_VM_CONFIG_CREATE_SUPER || pVm->pFrame->pParent == 0) { |
||
/* Register in the $GLOBALS array */ |
||
VmHashmapRefInsert(pVm->pGlobal, zName, nByte, nIdx); |
||
} |
||
} |
||
} |
||
break; |
||
} |
||
case PH7_VM_CONFIG_SERVER_ATTR: |
||
case PH7_VM_CONFIG_ENV_ATTR: |
||
case PH7_VM_CONFIG_SESSION_ATTR: |
||
case PH7_VM_CONFIG_POST_ATTR: |
||
case PH7_VM_CONFIG_GET_ATTR: |
||
case PH7_VM_CONFIG_COOKIE_ATTR: |
||
case PH7_VM_CONFIG_HEADER_ATTR: { |
||
const char *zKey = va_arg(ap, const char *); |
||
const char *zValue = va_arg(ap, const char *); |
||
int nLen = va_arg(ap, int); |
||
ph7_hashmap *pMap; |
||
ph7_value *pValue; |
||
if(nOp == PH7_VM_CONFIG_ENV_ATTR) { |
||
/* Extract the $_ENV superglobal */ |
||
pValue = VmExtractSuper(&(*pVm), "_ENV", sizeof("_ENV") - 1); |
||
} else if(nOp == PH7_VM_CONFIG_POST_ATTR) { |
||
/* Extract the $_POST superglobal */ |
||
pValue = VmExtractSuper(&(*pVm), "_POST", sizeof("_POST") - 1); |
||
} else if(nOp == PH7_VM_CONFIG_GET_ATTR) { |
||
/* Extract the $_GET superglobal */ |
||
pValue = VmExtractSuper(&(*pVm), "_GET", sizeof("_GET") - 1); |
||
} else if(nOp == PH7_VM_CONFIG_COOKIE_ATTR) { |
||
/* Extract the $_COOKIE superglobal */ |
||
pValue = VmExtractSuper(&(*pVm), "_COOKIE", sizeof("_COOKIE") - 1); |
||
} else if(nOp == PH7_VM_CONFIG_SESSION_ATTR) { |
||
/* Extract the $_SESSION superglobal */ |
||
pValue = VmExtractSuper(&(*pVm), "_SESSION", sizeof("_SESSION") - 1); |
||
} else if(nOp == PH7_VM_CONFIG_HEADER_ATTR) { |
||
/* Extract the $_HEADER superglobale */ |
||
pValue = VmExtractSuper(&(*pVm), "_HEADER", sizeof("_HEADER") - 1); |
||
} else { |
||
/* Extract the $_SERVER superglobal */ |
||
pValue = VmExtractSuper(&(*pVm), "_SERVER", sizeof("_SERVER") - 1); |
||
} |
||
if(pValue == 0 || (pValue->iFlags & MEMOBJ_HASHMAP) == 0) { |
||
/* No such entry */ |
||
rc = SXERR_NOTFOUND; |
||
break; |
||
} |
||
/* Point to the hashmap */ |
||
pMap = (ph7_hashmap *)pValue->x.pOther; |
||
/* Perform the insertion */ |
||
rc = VmHashmapInsert(pMap, zKey, -1, zValue, nLen); |
||
break; |
||
} |
||
case PH7_VM_CONFIG_ARGV_ENTRY: { |
||
/* Script arguments */ |
||
const char *zValue = va_arg(ap, const char *); |
||
ph7_hashmap *pMap; |
||
ph7_value *pValue; |
||
sxu32 n; |
||
if(SX_EMPTY_STR(zValue)) { |
||
rc = SXERR_EMPTY; |
||
break; |
||
} |
||
/* Extract the $argv array */ |
||
pValue = VmExtractSuper(&(*pVm), "argv", sizeof("argv") - 1); |
||
if(pValue == 0 || (pValue->iFlags & MEMOBJ_HASHMAP) == 0) { |
||
/* No such entry */ |
||
rc = SXERR_NOTFOUND; |
||
break; |
||
} |
||
/* Point to the hashmap */ |
||
pMap = (ph7_hashmap *)pValue->x.pOther; |
||
/* Perform the insertion */ |
||
n = (sxu32)SyStrlen(zValue); |
||
rc = VmHashmapInsert(pMap, 0, 0, zValue, (int)n); |
||
if(rc == SXRET_OK) { |
||
if(pMap->nEntry > 1) { |
||
/* Append space separator first */ |
||
SyBlobAppend(&pVm->sArgv, (const void *)" ", sizeof(char)); |
||
} |
||
SyBlobAppend(&pVm->sArgv, (const void *)zValue, n); |
||
} |
||
break; |
||
} |
||
case PH7_VM_CONFIG_ERR_LOG_HANDLER: { |
||
/* error_log() consumer */ |
||
ProcErrLog xErrLog = va_arg(ap, ProcErrLog); |
||
pVm->xErrLog = xErrLog; |
||
break; |
||
} |
||
case PH7_VM_CONFIG_EXEC_VALUE: { |
||
/* Script return value */ |
||
ph7_value **ppValue = va_arg(ap, ph7_value **); |
||
#ifdef UNTRUST |
||
if(ppValue == 0) { |
||
rc = SXERR_CORRUPT; |
||
break; |
||
} |
||
#endif |
||
*ppValue = &pVm->sExec; |
||
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 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" |
||
"PH7 VM Dump Copyright (C) 2011-2012 Symisc Systems\n" |
||
" http://www.symisc.net/\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 = 0; |
||
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, "%s %8d %8u %#8x [%u]\n", |
||
VmInstrToString(pInstr->iOp), pInstr->iP1, pInstr->iP2, |
||
SX_PTR_TO_INT(pInstr->p3), n); |
||
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); |
||
if(pCons->xConsumer != VmObConsumer) { |
||
/* Increment output length */ |
||
pVm->nOutputLen += SyBlobLength(pMsg); |
||
} |
||
return rc; |
||
} |
||
/* |
||
* Throw a run-time error and invoke the supplied VM output consumer callback. |
||
* Refer to the implementation of [ph7_context_throw_error()] for additional |
||
* information. |
||
*/ |
||
PH7_PRIVATE sxi32 PH7_VmThrowError( |
||
ph7_vm *pVm, /* Target VM */ |
||
SyString *pFuncName, /* Function name. NULL otherwise */ |
||
sxi32 iErr, /* Severity level: [i.e: Error,Warning or Notice]*/ |
||
const char *zMessage /* Null terminated error message */ |
||
) { |
||
SyBlob *pWorker = &pVm->sWorker; |
||
SyString *pFile; |
||
const char *zErr; |
||
sxi32 rc; |
||
if(!pVm->bErrReport) { |
||
/* Don't bother reporting errors */ |
||
return SXRET_OK; |
||
} |
||
/* Reset the working buffer */ |
||
SyBlobReset(pWorker); |
||
/* Peek the processed file if available */ |
||
pFile = (SyString *)SySetPeek(&pVm->aFiles); |
||
if(pFile) { |
||
/* Append file name */ |
||
SyBlobAppend(pWorker, pFile->zString, pFile->nByte); |
||
SyBlobAppend(pWorker, (const void *)" ", sizeof(char)); |
||