Validate interface contracts against effective C3 implementations and support transitive interface checks
Build / AerScript (push) Successful in 39s
Build / AerScript (push) Successful in 39s
This commit is contained in:
@@ -3568,6 +3568,7 @@ static sxi32 PH7_CompileClassInterface(ph7_gen_state *pGen) {
|
|||||||
PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory");
|
PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory");
|
||||||
}
|
}
|
||||||
/* Mark as an interface */
|
/* Mark as an interface */
|
||||||
|
pClass->pInfo = pClassInfo;
|
||||||
pClass->iFlags = PH7_CLASS_INTERFACE;
|
pClass->iFlags = PH7_CLASS_INTERFACE;
|
||||||
if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD)) {
|
if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD)) {
|
||||||
SyString pBaseName;
|
SyString pBaseName;
|
||||||
|
|||||||
+150
-23
@@ -398,6 +398,135 @@ done:
|
|||||||
SySetRelease(&sMethods);
|
SySetRelease(&sMethods);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
* Collect nominal interfaces from declarations, including forward declarations.
|
||||||
|
* The caller supplies an empty set and a complete class MRO. The growing set
|
||||||
|
* is also the visited set, so interface cycles are harmless.
|
||||||
|
*/
|
||||||
|
PH7_PRIVATE sxi32 PH7_ClassCollectInterfaces(ph7_vm *pVm, ph7_class *pClass, SySet *pSet) {
|
||||||
|
ph7_class *pOwner, *pInterface;
|
||||||
|
SySet *pNames;
|
||||||
|
sxu32 i, j, k, n;
|
||||||
|
sxi32 rc;
|
||||||
|
sxi32 bInterface = (pClass->iFlags & PH7_CLASS_INTERFACE) != 0;
|
||||||
|
if(bInterface) {
|
||||||
|
rc = SySetPut(pSet, &pClass);
|
||||||
|
if(rc != SXRET_OK) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n = bInterface ? 0 : SySetUsed(&pClass->aMro);
|
||||||
|
for(i = 0; i < n + SySetUsed(pSet); i++) {
|
||||||
|
pOwner = i < n ? *(ph7_class **)SySetAt(&pClass->aMro, i) : *(ph7_class **)SySetAt(pSet, i - n);
|
||||||
|
pNames = pOwner->pInfo ? ((pOwner->iFlags & PH7_CLASS_INTERFACE) ?
|
||||||
|
&pOwner->pInfo->sExtends : &pOwner->pInfo->sImplements) : 0;
|
||||||
|
for(j = 0; j < (pNames ? SySetUsed(pNames) : 0) + SySetUsed(&pOwner->aInterface); j++) {
|
||||||
|
if(pNames && j < SySetUsed(pNames)) {
|
||||||
|
SyString *pName = (SyString *)SySetAt(pNames, j);
|
||||||
|
pInterface = PH7_VmExtractClass(pVm, pName->zString, pName->nByte, FALSE);
|
||||||
|
if(pInterface == 0) {
|
||||||
|
PH7_VmThrowError(pVm, PH7_CTX_ERR, (pOwner->iFlags & PH7_CLASS_INTERFACE) ?
|
||||||
|
"Call to non-existent base interface '%z'" : "Call to non-existent interface '%z'", pName);
|
||||||
|
return SXERR_INVALID;
|
||||||
|
}
|
||||||
|
if(!(pInterface->iFlags & PH7_CLASS_INTERFACE)) {
|
||||||
|
PH7_VmThrowError(pVm, PH7_CTX_ERR, (pOwner->iFlags & PH7_CLASS_INTERFACE) ?
|
||||||
|
"Interface '%z' cannot inherit from class '%z'" : "Class '%z' cannot implement a class '%z'", &pOwner->sName, pName);
|
||||||
|
return SXERR_INVALID;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pInterface = *(ph7_class **)SySetAt(&pOwner->aInterface, j - (pNames ? SySetUsed(pNames) : 0));
|
||||||
|
}
|
||||||
|
for(k = 0; k < SySetUsed(pSet); k++) {
|
||||||
|
if(*(ph7_class **)SySetAt(pSet, k) == pInterface) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(k == SySetUsed(pSet)) {
|
||||||
|
rc = SySetPut(pSet, &pInterface);
|
||||||
|
if(rc != SXRET_OK) {
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SXRET_OK;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Interface obligations use exact input identity and the first concrete C3
|
||||||
|
* implementation. Virtual declarations remain contracts, not implementations.
|
||||||
|
*/
|
||||||
|
static sxi32 ClassValidateInterfaces(ph7_vm *pVm, ph7_class *pClass) {
|
||||||
|
SySet sInterfaces, sRequired;
|
||||||
|
SyHashEntry *pEntry;
|
||||||
|
ph7_class_method *pMeth, *pOther, *pImpl;
|
||||||
|
sxu32 i, j;
|
||||||
|
sxi32 rc;
|
||||||
|
SySetInit(&sInterfaces, &pVm->sAllocator, sizeof(ph7_class *));
|
||||||
|
SySetInit(&sRequired, &pVm->sAllocator, sizeof(ph7_class_method *));
|
||||||
|
rc = PH7_ClassCollectInterfaces(pVm, pClass, &sInterfaces);
|
||||||
|
if(rc != SXRET_OK) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
for(i = 0; i < SySetUsed(&sInterfaces); i++) {
|
||||||
|
ph7_class *pOwner = *(ph7_class **)SySetAt(&sInterfaces, i);
|
||||||
|
SyHashResetLoopCursor(&pOwner->hMethod);
|
||||||
|
while((pEntry = SyHashGetNextEntry(&pOwner->hMethod)) != 0) {
|
||||||
|
pMeth = (ph7_class_method *)pEntry->pUserData;
|
||||||
|
if(pMeth->sFunc.pClass == pOwner) {
|
||||||
|
rc = SySetPut(&sRequired, &pMeth);
|
||||||
|
if(rc != SXRET_OK) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(i = 0; i < SySetUsed(&sRequired); i++) {
|
||||||
|
pMeth = *(ph7_class_method **)SySetAt(&sRequired, i);
|
||||||
|
for(j = 0; j < i; j++) {
|
||||||
|
pOther = *(ph7_class_method **)SySetAt(&sRequired, j);
|
||||||
|
if(PH7_VmSameSignature(&pMeth->sFunc, &pOther->sFunc) &&
|
||||||
|
(pMeth->sFunc.nType != pOther->sFunc.nType ||
|
||||||
|
((pMeth->sFunc.iFlags ^ pOther->sFunc.iFlags) & VM_FUNC_REF_RETURN))) {
|
||||||
|
PH7_VmThrowError(pVm, PH7_CTX_ERR, "Conflicting interface contract '%z::%z()' inside class '%z'", &pMeth->sFunc.pClass->sName, &pMeth->sFunc.sName, &pClass->sName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(i = 0; i < SySetUsed(&sRequired); i++) {
|
||||||
|
pMeth = *(ph7_class_method **)SySetAt(&sRequired, i);
|
||||||
|
pImpl = 0;
|
||||||
|
for(j = 0; j < SySetUsed(&pClass->aMro); j++) {
|
||||||
|
ph7_class *pOwner = *(ph7_class **)SySetAt(&pClass->aMro, j);
|
||||||
|
SyHashResetLoopCursor(&pOwner->hMethod);
|
||||||
|
while((pEntry = SyHashGetNextEntry(&pOwner->hMethod)) != 0) {
|
||||||
|
pOther = (ph7_class_method *)pEntry->pUserData;
|
||||||
|
if(pOther->sFunc.pClass == pOwner && PH7_VmSameSignature(&pMeth->sFunc, &pOther->sFunc)) {
|
||||||
|
/* Inherited private helpers are only callable in their own scope. */
|
||||||
|
if(pOther->iProtection == PH7_CLASS_PROT_PRIVATE && pOwner != pClass) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(!pImpl || (pOther->iFlags & PH7_CLASS_ATTR_VIRTUAL)) {
|
||||||
|
if(pOther->iProtection != PH7_CLASS_PROT_PUBLIC ||
|
||||||
|
(pOther->iFlags & PH7_CLASS_ATTR_STATIC) || pOther->sFunc.nType != pMeth->sFunc.nType ||
|
||||||
|
((pOther->sFunc.iFlags ^ pMeth->sFunc.iFlags) & VM_FUNC_REF_RETURN)) {
|
||||||
|
PH7_VmThrowError(pVm, PH7_CTX_ERR, "Incompatible interface implementation '%z::%z()' inside class '%z'", &pMeth->sFunc.pClass->sName, &pMeth->sFunc.sName, &pClass->sName);
|
||||||
|
}
|
||||||
|
if(!(pOther->iFlags & PH7_CLASS_ATTR_VIRTUAL)) {
|
||||||
|
pImpl = pOther;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!pImpl && !(pClass->iFlags & PH7_CLASS_VIRTUAL)) {
|
||||||
|
PH7_VmThrowError(pVm, PH7_CTX_ERR, "Method '%z:%z()' must be defined inside class '%z'", &pMeth->sFunc.pClass->sName, &pMeth->sFunc.sName, &pClass->sName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done:
|
||||||
|
SySetRelease(&sInterfaces);
|
||||||
|
SySetRelease(&sRequired);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* Build [C] + merge(MRO(B1), ..., MRO(Bn), [B1, ..., Bn]).
|
* Build [C] + merge(MRO(B1), ..., MRO(Bn), [B1, ..., Bn]).
|
||||||
* Resolve declarations rather than hDerived: bytecode initialization may not
|
* Resolve declarations rather than hDerived: bytecode initialization may not
|
||||||
@@ -515,13 +644,11 @@ PH7_PRIVATE sxi32 PH7_ClassBuildMro(ph7_vm *pVm, ph7_class *pClass) {
|
|||||||
SySetRelease(&pClass->aMro);
|
SySetRelease(&pClass->aMro);
|
||||||
pClass->aBase = sBase;
|
pClass->aBase = sBase;
|
||||||
pClass->aMro = sMro;
|
pClass->aMro = sMro;
|
||||||
pClass->iMroState = 2;
|
|
||||||
done:
|
done:
|
||||||
SyMemBackendFree(&pVm->sAllocator, aPos);
|
SyMemBackendFree(&pVm->sAllocator, aPos);
|
||||||
if(rc != SXRET_OK) {
|
if(rc != SXRET_OK) {
|
||||||
SySetRelease(&sBase);
|
SySetRelease(&sBase);
|
||||||
SySetRelease(&sMro);
|
SySetRelease(&sMro);
|
||||||
pClass->iMroState = 0;
|
|
||||||
}
|
}
|
||||||
if(rc == SXRET_OK) {
|
if(rc == SXRET_OK) {
|
||||||
rc = ClassValidateAttributes(pVm, pClass);
|
rc = ClassValidateAttributes(pVm, pClass);
|
||||||
@@ -529,6 +656,12 @@ done:
|
|||||||
if(rc == SXRET_OK) {
|
if(rc == SXRET_OK) {
|
||||||
rc = ClassValidateMethods(pVm, pClass);
|
rc = ClassValidateMethods(pVm, pClass);
|
||||||
}
|
}
|
||||||
|
if(rc == SXRET_OK) {
|
||||||
|
rc = ClassValidateInterfaces(pVm, pClass);
|
||||||
|
}
|
||||||
|
/* Keep the reentrancy guard through validation. On failure the class owns
|
||||||
|
* any published arrays, but a retry must rebuild and validate them again. */
|
||||||
|
pClass->iMroState = rc == SXRET_OK ? 2 : 0;
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
@@ -650,7 +783,7 @@ PH7_PRIVATE sxi32 PH7_ClassInherit(ph7_vm *pVm, ph7_class *pSub, ph7_class *pBas
|
|||||||
PH7_PRIVATE sxi32 PH7_ClassInterfaceInherit(ph7_class *pSub, ph7_class *pBase) {
|
PH7_PRIVATE sxi32 PH7_ClassInterfaceInherit(ph7_class *pSub, ph7_class *pBase) {
|
||||||
ph7_class_method *pMeth;
|
ph7_class_method *pMeth;
|
||||||
ph7_class_attr *pAttr;
|
ph7_class_attr *pAttr;
|
||||||
SyHashEntry *pEntry;
|
SyHashEntry *pEntry, *pOther;
|
||||||
SyString *pName;
|
SyString *pName;
|
||||||
sxi32 rc;
|
sxi32 rc;
|
||||||
/* Install in the derived hashtable */
|
/* Install in the derived hashtable */
|
||||||
@@ -669,20 +802,27 @@ PH7_PRIVATE sxi32 PH7_ClassInterfaceInherit(ph7_class *pSub, ph7_class *pBase) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* Preserve all overloads for member enumeration. Contracts themselves are
|
||||||
|
* read from declarations, independently of bytecode initialization order. */
|
||||||
|
if(pSub != pBase) {
|
||||||
SyHashResetLoopCursor(&pBase->hMethod);
|
SyHashResetLoopCursor(&pBase->hMethod);
|
||||||
/* Copy methods signature */
|
|
||||||
while((pEntry = SyHashGetNextEntry(&pBase->hMethod)) != 0) {
|
while((pEntry = SyHashGetNextEntry(&pBase->hMethod)) != 0) {
|
||||||
/* Make sure the method are not redeclared in the subclass */
|
|
||||||
pMeth = (ph7_class_method *)pEntry->pUserData;
|
pMeth = (ph7_class_method *)pEntry->pUserData;
|
||||||
|
SyHashResetLoopCursor(&pSub->hMethod);
|
||||||
|
while((pOther = SyHashGetNextEntry(&pSub->hMethod)) != 0) {
|
||||||
|
if(pOther->pUserData == pMeth) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!pOther) {
|
||||||
pName = &pMeth->sFunc.sName;
|
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);
|
rc = SyHashInsert(&pSub->hMethod, (const void *)pName->zString, pName->nByte, pMeth);
|
||||||
if(rc != SXRET_OK) {
|
if(rc != SXRET_OK) {
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/* Mark as subclass */
|
/* Mark as subclass */
|
||||||
pSub->pBase = pBase;
|
pSub->pBase = pBase;
|
||||||
/* All done */
|
/* All done */
|
||||||
@@ -702,24 +842,11 @@ PH7_PRIVATE sxi32 PH7_ClassInterfaceInherit(ph7_class *pSub, ph7_class *pBase) {
|
|||||||
* error message.
|
* error message.
|
||||||
*/
|
*/
|
||||||
PH7_PRIVATE sxi32 PH7_ClassImplement(ph7_vm *pVm, ph7_class *pMain, ph7_class *pInterface) {
|
PH7_PRIVATE sxi32 PH7_ClassImplement(ph7_vm *pVm, ph7_class *pMain, ph7_class *pInterface) {
|
||||||
ph7_class_method *pMeth;
|
SXUNUSED(pVm);
|
||||||
SyHashEntry *pEntry;
|
|
||||||
SyString *pName;
|
|
||||||
/* Interface constants are not inherited by the implementing class.
|
/* Interface constants are not inherited by the implementing class.
|
||||||
* They remain reachable through the interface name only. */
|
* They remain reachable through the interface name only. */
|
||||||
SyHashResetLoopCursor(&pInterface->hMethod);
|
/* Contracts were checked against the complete C3 MRO, before mounting. */
|
||||||
while((pEntry = SyHashGetNextEntry(&pInterface->hMethod)) != 0) {
|
return SySetPut(&pMain->aInterface, (const void *)&pInterface);
|
||||||
pMeth = (ph7_class_method *)pEntry->pUserData;
|
|
||||||
pName = &pMeth->sFunc.sName;
|
|
||||||
if((pEntry = SyHashGet(&pMain->hMethod, (const void *)pName->zString, pName->nByte)) != 0) {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Method '%z:%z()' must be defined inside class '%z'", &pInterface->sName, pName, &pMain->sName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Install in the interface container */
|
|
||||||
SySetPut(&pMain->aInterface, (const void *)&pInterface);
|
|
||||||
return SXRET_OK;
|
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* Create a class instance [i.e: Object in the PHP jargon] at run-time.
|
* Create a class instance [i.e: Object in the PHP jargon] at run-time.
|
||||||
|
|||||||
+15
-37
@@ -6645,14 +6645,16 @@ static int VmQueryInterfaceSet(ph7_class *pClass, SySet *pSet) {
|
|||||||
* Otherwise FALSE is returned.
|
* Otherwise FALSE is returned.
|
||||||
*/
|
*/
|
||||||
static int VmInstanceOf(ph7_vm *pVm, ph7_class *pThis, ph7_class *pClass) {
|
static int VmInstanceOf(ph7_vm *pVm, ph7_class *pThis, ph7_class *pClass) {
|
||||||
SyHashEntry *pEntry;
|
SySet sInterfaces;
|
||||||
ph7_class *pDerived, *pParent;
|
|
||||||
sxi32 rc;
|
sxi32 rc;
|
||||||
|
if(!pThis || !pClass) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
if(pThis == pClass) {
|
if(pThis == pClass) {
|
||||||
/* Instance of the same class */
|
/* Instance of the same class */
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
if(!(pThis->iFlags & PH7_CLASS_INTERFACE) && !(pClass->iFlags & PH7_CLASS_INTERFACE)) {
|
if(!(pThis->iFlags & PH7_CLASS_INTERFACE)) {
|
||||||
sxu32 i;
|
sxu32 i;
|
||||||
if(PH7_ClassBuildMro(pVm, pThis) != SXRET_OK) {
|
if(PH7_ClassBuildMro(pVm, pThis) != SXRET_OK) {
|
||||||
PH7_VmThrowError(pVm, PH7_CTX_ERR, "PH7 is running out-of-memory");
|
PH7_VmThrowError(pVm, PH7_CTX_ERR, "PH7 is running out-of-memory");
|
||||||
@@ -6662,45 +6664,21 @@ static int VmInstanceOf(ph7_vm *pVm, ph7_class *pThis, ph7_class *pClass) {
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(!(pClass->iFlags & PH7_CLASS_INTERFACE)) {
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
/* Check implemented interfaces */
|
|
||||||
rc = VmQueryInterfaceSet(pClass, &pThis->aInterface);
|
|
||||||
if(rc) {
|
|
||||||
return TRUE;
|
|
||||||
}
|
}
|
||||||
/* Check derived classes */
|
SySetInit(&sInterfaces, &pVm->sAllocator, sizeof(ph7_class *));
|
||||||
SyHashResetLoopCursor(&pThis->hDerived);
|
rc = PH7_ClassCollectInterfaces(pVm, pThis, &sInterfaces);
|
||||||
while((pEntry = SyHashGetNextEntry(&pThis->hDerived)) != 0) {
|
if(rc != SXRET_OK) {
|
||||||
pDerived = (ph7_class *) pEntry->pUserData;
|
SySetRelease(&sInterfaces);
|
||||||
if(pDerived == pClass) {
|
PH7_VmMemoryError(pVm);
|
||||||
/* Same instance */
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
/* Check the implemented interfaces */
|
|
||||||
rc = VmQueryInterfaceSet(pClass, &pDerived->aInterface);
|
|
||||||
if(rc) {
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
/* Check parent classes */
|
|
||||||
pParent = pDerived->pBase;
|
|
||||||
while(pParent) {
|
|
||||||
if(pParent == pClass) {
|
|
||||||
/* Same instance */
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
/* Check the implemented interfaces */
|
|
||||||
rc = VmQueryInterfaceSet(pClass, &pParent->aInterface);
|
|
||||||
if(rc) {
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
/* Point to the parent class */
|
|
||||||
pParent = pParent->pBase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Not an instance of the the given class */
|
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
rc = VmQueryInterfaceSet(pClass, &sInterfaces);
|
||||||
|
SySetRelease(&sInterfaces);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
* This function returns TRUE if the given class (first argument)
|
* This function returns TRUE if the given class (first argument)
|
||||||
* is a subclass of the main class (second argument).
|
* is a subclass of the main class (second argument).
|
||||||
|
|||||||
@@ -1774,6 +1774,7 @@ PH7_PRIVATE sxi32 PH7_ClassInstallAttr(ph7_gen_state *pGen, ph7_class *pClass, p
|
|||||||
PH7_PRIVATE sxi32 PH7_ClassInstallMethod(ph7_class *pClass, ph7_class_method *pMeth);
|
PH7_PRIVATE sxi32 PH7_ClassInstallMethod(ph7_class *pClass, ph7_class_method *pMeth);
|
||||||
PH7_PRIVATE sxi32 PH7_ClassInherit(ph7_vm *pVm, ph7_class *pSub, ph7_class *pBase);
|
PH7_PRIVATE sxi32 PH7_ClassInherit(ph7_vm *pVm, ph7_class *pSub, ph7_class *pBase);
|
||||||
PH7_PRIVATE sxi32 PH7_ClassBuildMro(ph7_vm *pVm, ph7_class *pClass);
|
PH7_PRIVATE sxi32 PH7_ClassBuildMro(ph7_vm *pVm, ph7_class *pClass);
|
||||||
|
PH7_PRIVATE sxi32 PH7_ClassCollectInterfaces(ph7_vm *pVm, ph7_class *pClass, SySet *pSet);
|
||||||
PH7_PRIVATE sxi32 PH7_ClassInterfaceInherit(ph7_class *pSub, ph7_class *pBase);
|
PH7_PRIVATE sxi32 PH7_ClassInterfaceInherit(ph7_class *pSub, ph7_class *pBase);
|
||||||
PH7_PRIVATE sxi32 PH7_ClassImplement(ph7_vm *pVm, ph7_class *pMain, ph7_class *pInterface);
|
PH7_PRIVATE sxi32 PH7_ClassImplement(ph7_vm *pVm, ph7_class *pMain, ph7_class *pInterface);
|
||||||
PH7_PRIVATE ph7_class_instance *PH7_NewClassInstance(ph7_vm *pVm, ph7_class *pClass);
|
PH7_PRIVATE ph7_class_instance *PH7_NewClassInstance(ph7_vm *pVm, ph7_class *pClass);
|
||||||
|
|||||||
Reference in New Issue
Block a user