Aer/engine/oop.c
belliash b040886b97
Test and temporary version of compiler emiting PH7_OP_CLASS_INIT instruction.
However it works on ph7_class and thus passes whole class into the VM, what causes memory overhead,
as finally we have to find this class on the VM's stack. Instead, we could pass some ph7_class_info
structure containing a name of class to look for and information about its inheritances.
2018-07-27 08:24:53 +02:00

1116 lines
38 KiB
C

/*
* 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 *));
SySetInit(&pClass->sExtends, &pVm->sAllocator, sizeof(SyString));
SySetInit(&pClass->sImplements, &pVm->sAllocator, sizeof(SyString));
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;
}
/* 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 implement 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);
}