Aer/oop.c

1146 lines
38 KiB
C
Raw Normal View History

/*
* 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: oo.c v1.9 FeeBSD 2012-07-17 03:44 devel <chm@symisc.net> $ */
#include "ph7int.h"
/*
* This file implement an Object Oriented (OO) subsystem for the PH7 engine.
*/
/*
* Create an empty class.
* Return a pointer to a raw class (ph7_class instance) on success. NULL otherwise.
*/
PH7_PRIVATE ph7_class * PH7_NewRawClass(ph7_vm *pVm,const SyString *pName,sxu32 nLine)
{
ph7_class *pClass;
char *zName;
/* Allocate a new instance */
pClass = (ph7_class *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(ph7_class));
if( pClass == 0 ){
return 0;
}
/* Zero the structure */
SyZero(pClass,sizeof(ph7_class));
/* Duplicate class name */
zName = SyMemBackendStrDup(&pVm->sAllocator,pName->zString,pName->nByte);
if( zName == 0 ){
SyMemBackendPoolFree(&pVm->sAllocator,pClass);
return 0;
}
/* Initialize fields */
SyStringInitFromBuf(&pClass->sName,zName,pName->nByte);
SyHashInit(&pClass->hMethod,&pVm->sAllocator,0,0);
SyHashInit(&pClass->hAttr,&pVm->sAllocator,0,0);
SyHashInit(&pClass->hDerived,&pVm->sAllocator,0,0);
SySetInit(&pClass->aInterface,&pVm->sAllocator,sizeof(ph7_class *));
pClass->nLine = nLine;
/* All done */
return pClass;
}
/*
* Allocate and initialize a new class attribute.
* Return a pointer to the class attribute on success. NULL otherwise.
*/
PH7_PRIVATE ph7_class_attr * PH7_NewClassAttr(ph7_vm *pVm,const SyString *pName,sxu32 nLine,sxi32 iProtection,sxi32 iFlags)
{
ph7_class_attr *pAttr;
char *zName;
pAttr = (ph7_class_attr *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(ph7_class_attr));
if( pAttr == 0 ){
return 0;
}
/* Zero the structure */
SyZero(pAttr,sizeof(ph7_class_attr));
/* Duplicate attribute name */
zName = SyMemBackendStrDup(&pVm->sAllocator,pName->zString,pName->nByte);
if( zName == 0 ){
SyMemBackendPoolFree(&pVm->sAllocator,pAttr);
return 0;
}
/* Initialize fields */
SySetInit(&pAttr->aByteCode,&pVm->sAllocator,sizeof(VmInstr));
SyStringInitFromBuf(&pAttr->sName,zName,pName->nByte);
pAttr->iProtection = iProtection;
pAttr->nIdx = SXU32_HIGH;
pAttr->iFlags = iFlags;
pAttr->nLine = nLine;
return pAttr;
}
/*
* Allocate and initialize a new class method.
* Return a pointer to the class method on success. NULL otherwise
* This function associate with the newly created method an automatically generated
* random unique name.
*/
PH7_PRIVATE ph7_class_method * PH7_NewClassMethod(ph7_vm *pVm,ph7_class *pClass,const SyString *pName,sxu32 nLine,
sxi32 iProtection,sxi32 iFlags,sxi32 iFuncFlags)
{
ph7_class_method *pMeth;
SyHashEntry *pEntry;
SyString *pNamePtr;
char zSalt[10];
char *zName;
sxu32 nByte;
/* Allocate a new class method instance */
pMeth = (ph7_class_method *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(ph7_class_method));
if( pMeth == 0 ){
return 0;
}
/* Zero the structure */
SyZero(pMeth,sizeof(ph7_class_method));
/* Check for an already installed method with the same name */
pEntry = SyHashGet(&pClass->hMethod,(const void *)pName->zString,pName->nByte);
if( pEntry == 0 ){
/* Associate an unique VM name to this method */
nByte = sizeof(zSalt) + pName->nByte + SyStringLength(&pClass->sName)+sizeof(char)*7/*[[__'\0'*/;
zName = (char *)SyMemBackendAlloc(&pVm->sAllocator,nByte);
if( zName == 0 ){
SyMemBackendPoolFree(&pVm->sAllocator,pMeth);
return 0;
}
pNamePtr = &pMeth->sVmName;
/* Generate a random string */
PH7_VmRandomString(&(*pVm),zSalt,sizeof(zSalt));
pNamePtr->nByte = SyBufferFormat(zName,nByte,"[__%z@%z_%.*s]",&pClass->sName,pName,sizeof(zSalt),zSalt);
pNamePtr->zString = zName;
}else{
/* Method is condidate for 'overloading' */
ph7_class_method *pCurrent = (ph7_class_method *)pEntry->pUserData;
pNamePtr = &pMeth->sVmName;
/* Use the same VM name */
SyStringDupPtr(pNamePtr,&pCurrent->sVmName);
zName = (char *)pNamePtr->zString;
}
if( iProtection != PH7_CLASS_PROT_PUBLIC ){
if( (pName->nByte == sizeof("__construct") - 1 && SyMemcmp(pName->zString,"__construct",sizeof("__construct") - 1 ) == 0)
|| (pName->nByte == sizeof("__destruct") - 1 && SyMemcmp(pName->zString,"__destruct",sizeof("__destruct") - 1 ) == 0)
|| SyStringCmp(pName,&pClass->sName,SyMemcmp) == 0 ){
/* Switch to public visibility when dealing with constructor/destructor */
iProtection = PH7_CLASS_PROT_PUBLIC;
}
}
/* Initialize method fields */
pMeth->iProtection = iProtection;
pMeth->iFlags = iFlags;
pMeth->nLine = nLine;
PH7_VmInitFuncState(&(*pVm),&pMeth->sFunc,&zName[sizeof(char)*4/*[__@*/+SyStringLength(&pClass->sName)],
pName->nByte,iFuncFlags|VM_FUNC_CLASS_METHOD,pClass);
return pMeth;
}
/*
* Check if the given name have a class method associated with it.
* Return the desired method [i.e: ph7_class_method instance] on success. NULL otherwise.
*/
PH7_PRIVATE ph7_class_method * PH7_ClassExtractMethod(ph7_class *pClass,const char *zName,sxu32 nByte)
{
SyHashEntry *pEntry;
/* Perform a hash lookup */
pEntry = SyHashGet(&pClass->hMethod,(const void *)zName,nByte);
if( pEntry == 0 ){
/* No such entry */
return 0;
}
/* Point to the desired method */
return (ph7_class_method *)pEntry->pUserData;
}
/*
* Check if the given name is a class attribute.
* Return the desired attribute [i.e: ph7_class_attr instance] on success.NULL otherwise.
*/
PH7_PRIVATE ph7_class_attr * PH7_ClassExtractAttribute(ph7_class *pClass,const char *zName,sxu32 nByte)
{
SyHashEntry *pEntry;
/* Perform a hash lookup */
pEntry = SyHashGet(&pClass->hAttr,(const void *)zName,nByte);
if( pEntry == 0 ){
/* No such entry */
return 0;
}
/* Point to the desierd method */
return (ph7_class_attr *)pEntry->pUserData;
}
/*
* Install a class attribute in the corresponding container.
* Return SXRET_OK on success. Any other return value indicates failure.
*/
PH7_PRIVATE sxi32 PH7_ClassInstallAttr(ph7_class *pClass,ph7_class_attr *pAttr)
{
SyString *pName = &pAttr->sName;
sxi32 rc;
rc = SyHashInsert(&pClass->hAttr,(const void *)pName->zString,pName->nByte,pAttr);
return rc;
}
/*
* Install a class method in the corresponding container.
* Return SXRET_OK on success. Any other return value indicates failure.
*/
PH7_PRIVATE sxi32 PH7_ClassInstallMethod(ph7_class *pClass,ph7_class_method *pMeth)
{
SyString *pName = &pMeth->sFunc.sName;
sxi32 rc;
rc = SyHashInsert(&pClass->hMethod,(const void *)pName->zString,pName->nByte,pMeth);
return rc;
}
/*
* Perform an inheritance operation.
* According to the PHP language reference manual
* When you extend a class, the subclass inherits all of the public and protected methods
* from the parent class. Unless a class Overwrites those methods, they will retain their original
* functionality.
* This is useful for defining and abstracting functionality, and permits the implementation
* of additional functionality in similar objects without the need to reimplement all of the shared
* functionality.
* Example #1 Inheritance Example
* <?php
* class foo
* {
* public function printItem($string)
* {
* echo 'Foo: ' . $string . PHP_EOL;
* }
*
* public function printPHP()
* {
* echo 'PHP is great.' . PHP_EOL;
* }
* }
* class bar extends foo
* {
* public function printItem($string)
* {
* echo 'Bar: ' . $string . PHP_EOL;
* }
* }
* $foo = new foo();
* $bar = new bar();
* $foo->printItem('baz'); // Output: 'Foo: baz'
* $foo->printPHP(); // Output: 'PHP is great'
* $bar->printItem('baz'); // Output: 'Bar: baz'
* $bar->printPHP(); // Output: 'PHP is great'
*
* This function return SXRET_OK if the inheritance operation was successfully performed.
* Any other return value indicates failure and the upper layer must generate an appropriate
* error message.
*/
PH7_PRIVATE sxi32 PH7_ClassInherit(ph7_gen_state *pGen,ph7_class *pSub,ph7_class *pBase)
{
ph7_class_method *pMeth;
ph7_class_attr *pAttr;
SyHashEntry *pEntry;
SyString *pName;
sxi32 rc;
/* Install in the derived hashtable */
rc = SyHashInsert(&pBase->hDerived,(const void *)SyStringData(&pSub->sName),SyStringLength(&pSub->sName),pSub);
if( rc != SXRET_OK ){
return rc;
}
/* Copy public/protected attributes from the base class */
SyHashResetLoopCursor(&pBase->hAttr);
while((pEntry = SyHashGetNextEntry(&pBase->hAttr)) != 0 ){
/* Make sure the private attributes are not redeclared in the subclass */
pAttr = (ph7_class_attr *)pEntry->pUserData;
pName = &pAttr->sName;
if( (pEntry = SyHashGet(&pSub->hAttr,(const void *)pName->zString,pName->nByte)) != 0 ){
if( pAttr->iProtection == PH7_CLASS_PROT_PRIVATE &&
((ph7_class_attr *)pEntry->pUserData)->iProtection != PH7_CLASS_PROT_PUBLIC ){
/* Cannot redeclare private attribute */
PH7_GenCompileError(&(*pGen),E_WARNING,((ph7_class_attr *)pEntry->pUserData)->nLine,
"Private attribute '%z::%z' redeclared inside child class '%z'",
&pBase->sName,pName,&pSub->sName);
}
continue;
}
/* Install the attribute */
if( pAttr->iProtection != PH7_CLASS_PROT_PRIVATE ){
rc = SyHashInsert(&pSub->hAttr,(const void *)pName->zString,pName->nByte,pAttr);
if( rc != SXRET_OK ){
return rc;
}
}
}
SyHashResetLoopCursor(&pBase->hMethod);
while((pEntry = SyHashGetNextEntry(&pBase->hMethod)) != 0 ){
/* Make sure the private/final methods are not redeclared in the subclass */
pMeth = (ph7_class_method *)pEntry->pUserData;
pName = &pMeth->sFunc.sName;
if( (pEntry = SyHashGet(&pSub->hMethod,(const void *)pName->zString,pName->nByte)) != 0 ){
if( pMeth->iFlags & PH7_CLASS_ATTR_FINAL ){
/* Cannot Overwrite final method */
rc = PH7_GenCompileError(&(*pGen),E_ERROR,((ph7_class_method *)pEntry->pUserData)->nLine,
"Cannot Overwrite final method '%z:%z' inside child class '%z'",
&pBase->sName,pName,&pSub->sName);
if( rc == SXERR_ABORT ){
return SXERR_ABORT;
}
}
continue;
}else{
if( pMeth->iFlags & PH7_CLASS_ATTR_ABSTRACT ){
/* Abstract method must be defined in the child class */
PH7_GenCompileError(&(*pGen),E_WARNING,pMeth->nLine,
"Abstract method '%z:%z' must be defined inside child class '%z'",
&pBase->sName,pName,&pSub->sName);
continue;
}
}
/* Install the method */
if( pMeth->iProtection != PH7_CLASS_PROT_PRIVATE ){
rc = SyHashInsert(&pSub->hMethod,(const void *)pName->zString,pName->nByte,pMeth);
if( rc != SXRET_OK ){
return rc;
}
}
}
/* Mark as subclass */
pSub->pBase = pBase;
/* All done */
return SXRET_OK;
}
/*
* Inherit an object interface from another object interface.
* According to the PHP language reference manual.
* Object interfaces allow you to create code which specifies which methods a class
* must implement, without having to define how these methods are handled.
* Interfaces are defined using the interface keyword, in the same way as a standard
* class, but without any of the methods having their contents defined.
* All methods declared in an interface must be public, this is the nature of an interface.
*
* This function return SXRET_OK if the interface inheritance operation was successfully performed.
* Any other return value indicates failure and the upper layer must generate an appropriate
* error message.
*/
PH7_PRIVATE sxi32 PH7_ClassInterfaceInherit(ph7_class *pSub,ph7_class *pBase)
{
ph7_class_method *pMeth;
ph7_class_attr *pAttr;
SyHashEntry *pEntry;
SyString *pName;
sxi32 rc;
/* Install in the derived hashtable */
SyHashInsert(&pBase->hDerived,(const void *)SyStringData(&pSub->sName),SyStringLength(&pSub->sName),pSub);
SyHashResetLoopCursor(&pBase->hAttr);
/* Copy constants */
while((pEntry = SyHashGetNextEntry(&pBase->hAttr)) != 0 ){
/* Make sure the constants are not redeclared in the subclass */
pAttr = (ph7_class_attr *)pEntry->pUserData;
pName = &pAttr->sName;
if( SyHashGet(&pSub->hAttr,(const void *)pName->zString,pName->nByte) == 0 ){
/* Install the constant in the subclass */
rc = SyHashInsert(&pSub->hAttr,(const void *)pName->zString,pName->nByte,pAttr);
if( rc != SXRET_OK ){
return rc;
}
}
}
SyHashResetLoopCursor(&pBase->hMethod);
/* Copy methods signature */
while((pEntry = SyHashGetNextEntry(&pBase->hMethod)) != 0 ){
/* Make sure the method are not redeclared in the subclass */
pMeth = (ph7_class_method *)pEntry->pUserData;
pName = &pMeth->sFunc.sName;
if( SyHashGet(&pSub->hMethod,(const void *)pName->zString,pName->nByte) == 0 ){
/* Install the method */
rc = SyHashInsert(&pSub->hMethod,(const void *)pName->zString,pName->nByte,pMeth);
if( rc != SXRET_OK ){
return rc;
}
}
}
/* Mark as subclass */
pSub->pBase = pBase;
/* All done */
return SXRET_OK;
}
/*
* Implements an object interface in the given main class.
* According to the PHP language reference manual.
* Object interfaces allow you to create code which specifies which methods a class
* must implement, without having to define how these methods are handled.
* Interfaces are defined using the interface keyword, in the same way as a standard
* class, but without any of the methods having their contents defined.
* All methods declared in an interface must be public, this is the nature of an interface.
*
* This function return SXRET_OK if the interface was successfully implemented.
* Any other return value indicates failure and the upper layer must generate an appropriate
* error message.
*/
PH7_PRIVATE sxi32 PH7_ClassImplement(ph7_class *pMain,ph7_class *pInterface)
{
ph7_class_attr *pAttr;
SyHashEntry *pEntry;
SyString *pName;
sxi32 rc;
/* First off,copy all constants declared inside the interface */
SyHashResetLoopCursor(&pInterface->hAttr);
while((pEntry = SyHashGetNextEntry(&pInterface->hAttr)) != 0 ){
/* Point to the constant declaration */
pAttr = (ph7_class_attr *)pEntry->pUserData;
pName = &pAttr->sName;
/* Make sure the attribute is not redeclared in the main class */
if( SyHashGet(&pMain->hAttr,pName->zString,pName->nByte) == 0 ){
/* Install the attribute */
rc = SyHashInsert(&pMain->hAttr,pName->zString,pName->nByte,pAttr);
if( rc != SXRET_OK ){
return rc;
}
}
}
/* Install in the interface container */
SySetPut(&pMain->aInterface,(const void *)&pInterface);
/* TICKET 1433-49/1: Symisc eXtension
* A class may not implemnt all declared interface methods,so there
* is no need for a method installer loop here.
*/
return SXRET_OK;
}
/*
* Create a class instance [i.e: Object in the PHP jargon] at run-time.
* The following function is called when an object is created at run-time
* typically when the PH7_OP_NEW/PH7_OP_CLONE instructions are executed.
* Notes on object creation.
*
* According to PHP language reference manual.
* To create an instance of a class, the new keyword must be used. An object will always
* be created unless the object has a constructor defined that throws an exception on error.
* Classes should be defined before instantiation (and in some cases this is a requirement).
* If a string containing the name of a class is used with new, a new instance of that class
* will be created. If the class is in a namespace, its fully qualified name must be used when
* doing this.
* Example #3 Creating an instance
* <?php
* $instance = new SimpleClass();
* // This can also be done with a variable:
* $className = 'Foo';
* $instance = new $className(); // Foo()
* ?>
* In the class context, it is possible to create a new object by new self and new parent.
* When assigning an already created instance of a class to a new variable, the new variable
* will access the same instance as the object that was assigned. This behaviour is the same
* when passing instances to a function. A copy of an already created object can be made by
* cloning it.
* Example #4 Object Assignment
* <?php
* class SimpleClass(){
* public $var;
* };
* $instance = new SimpleClass();
* $assigned = $instance;
* $reference =& $instance;
* $instance->var = '$assigned will have this value';
* $instance = null; // $instance and $reference become null
* var_dump($instance);
* var_dump($reference);
* var_dump($assigned);
* ?>
* The above example will output:
* NULL
* NULL
* object(SimpleClass)#1 (1) {
* ["var"]=>
* string(30) "$assigned will have this value"
* }
* Example #5 Creating new objects
* <?php
* class Test
* {
* static public function getNew()
* {
* return new static;
* }
* }
* class Child extends Test
* {}
* $obj1 = new Test();
* $obj2 = new $obj1;
* var_dump($obj1 !== $obj2);
* $obj3 = Test::getNew();
* var_dump($obj3 instanceof Test);
* $obj4 = Child::getNew();
* var_dump($obj4 instanceof Child);
* ?>
* The above example will output:
* bool(true)
* bool(true)
* bool(true)
* Note that Symisc Systems have introduced powerfull extension to
* OO subsystem. For example a class attribute may have any complex
* expression associated with it when declaring the attribute unlike
* the standard PHP engine which would allow a single value.
* Example:
* class myClass{
* public $var = 25<<1+foo()/bar();
* };
* Refer to the official documentation for more information.
*/
static ph7_class_instance * NewClassInstance(ph7_vm *pVm,ph7_class *pClass)
{
ph7_class_instance *pThis;
/* Allocate a new instance */
pThis = (ph7_class_instance *)SyMemBackendPoolAlloc(&pVm->sAllocator,sizeof(ph7_class_instance));
if( pThis == 0 ){
return 0;
}
/* Zero the structure */
SyZero(pThis,sizeof(ph7_class_instance));
/* Initialize fields */
pThis->iRef = 1;
pThis->pVm = pVm;
pThis->pClass = pClass;
SyHashInit(&pThis->hAttr,&pVm->sAllocator,0,0);
return pThis;
}
/*
* Wrapper around the NewClassInstance() function defined above.
* See the block comment above for more information.
*/
PH7_PRIVATE ph7_class_instance * PH7_NewClassInstance(ph7_vm *pVm,ph7_class *pClass)
{
ph7_class_instance *pNew;
sxi32 rc;
pNew = NewClassInstance(&(*pVm),&(*pClass));
if( pNew == 0 ){
return 0;
}
/* Associate a private VM frame with this class instance */
rc = PH7_VmCreateClassInstanceFrame(&(*pVm),pNew);
if( rc != SXRET_OK ){
SyMemBackendPoolFree(&pVm->sAllocator,pNew);
return 0;
}
return pNew;
}
/*
* Extract the value of a class instance [i.e: Object in the PHP jargon] attribute.
* This function never fail.
*/
static ph7_value * ExtractClassAttrValue(ph7_vm *pVm,VmClassAttr *pAttr)
{
/* Extract the value */
ph7_value *pValue;
pValue = (ph7_value *)SySetAt(&pVm->aMemObj,pAttr->nIdx);
return pValue;
}
/*
* Perform a clone operation on a class instance [i.e: Object in the PHP jargon].
* The following function is called when an object is cloned at run-time
* typically when the PH7_OP_CLONE instruction is executed.
* Notes on object cloning.
*
* According to PHP language reference manual.
* Creating a copy of an object with fully replicated properties is not always the wanted behavior.
* A good example of the need for copy constructors. Another example is if your object holds a reference
* to another object which it uses and when you replicate the parent object you want to create
* a new instance of this other object so that the replica has its own separate copy.
* An object copy is created by using the clone keyword (which calls the object's __clone() method if possible).
* An object's __clone() method cannot be called directly.
* $copy_of_object = clone $object;
* When an object is cloned, PHP 5 will perform a shallow copy of all of the object's properties.
* Any properties that are references to other variables, will remain references.
* Once the cloning is complete, if a __clone() method is defined, then the newly created object's __clone() method
* will be called, to allow any necessary properties that need to be changed.
* Example #1 Cloning an object
* <?php
* class SubObject
* {
* static $instances = 0;
* public $instance;
*
* public function __construct() {
* $this->instance = ++self::$instances;
* }
*
* public function __clone() {
* $this->instance = ++self::$instances;
* }
* }
*
* class MyCloneable
* {
* public $object1;
* public $object2;
*
* function __clone()
* {
* // Force a copy of this->object, otherwise
* // it will point to same object.
* $this->object1 = clone $this->object1;
* }
* }
* $obj = new MyCloneable();
* $obj->object1 = new SubObject();
* $obj->object2 = new SubObject();
* $obj2 = clone $obj;
* print("Original Object:\n");
* print_r($obj);
* print("Cloned Object:\n");
* print_r($obj2);
* ?>
* The above example will output:
* Original Object:
* MyCloneable Object
* (
* [object1] => SubObject Object
* (
* [instance] => 1
* )
*
* [object2] => SubObject Object
* (
* [instance] => 2
* )
*
* )
* Cloned Object:
* MyCloneable Object
* (
* [object1] => SubObject Object
* (
* [instance] => 3
* )
*
* [object2] => SubObject Object
* (
* [instance] => 2
* )
* )
*/
PH7_PRIVATE ph7_class_instance * PH7_CloneClassInstance(ph7_class_instance *pSrc)
{
ph7_class_instance *pClone;
ph7_class_method *pMethod;
SyHashEntry *pEntry2;
SyHashEntry *pEntry;
ph7_vm *pVm;
sxi32 rc;
/* Allocate a new instance */
pVm = pSrc->pVm;
pClone = NewClassInstance(pVm,pSrc->pClass);
if( pClone == 0 ){
return 0;
}
/* Associate a private VM frame with this class instance */
rc = PH7_VmCreateClassInstanceFrame(pVm,pClone);
if( rc != SXRET_OK ){
SyMemBackendPoolFree(&pVm->sAllocator,pClone);
return 0;
}
/* Duplicate object values */
SyHashResetLoopCursor(&pSrc->hAttr);
SyHashResetLoopCursor(&pClone->hAttr);
while((pEntry = SyHashGetNextEntry(&pSrc->hAttr)) != 0 && (pEntry2 = SyHashGetNextEntry(&pClone->hAttr)) != 0 ){
VmClassAttr *pSrcAttr = (VmClassAttr *)pEntry->pUserData;
VmClassAttr *pDestAttr = (VmClassAttr *)pEntry2->pUserData;
/* Duplicate non-static attribute */
if( (pSrcAttr->pAttr->iFlags & (PH7_CLASS_ATTR_STATIC|PH7_CLASS_ATTR_CONSTANT)) == 0 ){
ph7_value *pvSrc,*pvDest;
pvSrc = ExtractClassAttrValue(pVm,pSrcAttr);
pvDest = ExtractClassAttrValue(pVm,pDestAttr);
if( pvSrc && pvDest ){
PH7_MemObjStore(pvSrc,pvDest);
}
}
}
/* call the __clone method on the cloned object if available */
pMethod = PH7_ClassExtractMethod(pClone->pClass,"__clone",sizeof("__clone")-1);
if( pMethod ){
if( pMethod->iCloneDepth < 16 ){
pMethod->iCloneDepth++;
PH7_VmCallClassMethod(pVm,pClone,pMethod,0,0,0);
}else{
/* Nesting limit reached */
PH7_VmThrowError(pVm,0,PH7_CTX_ERR,"Object clone limit reached,no more call to __clone()");
}
/* Reset the cursor */
pMethod->iCloneDepth = 0;
}
/* Return the cloned object */
return pClone;
}
#define CLASS_INSTANCE_DESTROYED 0x001 /* Instance is released */
/*
* Release a class instance [i.e: Object in the PHP jargon] and invoke any defined destructor.
* This routine is invoked as soon as there are no other references to a particular
* class instance.
*/
static void PH7_ClassInstanceRelease(ph7_class_instance *pThis)
{
ph7_class_method *pDestr;
SyHashEntry *pEntry;
ph7_class *pClass;
ph7_vm *pVm;
if( pThis->iFlags & CLASS_INSTANCE_DESTROYED ){
/*
* Already destroyed,return immediately.
* This could happend if someone perform unset($this) in the destructor body.
*/
return;
}
/* Mark as destroyed */
pThis->iFlags |= CLASS_INSTANCE_DESTROYED;
/* Invoke any defined destructor if available */
pVm = pThis->pVm;
pClass = pThis->pClass;
pDestr = PH7_ClassExtractMethod(pClass,"__destruct",sizeof("__destruct")-1);
if( pDestr ){
/* Invoke the destructor */
pThis->iRef = 2; /* Prevent garbage collection */
PH7_VmCallClassMethod(pVm,pThis,pDestr,0,0,0);
}
/* Release non-static attributes */
SyHashResetLoopCursor(&pThis->hAttr);
while((pEntry = SyHashGetNextEntry(&pThis->hAttr)) != 0 ){
VmClassAttr *pVmAttr = (VmClassAttr *)pEntry->pUserData;
if( (pVmAttr->pAttr->iFlags & (PH7_CLASS_ATTR_STATIC|PH7_CLASS_ATTR_CONSTANT)) == 0 ){
PH7_VmUnsetMemObj(pVm,pVmAttr->nIdx,TRUE);
}
SyMemBackendPoolFree(&pVm->sAllocator,pVmAttr);
}
/* Release the whole structure */
SyHashRelease(&pThis->hAttr);
SyMemBackendPoolFree(&pVm->sAllocator,pThis);
}
/*
* Decrement the reference count of a class instance [i.e Object in the PHP jargon].
* If the reference count reaches zero,release the whole instance.
*/
PH7_PRIVATE void PH7_ClassInstanceUnref(ph7_class_instance *pThis)
{
pThis->iRef--;
if( pThis->iRef < 1 ){
/* No more reference to this instance */
PH7_ClassInstanceRelease(&(*pThis));
}
}
/*
* Compare two class instances [i.e: Objects in the PHP jargon]
* Note on objects comparison:
* According to the PHP langauge reference manual
* When using the comparison operator (==), object variables are compared in a simple manner
* namely: Two object instances are equal if they have the same attributes and values, and are
* instances of the same class.
* On the other hand, when using the identity operator (===), object variables are identical
* if and only if they refer to the same instance of the same class.
* An example will clarify these rules.
* Example #1 Example of object comparison
* <?php
* function bool2str($bool)
* {
* if ($bool === false) {
* return 'FALSE';
* } else {
* return 'TRUE';
* }
* }
* function compareObjects(&$o1, &$o2)
* {
* echo 'o1 == o2 : ' . bool2str($o1 == $o2) . "\n";
* echo 'o1 != o2 : ' . bool2str($o1 != $o2) . "\n";
* echo 'o1 === o2 : ' . bool2str($o1 === $o2) . "\n";
* echo 'o1 !== o2 : ' . bool2str($o1 !== $o2) . "\n";
* }
* class Flag
* {
* public $flag;
*
* function Flag($flag = true) {
* $this->flag = $flag;
* }
* }
*
* class OtherFlag
* {
* public $flag;
*
* function OtherFlag($flag = true) {
* $this->flag = $flag;
* }
* }
*
* $o = new Flag();
* $p = new Flag();
* $q = $o;
* $r = new OtherFlag();
*
* echo "Two instances of the same class\n";
* compareObjects($o, $p);
* echo "\nTwo references to the same instance\n";
* compareObjects($o, $q);
* echo "\nInstances of two different classes\n";
* compareObjects($o, $r);
* ?>
* The above example will output:
* Two instances of the same class
* o1 == o2 : TRUE
* o1 != o2 : FALSE
* o1 === o2 : FALSE
* o1 !== o2 : TRUE
* Two references to the same instance
* o1 == o2 : TRUE
* o1 != o2 : FALSE
* o1 === o2 : TRUE
* o1 !== o2 : FALSE
* Instances of two different classes
* o1 == o2 : FALSE
* o1 != o2 : TRUE
* o1 === o2 : FALSE
* o1 !== o2 : TRUE
*
* This function return 0 if the objects are equals according to the comprison rules defined above.
* Any other return values indicates difference.
*/
PH7_PRIVATE sxi32 PH7_ClassInstanceCmp(ph7_class_instance *pLeft,ph7_class_instance *pRight,int bStrict,int iNest)
{
SyHashEntry *pEntry,*pEntry2;
ph7_value sV1,sV2;
sxi32 rc;
if( iNest > 31 ){
/* Nesting limit reached */
PH7_VmThrowError(pLeft->pVm,0,PH7_CTX_ERR,"Nesting limit reached: Infinite recursion?");
return 1;
}
/* Comparison is performed only if the objects are instance of the same class */
if( pLeft->pClass != pRight->pClass ){
return 1;
}
if( bStrict ){
/*
* According to the PHP language reference manual:
* when using the identity operator (===), object variables
* are identical if and only if they refer to the same instance
* of the same class.
*/
return !(pLeft == pRight);
}
/*
* Attribute comparison.
* According to the PHP reference manual:
* When using the comparison operator (==), object variables are compared
* in a simple manner, namely: Two object instances are equal if they have
* the same attributes and values, and are instances of the same class.
*/
if( pLeft == pRight ){
/* Same instance,don't bother processing,object are equals */
return 0;
}
SyHashResetLoopCursor(&pLeft->hAttr);
SyHashResetLoopCursor(&pRight->hAttr);
PH7_MemObjInit(pLeft->pVm,&sV1);
PH7_MemObjInit(pLeft->pVm,&sV2);
sV1.nIdx = sV2.nIdx = SXU32_HIGH;
while((pEntry = SyHashGetNextEntry(&pLeft->hAttr)) != 0 && (pEntry2 = SyHashGetNextEntry(&pRight->hAttr)) != 0 ){
VmClassAttr *p1 = (VmClassAttr *)pEntry->pUserData;
VmClassAttr *p2 = (VmClassAttr *)pEntry2->pUserData;
/* Compare only non-static attribute */
if( (p1->pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT|PH7_CLASS_ATTR_STATIC)) == 0 ){
ph7_value *pL,*pR;
pL = ExtractClassAttrValue(pLeft->pVm,p1);
pR = ExtractClassAttrValue(pRight->pVm,p2);
if( pL && pR ){
PH7_MemObjLoad(pL,&sV1);
PH7_MemObjLoad(pR,&sV2);
/* Compare the two values now */
rc = PH7_MemObjCmp(&sV1,&sV2,bStrict,iNest+1);
PH7_MemObjRelease(&sV1);
PH7_MemObjRelease(&sV2);
if( rc != 0 ){
/* Not equals */
return rc;
}
}
}
}
/* Object are equals */
return 0;
}
/*
* Dump a class instance and the store the dump in the BLOB given
* as the first argument.
* Note that only non-static/non-constants attribute are dumped.
* This function is typically invoked when the user issue a call
* to [var_dump(),var_export(),print_r(),...].
* This function SXRET_OK on success. Any other return value including
* SXERR_LIMIT(infinite recursion) indicates failure.
*/
PH7_PRIVATE sxi32 PH7_ClassInstanceDump(SyBlob *pOut,ph7_class_instance *pThis,int ShowType,int nTab,int nDepth)
{
SyHashEntry *pEntry;
ph7_value *pValue;
sxi32 rc;
int i;
if( nDepth > 31 ){
static const char zInfinite[] = "Nesting limit reached: Infinite recursion?";
/* Nesting limit reached..halt immediately*/
SyBlobAppend(&(*pOut),zInfinite,sizeof(zInfinite)-1);
if( ShowType ){
SyBlobAppend(&(*pOut),")",sizeof(char));
}
return SXERR_LIMIT;
}
rc = SXRET_OK;
if( !ShowType ){
SyBlobAppend(&(*pOut),"Object(",sizeof("Object(")-1);
}
/* Append class name */
SyBlobFormat(&(*pOut),"%z) {",&pThis->pClass->sName);
#ifdef __WINNT__
SyBlobAppend(&(*pOut),"\r\n",sizeof("\r\n")-1);
#else
SyBlobAppend(&(*pOut),"\n",sizeof(char));
#endif
/* Dump object attributes */
SyHashResetLoopCursor(&pThis->hAttr);
while((pEntry = SyHashGetNextEntry(&pThis->hAttr)) != 0){
VmClassAttr *pVmAttr = (VmClassAttr *)pEntry->pUserData;
if((pVmAttr->pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT|PH7_CLASS_ATTR_STATIC)) == 0 ){
/* Dump non-static/constant attribute only */
for( i = 0 ; i < nTab ; i++ ){
SyBlobAppend(&(*pOut)," ",sizeof(char));
}
pValue = ExtractClassAttrValue(pThis->pVm,pVmAttr);
if( pValue ){
SyBlobFormat(&(*pOut),"['%z'] =>",&pVmAttr->pAttr->sName);
#ifdef __WINNT__
SyBlobAppend(&(*pOut),"\r\n",sizeof("\r\n")-1);
#else
SyBlobAppend(&(*pOut),"\n",sizeof(char));
#endif
rc = PH7_MemObjDump(&(*pOut),pValue,ShowType,nTab+1,nDepth,0);
if( rc == SXERR_LIMIT ){
break;
}
}
}
}
for( i = 0 ; i < nTab ; i++ ){
SyBlobAppend(&(*pOut)," ",sizeof(char));
}
SyBlobAppend(&(*pOut),"}",sizeof(char));
return rc;
}
/*
* Call a magic method [i.e: __toString(),__toBool(),__Invoke()...]
* Return SXRET_OK on successfull call. Any other return value indicates failure.
* Notes on magic methods.
* According to the PHP language reference manual.
* The function names __construct(), __destruct(), __call(), __callStatic()
* __get(), __toString(), __invoke(), __clone() are magical in PHP classes.
* You cannot have functions with these names in any of your classes unless
* you want the magic functionality associated with them.
* Example of magical methods:
* __toString()
* The __toString() method allows a class to decide how it will react when it is treated like
* a string. For example, what echo $obj; will print. This method must return a string.
* Example #2 Simple example
* <?php
* // Declare a simple class
* class TestClass
* {
* public $foo;
*
* public function __construct($foo)
* {
* $this->foo = $foo;
* }
*
* public function __toString()
* {
* return $this->foo;
* }
* }
* $class = new TestClass('Hello');
* echo $class;
* ?>
* The above example will output:
* Hello
*
* Note that PH7 does not support all the magical method and introudces __toFloat(),__toInt()
* which have the same behaviour as __toString() but for float and integer types
* respectively.
* Refer to the official documentation for more information.
*/
PH7_PRIVATE sxi32 PH7_ClassInstanceCallMagicMethod(
ph7_vm *pVm, /* VM that own all this stuff */
ph7_class *pClass, /* Target class */
ph7_class_instance *pThis, /* Target object */
const char *zMethod, /* Magic method name [i.e: __toString()]*/
sxu32 nByte, /* zMethod length*/
const SyString *pAttrName /* Attribute name */
)
{
ph7_value *apArg[2] = { 0 , 0 };
ph7_class_method *pMeth;
ph7_value sAttr; /* cc warning */
sxi32 rc;
int nArg;
/* Make sure the magic method is available */
pMeth = PH7_ClassExtractMethod(&(*pClass),zMethod,nByte);
if( pMeth == 0 ){
/* No such method,return immediately */
return SXERR_NOTFOUND;
}
nArg = 0;
/* Copy arguments */
if( pAttrName ){
PH7_MemObjInitFromString(pVm,&sAttr,pAttrName);
sAttr.nIdx = SXU32_HIGH; /* Mark as constant */
apArg[0] = &sAttr;
nArg = 1;
}
/* Call the magic method now */
rc = PH7_VmCallClassMethod(pVm,&(*pThis),pMeth,0,nArg,apArg);
/* Clean up */
if( pAttrName ){
PH7_MemObjRelease(&sAttr);
}
return rc;
}
/*
* Extract the value of a class instance [i.e: Object in the PHP jargon].
* This function is simply a wrapper on ExtractClassAttrValue().
*/
PH7_PRIVATE ph7_value * PH7_ClassInstanceExtractAttrValue(ph7_class_instance *pThis,VmClassAttr *pAttr)
{
/* Extract the attribute value */
ph7_value *pValue;
pValue = ExtractClassAttrValue(pThis->pVm,pAttr);
return pValue;
}
/*
* Convert a class instance [i.e: Object in the PHP jargon] into a hashmap [i.e: array in the PHP jargon].
* Return SXRET_OK on success. Any other value indicates failure.
* Note on object conversion to array:
* Acccording to the PHP language reference manual
* If an object is converted to an array, the result is an array whose elements are the object's properties.
* The keys are the member variable names.
*
* The following example:
* class Test {
* public $A = 25<<1; // 50
* public $c = rand_str(3); // Random string of length 3
* public $d = rand() & 1023; // Random number between 0..1023
* }
* var_dump((array) new Test());
* Will output:
* array(3) {
* [A] =>
* int(50)
* [c] =>
* string(3 'aps')
* [d] =>
* int(991)
* }
* You have noticed that PH7 allow class attributes [i.e: $a,$c,$d in the example above]
* have any complex expression (even function calls/Annonymous functions) as their default
* value unlike the standard PHP engine.
* This is a very powerful feature that you have to look at.
*/
PH7_PRIVATE sxi32 PH7_ClassInstanceToHashmap(ph7_class_instance *pThis,ph7_hashmap *pMap)
{
SyHashEntry *pEntry;
SyString *pAttrName;
VmClassAttr *pAttr;
ph7_value *pValue;
ph7_value sName;
/* Reset the loop cursor */
SyHashResetLoopCursor(&pThis->hAttr);
PH7_MemObjInitFromString(pThis->pVm,&sName,0);
while((pEntry = SyHashGetNextEntry(&pThis->hAttr)) != 0 ){
/* Point to the current attribute */
pAttr = (VmClassAttr *)pEntry->pUserData;
/* Extract attribute value */
pValue = ExtractClassAttrValue(pThis->pVm,pAttr);
if( pValue ){
/* Build attribute name */
pAttrName = &pAttr->pAttr->sName;
PH7_MemObjStringAppend(&sName,pAttrName->zString,pAttrName->nByte);
/* Perform the insertion */
PH7_HashmapInsert(pMap,&sName,pValue);
/* Reset the string cursor */
SyBlobReset(&sName.sBlob);
}
}
PH7_MemObjRelease(&sName);
return SXRET_OK;
}
/*
* Iterate throw class attributes and invoke the given callback [i.e: xWalk()] for each
* retrieved attribute.
* Note that argument are passed to the callback by copy. That is,any modification to
* the attribute value in the callback body will not alter the real attribute value.
* If the callback wishes to abort processing [i.e: it's invocation] it must return
* a value different from PH7_OK.
* Refer to [ph7_object_walk()] for more information.
*/
PH7_PRIVATE sxi32 PH7_ClassInstanceWalk(
ph7_class_instance *pThis, /* Target object */
int (*xWalk)(const char *,ph7_value *,void *), /* Walker callback */
void *pUserData /* Last argument to xWalk() */
)
{
SyHashEntry *pEntry; /* Hash entry */
VmClassAttr *pAttr; /* Pointer to the attribute */
ph7_value *pValue; /* Attribute value */
ph7_value sValue; /* Copy of the attribute value */
int rc;
/* Reset the loop cursor */
SyHashResetLoopCursor(&pThis->hAttr);
PH7_MemObjInit(pThis->pVm,&sValue);
/* Start the walk process */
while((pEntry = SyHashGetNextEntry(&pThis->hAttr)) != 0 ){
/* Point to the current attribute */
pAttr = (VmClassAttr *)pEntry->pUserData;
/* Extract attribute value */
pValue = ExtractClassAttrValue(pThis->pVm,pAttr);
if( pValue ){
PH7_MemObjLoad(pValue,&sValue);
/* Invoke the supplied callback */
rc = xWalk(SyStringData(&pAttr->pAttr->sName),&sValue,pUserData);
PH7_MemObjRelease(&sValue);
if( rc != PH7_OK){
/* User callback request an operation abort */
return SXERR_ABORT;
}
}
}
/* All done */
return SXRET_OK;
}
/*
* Extract a class atrribute value.
* Return a pointer to the attribute value on success. Otherwise NULL.
* Note:
* Access to static and constant attribute is not allowed. That is,the function
* will return NULL in case someone (host-application code) try to extract
* a static/constant attribute.
*/
PH7_PRIVATE ph7_value * PH7_ClassInstanceFetchAttr(ph7_class_instance *pThis,const SyString *pName)
{
SyHashEntry *pEntry;
VmClassAttr *pAttr;
/* Query the attribute hashtable */
pEntry = SyHashGet(&pThis->hAttr,(const void *)pName->zString,pName->nByte);
if( pEntry == 0 ){
/* No such attribute */
return 0;
}
/* Point to the class atrribute */
pAttr = (VmClassAttr *)pEntry->pUserData;
/* Check if we are dealing with a static/constant attribute */
if( pAttr->pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT|PH7_CLASS_ATTR_STATIC) ){
/* Access is forbidden */
return 0;
}
/* Return the attribute value */
return ExtractClassAttrValue(pThis->pVm,pAttr);
}