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.
12842 lines
403 KiB
12842 lines
403 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 special
|
|
* 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
|
|
* following 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 Function */
|
|
const char *zName, /* Function name */
|
|
sxu32 nByte, /* zName length */
|
|
sxi32 iFlags, /* Configuration flags */
|
|
void *pUserData /* Function private data */
|
|
) {
|
|
/* Zero the structure */
|
|
SyZero(pFunc, sizeof(ph7_vm_func));
|
|
/* Initialize structure fields */
|
|
/* Arguments container */
|
|
SySetInit(&pFunc->aArgs, &pVm->sAllocator, sizeof(ph7_vm_func_arg));
|
|
/* Static variable container */
|
|
SySetInit(&pFunc->aStatic, &pVm->sAllocator, sizeof(ph7_vm_func_static_var));
|
|
/* Bytecode container */
|
|
SySetInit(&pFunc->aByteCode, &pVm->sAllocator, sizeof(VmInstr));
|
|
/* Preallocate some instruction slots */
|
|
SySetAlloc(&pFunc->aByteCode, 0x10);
|
|
/* Closure environment */
|
|
SySetInit(&pFunc->aClosureEnv, &pVm->sAllocator, sizeof(ph7_vm_func_closure_env));
|
|
pFunc->iFlags = iFlags;
|
|
pFunc->pUserData = pUserData;
|
|
SyStringInitFromBuf(&pFunc->sName, zName, nByte);
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Install a user defined function in the corresponding VM container.
|
|
*/
|
|
PH7_PRIVATE sxi32 PH7_VmInstallUserFunction(
|
|
ph7_vm *pVm, /* Target VM */
|
|
ph7_vm_func *pFunc, /* Target function */
|
|
SyString *pName /* Function name */
|
|
) {
|
|
SyHashEntry *pEntry;
|
|
sxi32 rc;
|
|
if(pName == 0) {
|
|
/* Use the built-in name */
|
|
pName = &pFunc->sName;
|
|
}
|
|
/* Check for duplicates (functions with the same name) first */
|
|
pEntry = SyHashGet(&pVm->hFunction, pName->zString, pName->nByte);
|
|
if(pEntry) {
|
|
ph7_vm_func *pLink = (ph7_vm_func *)pEntry->pUserData;
|
|
if(pLink != pFunc) {
|
|
/* Link */
|
|
pFunc->pNextName = pLink;
|
|
pEntry->pUserData = pFunc;
|
|
}
|
|
return SXRET_OK;
|
|
}
|
|
/* First time seen */
|
|
pFunc->pNextName = 0;
|
|
rc = SyHashInsert(&pVm->hFunction, pName->zString, pName->nByte, pFunc);
|
|
return rc;
|
|
}
|
|
/*
|
|
* Install a user defined class in the corresponding VM container.
|
|
*/
|
|
PH7_PRIVATE sxi32 PH7_VmInstallClass(
|
|
ph7_vm *pVm, /* Target VM */
|
|
ph7_class *pClass /* Target Class */
|
|
) {
|
|
SyString *pName = &pClass->sName;
|
|
SyHashEntry *pEntry;
|
|
sxi32 rc;
|
|
/* Check for duplicates */
|
|
pEntry = SyHashGet(&pVm->hClass, (const void *)pName->zString, pName->nByte);
|
|
if(pEntry) {
|
|
PH7_VmThrowError(&(*pVm), 0, PH7_CTX_ERR, "Cannot declare class, because the name is already in use");
|
|
}
|
|
/* Perform a simple hashtable insertion */
|
|
rc = SyHashInsert(&pVm->hClass, (const void *)pName->zString, pName->nByte, pClass);
|
|
return rc;
|
|
}
|
|
/*
|
|
* Instruction builder interface.
|
|
*/
|
|
PH7_PRIVATE sxi32 PH7_VmEmitInstr(
|
|
ph7_vm *pVm, /* Target VM */
|
|
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;
|
|
sInstr.iLine = 1;
|
|
if(pVm->sCodeGen.pEnd && pVm->sCodeGen.pEnd->nLine > 0) {
|
|
sInstr.iLine = pVm->sCodeGen.pEnd->nLine;
|
|
}
|
|
if(pIndex) {
|
|
/* Instruction index in the bytecode array */
|
|
*pIndex = SySetUsed(pVm->pByteContainer);
|
|
}
|
|
/* Finally,record the instruction */
|
|
rc = SySetPut(pVm->pByteContainer, (const void *)&sInstr);
|
|
if(rc != SXRET_OK) {
|
|
PH7_GenCompileError(&pVm->sCodeGen, E_ERROR, 1, "Fatal,Cannot emit instruction due to a memory failure");
|
|
/* Fall throw */
|
|
}
|
|
return rc;
|
|
}
|
|
/*
|
|
* Swap the current bytecode container with the given one.
|
|
*/
|
|
PH7_PRIVATE sxi32 PH7_VmSetByteCodeContainer(ph7_vm *pVm, SySet *pContainer) {
|
|
if(pContainer == 0) {
|
|
/* Point to the default container */
|
|
pVm->pByteContainer = &pVm->aByteCode;
|
|
} else {
|
|
/* Change container */
|
|
pVm->pByteContainer = &(*pContainer);
|
|
}
|
|
return SXRET_OK;
|
|
}
|
|
/*
|
|
* Return the current bytecode container.
|
|
*/
|
|
PH7_PRIVATE SySet *PH7_VmGetByteCodeContainer(ph7_vm *pVm) {
|
|
return pVm->pByteContainer;
|
|
}
|
|
/*
|
|
* Extract the VM instruction rooted at nIndex.
|
|
*/
|
|
PH7_PRIVATE VmInstr *PH7_VmGetInstr(ph7_vm *pVm, sxu32 nIndex) {
|
|
VmInstr *pInstr;
|
|
pInstr = (VmInstr *)SySetAt(pVm->pByteContainer, nIndex);
|
|
return pInstr;
|
|
}
|
|
/*
|
|
* Return the total number of VM instructions recorded so far.
|
|
*/
|
|
PH7_PRIVATE sxu32 PH7_VmInstrLength(ph7_vm *pVm) {
|
|
return SySetUsed(pVm->pByteContainer);
|
|
}
|
|
/*
|
|
* Pop the last VM instruction.
|
|
*/
|
|
PH7_PRIVATE VmInstr *PH7_VmPopInstr(ph7_vm *pVm) {
|
|
return (VmInstr *)SySetPop(pVm->pByteContainer);
|
|
}
|
|
/*
|
|
* Peek the last VM instruction.
|
|
*/
|
|
PH7_PRIVATE VmInstr *PH7_VmPeekInstr(ph7_vm *pVm) {
|
|
return (VmInstr *)SySetPeek(pVm->pByteContainer);
|
|
}
|
|
PH7_PRIVATE VmInstr *PH7_VmPeekNextInstr(ph7_vm *pVm) {
|
|
VmInstr *aInstr;
|
|
sxu32 n;
|
|
n = SySetUsed(pVm->pByteContainer);
|
|
if(n < 2) {
|
|
return 0;
|
|
}
|
|
aInstr = (VmInstr *)SySetBasePtr(pVm->pByteContainer);
|
|
return &aInstr[n - 2];
|
|
}
|
|
/*
|
|
* Allocate a new virtual machine frame.
|
|
*/
|
|
static VmFrame *VmNewFrame(
|
|
ph7_vm *pVm, /* Target VM */
|
|
void *pUserData, /* Upper-layer private data */
|
|
ph7_class_instance *pThis /* Top most class instance [i.e: Object in the PHP jargon]. NULL otherwise */
|
|
) {
|
|
VmFrame *pFrame;
|
|
/* Allocate a new vm frame */
|
|
pFrame = (VmFrame *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(VmFrame));
|
|
if(pFrame == 0) {
|
|
return 0;
|
|
}
|
|
/* Zero the structure */
|
|
SyZero(pFrame, sizeof(VmFrame));
|
|
/* Initialize frame fields */
|
|
pFrame->pUserData = pUserData;
|
|
pFrame->pThis = pThis;
|
|
pFrame->pVm = pVm;
|
|
SyHashInit(&pFrame->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) {
|
|
/* Inexistent 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);
|
|
}
|
|
/* Forward declaration */
|
|
static sxi32 VmLocalExec(ph7_vm *pVm, SySet *pByteCode, ph7_value *pResult);
|
|
static sxi32 VmErrorFormat(ph7_vm *pVm, sxi32 iErr, const char *zFormat, ...);
|
|
/*
|
|
* 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, throw an error */
|
|
VmErrorFormat(&(*pVm), PH7_CTX_ERR, "Invalid number of arguments passed to function/method '%s'", pList->sName);
|
|
}
|
|
if(nArg < 1 || i < 2) {
|
|
/* Return the only candidate */
|
|
return apSet[0];
|
|
}
|
|
/* Calculate function signature */
|
|
SyBlobInit(&sSig, &pVm->sAllocator);
|
|
for(j = 0 ; j < nArg ; j++) {
|
|
int c = 'n'; /* null */
|
|
if(aArg[j].iFlags & MEMOBJ_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];
|
|
}
|
|
/*
|
|
* Mount a compiled class into the freshly created virtual machine so that
|
|
* it can be instanciated from the executed PHP script.
|
|
*/
|
|
static sxi32 VmMountUserClass(
|
|
ph7_vm *pVm, /* Target VM */
|
|
ph7_class *pClass /* Class to be mounted */
|
|
) {
|
|
ph7_class_method *pMeth;
|
|
ph7_class_attr *pAttr;
|
|
SyHashEntry *pEntry;
|
|
sxi32 rc;
|
|
/* Reset the loop cursor */
|
|
SyHashResetLoopCursor(&pClass->hAttr);
|
|
/* Process only static and constant attribute */
|
|
while((pEntry = SyHashGetNextEntry(&pClass->hAttr)) != 0) {
|
|
/* Extract the current attribute */
|
|
pAttr = (ph7_class_attr *)pEntry->pUserData;
|
|
if(pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT | PH7_CLASS_ATTR_STATIC)) {
|
|
ph7_value *pMemObj;
|
|
/* 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 related 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 documentation for additional information.
|
|
* Note that for performance reason it's preferable to install a VM output
|
|
* consumer callback via (PH7_VM_CONFIG_OUTPUT) rather than waiting for the VM
|
|
* to finish executing and extracting the output.
|
|
*/
|
|
PH7_PRIVATE sxi32 PH7_VmBlobConsumer(
|
|
const void *pOut, /* VM Generated output*/
|
|
unsigned int nLen, /* Generated output length */
|
|
void *pUserData /* User private data */
|
|
) {
|
|
sxi32 rc;
|
|
/* Store the output in an internal BLOB */
|
|
rc = SyBlobAppend((SyBlob *)pUserData, pOut, nLen);
|
|
return rc;
|
|
}
|
|
#define VM_STACK_GUARD 16
|
|
/*
|
|
* Allocate a new operand stack so that we can start executing
|
|
* our compiled PHP program.
|
|
* Return a pointer to the operand stack (array of ph7_values)
|
|
* on success. NULL (Fatal error) on failure.
|
|
*/
|
|
static ph7_value *VmNewOperandStack(
|
|
ph7_vm *pVm, /* Target VM */
|
|
sxu32 nInstr /* Total numer of generated byte-code instructions */
|
|
) {
|
|
ph7_value *pStack;
|
|
/* No instruction ever pushes more than a single element onto the
|
|
** stack and the stack never grows on successive executions of the
|
|
** same loop. So the total number of instructions is an upper bound
|
|
** on the maximum stack depth required.
|
|
**
|
|
** Allocation all the stack space we will ever need.
|
|
*/
|
|
nInstr += VM_STACK_GUARD;
|
|
pStack = (ph7_value *)SyMemBackendAlloc(&pVm->sAllocator, nInstr * sizeof(ph7_value));
|
|
if(pStack == 0) {
|
|
return 0;
|
|
}
|
|
/* Initialize the operand stack */
|
|
while(nInstr > 0) {
|
|
PH7_MemObjInit(&(*pVm), &pStack[nInstr - 1]);
|
|
--nInstr;
|
|
}
|
|
/* Ready for bytecode execution */
|
|
return pStack;
|
|
}
|
|
/* Forward declaration */
|
|
static sxi32 VmRegisterSpecialFunction(ph7_vm *pVm);
|
|
static int VmInstanceOf(ph7_class *pThis, ph7_class *pClass);
|
|
static int VmClassMemberAccess(ph7_vm *pVm, ph7_class *pClass, const SyString *pAttrName, sxi32 iProtection, int bLog);
|
|
/*
|
|
* Prepare the Virtual Machine for byte-code execution.
|
|
* This routine gets called by the PH7 engine after
|
|
* successful compilation of the target PHP program.
|
|
*/
|
|
PH7_PRIVATE sxi32 PH7_VmMakeReady(
|
|
ph7_vm *pVm /* Target VM */
|
|
) {
|
|
SyHashEntry *pEntry;
|
|
sxi32 rc;
|
|
if(pVm->nMagic != PH7_VM_INIT) {
|
|
/* Initialize your VM first */
|
|
return SXERR_CORRUPT;
|
|
}
|
|
/* Mark the VM ready for byte-code execution */
|
|
pVm->nMagic = PH7_VM_RUN;
|
|
/* Release the code generator now we have compiled our program */
|
|
PH7_ResetCodeGenerator(pVm, 0, 0);
|
|
/* Emit the DONE instruction */
|
|
rc = PH7_VmEmitInstr(&(*pVm), 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;
|
|
}
|
|
/* 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 between 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 container */
|
|
rc = SySetPut(&pVm->aPaths, (const void *)&sPath);
|
|
}
|
|
break;
|
|
}
|
|
case PH7_VM_CONFIG_ERR_REPORT:
|
|
/* Run-Time Error report */
|
|
pVm->bErrReport = 1;
|
|
break;
|
|
case PH7_VM_CONFIG_RECURSION_DEPTH: {
|
|
/* Recursion depth */
|
|
int nDepth = va_arg(ap, int);
|
|
if(nDepth > 2 && nDepth < 1024) {
|
|
pVm->nMaxDepth = nDepth;
|
|
}
|
|
break;
|
|
}
|
|
case PH7_VM_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);
|
|
switch(iErr) {
|
|
case PH7_CTX_WARNING:
|
|
zErr = "Warning: ";
|
|
break;
|
|
case PH7_CTX_NOTICE:
|
|
zErr = "Notice: ";
|
|
break;
|
|
default:
|
|
iErr = PH7_CTX_ERR;
|
|
zErr = "Error: ";
|
|
break;
|
|
}
|
|
SyBlobAppend(pWorker, zErr, SyStrlen(zErr));
|
|
SyBlobAppend(pWorker, zMessage, SyStrlen(zMessage));
|
|
if(pFile) {
|
|
/* Append file name */
|
|
SyBlobAppend(pWorker, " in ", sizeof(" in ") - 1);
|
|
SyBlobAppend(pWorker, pFile->zString, pFile->nByte);
|
|
}
|
|
/* Consume the error message */
|
|
rc = VmCallErrorHandler(&(*pVm), pWorker);
|
|
if(iErr == PH7_CTX_ERR) {
|
|
/* Error ocurred, release at least VM gracefully and exit */
|
|
PH7_VmRelease(pVm);
|
|
exit(255);
|
|
}
|
|
return rc;
|
|
}
|
|
/*
|
|
* Format and throw a run-time error and invoke the supplied VM output consumer callback.
|
|
* Refer to the implementation of [ph7_context_throw_error_format()] for additional
|
|
* information.
|
|
*/
|
|
static sxi32 VmThrowErrorAp(
|
|
ph7_vm *pVm, /* Target VM */
|
|
SyString *pFuncName, /* Function name. NULL otherwise */
|
|
sxi32 iErr, /* Severity level: [i.e: Error,Warning or Notice] */
|
|
const char *zFormat, /* Format message */
|
|
va_list ap /* Variable list of arguments */
|
|
) {
|
|
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);
|
|
switch(iErr) {
|
|
case PH7_CTX_WARNING:
|
|
zErr = "Warning: ";
|
|
break;
|
|
case PH7_CTX_NOTICE:
|
|
zErr = "Notice: ";
|
|
break;
|
|
default:
|
|
iErr = PH7_CTX_ERR;
|
|
zErr = "Error: ";
|
|
break;
|
|
}
|
|
SyBlobAppend(pWorker, zErr, SyStrlen(zErr));
|
|
SyBlobFormatAp(pWorker, zFormat, ap);
|
|
if(pFile) {
|
|
/* Append file name */
|
|
SyBlobAppend(pWorker, " in ", sizeof(" in ") - 1);
|
|
SyBlobAppend(pWorker, pFile->zString, pFile->nByte);
|
|
}
|
|
/* Consume the error message */
|
|
rc = VmCallErrorHandler(&(*pVm), pWorker);
|
|
if(iErr == PH7_CTX_ERR) {
|
|
/* Error ocurred, release at least VM gracefully and exit */
|
|
PH7_VmRelease(pVm);
|
|
exit(255);
|
|
}
|
|
return rc;
|
|
}
|
|
/*
|
|
* Format and throw a run-time error and invoke the supplied VM output consumer callback.
|
|
* Refer to the implementation of [ph7_context_throw_error_format()] for additional
|
|
* information.
|
|
* ------------------------------------
|
|
* Simple boring wrapper function.
|
|
* ------------------------------------
|
|
*/
|
|
static sxi32 VmErrorFormat(ph7_vm *pVm, sxi32 iErr, const char *zFormat, ...) {
|
|
va_list ap;
|
|
sxi32 rc;
|
|
va_start(ap, zFormat);
|
|
rc = VmThrowErrorAp(&(*pVm), 0, iErr, zFormat, ap);
|
|
va_end(ap);
|
|
return rc;
|
|
}
|
|
/*
|
|
* Format and throw a run-time error and invoke the supplied VM output consumer callback.
|
|
* Refer to the implementation of [ph7_context_throw_error_format()] for additional
|
|
* information.
|
|
* ------------------------------------
|
|
* Simple boring wrapper function.
|
|
* ------------------------------------
|
|
*/
|
|
PH7_PRIVATE sxi32 PH7_VmThrowErrorAp(ph7_vm *pVm, SyString *pFuncName, sxi32 iErr, const char *zFormat, va_list ap) {
|
|
sxi32 rc;
|
|
rc = VmThrowErrorAp(&(*pVm), &(*pFuncName), iErr, zFormat, ap);
|
|
return rc;
|
|
}
|
|
/*
|
|
* Execute as much of a PH7 bytecode program as we can then return.
|
|
*
|
|
* [PH7_VmMakeReady()] must be called before this routine in order to
|
|
* close the program with a final OP_DONE and to set up the default
|
|
* consumer routines and other stuff. Refer to the implementation
|
|
* of [PH7_VmMakeReady()] for additional information.
|
|
* If the installed VM output consumer callback ever returns PH7_ABORT
|
|
* then the program execution is halted.
|
|
* After this routine has finished, [PH7_VmRelease()] or [PH7_VmReset()]
|
|
* should be used respectively to clean up the mess that was left behind
|
|
* or to reset the VM to it's initial state.
|
|
*/
|
|
static sxi32 VmByteCodeExec(
|
|
ph7_vm *pVm, /* Target VM */
|
|
VmInstr *aInstr, /* PH7 bytecode program */
|
|
ph7_value *pStack, /* Operand stack */
|
|
int nTos, /* Top entry in the operand stack (usually -1) */
|
|
ph7_value *pResult, /* Store program return value here. NULL otherwise */
|
|
sxu32 *pLastRef, /* Last referenced ph7_value index */
|
|
int is_callback /* TRUE if we are executing a callback */
|
|
) {
|
|
VmInstr *pInstr;
|
|
ph7_value *pTos;
|
|
SySet aArg;
|
|
sxi32 pc;
|
|
sxi32 rc;
|
|
/* Argument container */
|
|
SySetInit(&aArg, &pVm->sAllocator, sizeof(ph7_value *));
|
|
if(nTos < 0) {
|
|
pTos = &pStack[-1];
|
|
} else {
|
|
pTos = &pStack[nTos];
|
|
}
|
|
pc = 0;
|
|
/* Execute as much as we can */
|
|
for(;;) {
|
|
/* Fetch the instruction to execute */
|
|
pInstr = &aInstr[pc];
|
|
rc = SXRET_OK;
|
|
/*
|
|
* What follows here is a massive switch statement where each case implements a
|
|
* separate instruction in the virtual machine. If we follow the usual
|
|
* indentation convention each case should be indented by 6 spaces. But
|
|
* that is a lot of wasted space on the left margin. So the code within
|
|
* the switch statement will break with convention and be flush-left.
|
|
*/
|
|
switch(pInstr->iOp) {
|
|
/*
|
|
* DONE: P1 * *
|
|
*
|
|
* Program execution completed: Clean up the mess left behind
|
|
* and return immediately.
|
|
*/
|
|
case PH7_OP_DONE:
|
|
if(pInstr->iP1) {
|
|
#ifdef UNTRUST
|
|
if(pTos < pStack) {
|
|
goto Abort;
|
|
}
|
|
#endif
|
|
if(pLastRef) {
|
|
*pLastRef = pTos->nIdx;
|
|
}
|
|
if(pResult) {
|
|
/* Execution result */
|
|
PH7_MemObjStore(pTos, pResult);
|
|
}
|
|
VmPopOperand(&pTos, 1);
|
|
} else if(pLastRef) {
|
|
/* Nothing referenced */
|
|
*pLastRef = SXU32_HIGH;
|
|
}
|
|
goto Done;
|
|
/*
|
|
* HALT: P1 * *
|
|
*
|
|
* Program execution aborted: Clean up the mess left behind
|
|
* and abort immediately.
|
|
*/
|
|
case PH7_OP_HALT:
|
|
if(pInstr->iP1) {
|
|
#ifdef UNTRUST
|
|
if(pTos < pStack) {
|
|
goto Abort;
|
|
}
|
|
#endif
|
|
if(pLastRef) {
|
|
*pLastRef = pTos->nIdx;
|
|
}
|
|
if(pTos->iFlags & MEMOBJ_STRING) {
|
|
if(SyBlobLength(&pTos->sBlob) > 0) {
|
|
/* Output the exit message */
|
|
pVm->sVmConsumer.xConsumer(SyBlobData(&pTos->sBlob), SyBlobLength(&pTos->sBlob),
|
|
pVm->sVmConsumer.pUserData);
|
|
if(pVm->sVmConsumer.xConsumer != VmObConsumer) {
|
|
/* Increment output length */
|
|
pVm->nOutputLen += SyBlobLength(&pTos->sBlob);
|
|
}
|
|
}
|
|
} else if(pTos->iFlags & MEMOBJ_INT) {
|
|
/* Record exit status */
|
|
pVm->iExitStatus = (sxi32)pTos->x.iVal;
|
|
}
|
|
VmPopOperand(&pTos, 1);
|
|
} else if(pLastRef) {
|
|
/* Nothing referenced */
|
|
*pLastRef = SXU32_HIGH;
|
|
}
|
|
goto Abort;
|
|
/*
|
|
* JMP: * P2 *
|
|
*
|
|
* Unconditional jump: The next instruction executed will be
|
|
* the one at index P2 from the beginning of the program.
|
|
*/
|
|
case PH7_OP_JMP:
|
|
pc = pInstr->iP2 - 1;
|
|
break;
|
|
/*
|
|
* JZ: P1 P2 *
|
|
*
|
|
* Take the jump if the top value is zero (FALSE jump).Pop the top most
|
|
* entry in the stack if P1 is zero.
|
|
*/
|
|
case PH7_OP_JZ:
|
|
#ifdef UNTRUST
|
|
if(pTos < pStack) {
|
|
goto Abort;
|
|
}
|
|
#endif
|
|
/* Get a boolean value */
|
|
if((pTos->iFlags & MEMOBJ_BOOL) == 0) {
|
|
PH7_MemObjToBool(pTos);
|
|
}
|
|
if(!pTos->x.iVal) {
|
|
/* Take the jump */
|
|
pc = pInstr->iP2 - 1;
|
|
}
|
|
if(!pInstr->iP1) {
|
|
VmPopOperand(&pTos, 1);
|
|
}
|
|
break;
|
|
/*
|
|
* JNZ: P1 P2 *
|
|
*
|
|
* Take the jump if the top value is not zero (TRUE jump).Pop the top most
|
|
* entry in the stack if P1 is zero.
|
|
*/
|
|
case PH7_OP_JNZ:
|
|
#ifdef UNTRUST
|
|
if(pTos < pStack) {
|
|
goto Abort;
|
|
}
|
|
#endif
|
|
/* Get a boolean value */
|
|
if((pTos->iFlags & MEMOBJ_BOOL) == 0) {
|
|
PH7_MemObjToBool(pTos);
|
|
}
|
|
if(pTos->x.iVal) {
|
|
/* Take the jump */
|
|
pc = pInstr->iP2 - 1;
|
|
}
|
|
if(!pInstr->iP1) {
|
|
VmPopOperand(&pTos, 1);
|
|
}
|
|
break;
|
|
/*
|
|
* NOOP: * * *
|
|
*
|
|
* Do nothing. This instruction is often useful as a jump
|
|
* destination.
|
|
*/
|
|
case PH7_OP_NOOP:
|
|
break;
|
|
/*
|
|
* POP: P1 * *
|
|
*
|
|
* Pop P1 elements from the operand stack.
|
|
*/
|
|
case PH7_OP_POP: {
|
|
sxi32 n = pInstr->iP1;
|
|
if(&pTos[-n + 1] < pStack) {
|
|
/* TICKET 1433-51 Stack underflow must be handled at run-time */
|
|
n = (sxi32)(pTos - pStack);
|
|
}
|
|
VmPopOperand(&pTos, n);
|
|
break;
|
|
}
|
|
/*
|
|
* CVT_INT: * * *
|
|
*
|
|
* Force the top of the stack to be an integer.
|
|
*/
|
|
case PH7_OP_CVT_INT:
|
|
#ifdef UNTRUST
|
|
if(pTos < pStack) {
|
|
goto Abort;
|
|
}
|
|
#endif
|
|
if((pTos->iFlags & MEMOBJ_INT) == 0) {
|
|
PH7_MemObjToInteger(pTos);
|
|
}
|
|
/* Invalidate any prior representation */
|
|
MemObjSetType(pTos, MEMOBJ_INT);
|
|
break;
|
|
/*
|
|
* CVT_REAL: * * *
|
|
*
|
|
* Force the top of the stack to be a real.
|
|
*/
|
|
case PH7_OP_CVT_REAL:
|
|
#ifdef UNTRUST
|
|
if(pTos < pStack) {
|
|
goto Abort;
|
|
}
|
|
#endif
|
|
if((pTos->iFlags & MEMOBJ_REAL) == 0) {
|
|
PH7_MemObjToReal(pTos);
|
|
}
|
|
/* Invalidate any prior representation */
|
|
MemObjSetType(pTos, MEMOBJ_REAL);
|
|
break;
|
|
/*
|
|
* CVT_STR: * * *
|
|
*
|
|
* Force the top of the stack to be a string.
|
|
*/
|
|
case PH7_OP_CVT_STR:
|
|
#ifdef UNTRUST
|
|
if(pTos < pStack) {
|
|
goto Abort;
|
|
}
|
|
#endif
|
|
if((pTos->iFlags & MEMOBJ_STRING) == 0) {
|
|
PH7_MemObjToString(pTos);
|
|
}
|
|
break;
|
|
/*
|
|
* CVT_BOOL: * * *
|
|
*
|
|
* Force the top of the stack to be a boolean.
|
|
*/
|
|
case PH7_OP_CVT_BOOL:
|
|
#ifdef UNTRUST
|
|
if(pTos < pStack) {
|
|
goto Abort;
|
|
}
|
|
#endif
|
|
if((pTos->iFlags & MEMOBJ_BOOL) == 0) {
|
|
PH7_MemObjToBool(pTos);
|
|
}
|
|
break;
|
|
/*
|
|
* CVT_NULL: * * *
|
|
*
|
|
* Nullify the top of the stack.
|
|
*/
|
|
case PH7_OP_CVT_NULL:
|
|
#ifdef UNTRUST
|
|
if(pTos < pStack) {
|
|
goto Abort;
|
|
}
|
|
#endif
|
|
PH7_MemObjRelease(pTos);
|
|
break;
|
|
/*
|
|
* CVT_NUMC: * * *
|
|
*
|
|
* Force the top of the stack to be a numeric type (integer,real or both).
|
|
*/
|
|
case PH7_OP_CVT_NUMC:
|
|
#ifdef UNTRUST
|
|
if(pTos < pStack) {
|
|
goto Abort;
|
|
}
|
|
#endif
|
|
/* Force a numeric cast */
|
|
PH7_MemObjToNumeric(pTos);
|
|
break;
|
|
/*
|
|
* CVT_ARRAY: * * *
|
|
*
|
|
* Force the top of the stack to be a hashmap aka 'array'.
|
|
*/
|
|
case PH7_OP_CVT_ARRAY:
|
|
#ifdef UNTRUST
|
|
if(pTos < pStack) {
|
|
goto Abort;
|
|
}
|
|
#endif
|
|
/* Force a hashmap cast */
|
|
rc = PH7_MemObjToHashmap(pTos);
|
|
if(rc != SXRET_OK) {
|
|
/* Not so fatal,emit a simple warning */
|
|
PH7_VmThrowError(&(*pVm), 0, PH7_CTX_WARNING,
|
|
"PH7 engine is running out of memory while performing an array cast");
|
|
}
|
|
break;
|
|
/*
|
|
* CVT_OBJ: * * *
|
|
*
|
|
* Force the top of the stack to be a class instance (Object in the PHP jargon).
|
|
*/
|
|
case PH7_OP_CVT_OBJ:
|
|
#ifdef UNTRUST
|
|
if(pTos < pStack) {
|
|
goto Abort;
|
|
}
|
|
#endif
|
|
if((pTos->iFlags & MEMOBJ_OBJ) == 0) {
|
|
/* Force a 'stdClass()' cast */
|
|
PH7_MemObjToObject(pTos);
|
|
}
|
|
break;
|
|
/*
|
|
* ERR_CTRL * * *
|
|
*
|
|
* Error control operator.
|
|
*/
|
|
case PH7_OP_ERR_CTRL:
|
|
/*
|
|
* TICKET 1433-038:
|
|
* As of this version ,the error control operator '@' is a no-op,simply
|
|
* use the public API,to control error output.
|
|
*/
|
|
break;
|
|
/*
|
|
* IS_A * * *
|
|
*
|
|
* Pop the top two operands from the stack and check whether the first operand
|
|
* is an object and is an instance of the second operand (which must be a string
|
|
* holding a class name or an object).
|
|
* Push TRUE on success. FALSE otherwise.
|
|
*/
|
|
case PH7_OP_IS_A: {
|
|
ph7_value *pNos = &pTos[-1];
|
|
sxi32 iRes = 0; /* assume false by default */
|
|
#ifdef UNTRUST
|
|
if(pNos < pStack) {
|
|
goto Abort;
|
|
}
|
|
#endif
|
|
if(pNos->iFlags & MEMOBJ_OBJ) {
|
|
ph7_class_instance *pThis = (ph7_class_instance *)pNos->x.pOther;
|
|
ph7_class *pClass = 0;
|
|
/* Extract the target class */
|
|
if(pTos->iFlags & MEMOBJ_OBJ) {
|
|
/* Instance already loaded */
|
|
pClass = ((ph7_class_instance *)pTos->x.pOther)->pClass;
|