diff --git a/engine/compiler.c b/engine/compiler.c index cc77fdb..c9e49ff 100644 --- a/engine/compiler.c +++ b/engine/compiler.c @@ -3749,6 +3749,7 @@ static sxi32 PH7_GenStateCompileClass(ph7_gen_state *pGen, sxi32 iFlags) { if(pClassInfo == 0) { PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory"); } + pClass->pInfo = pClassInfo; /* Assume a standalone class */ if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_KEYWORD)) { SyString pBaseName; @@ -3987,10 +3988,8 @@ static sxi32 PH7_GenStateCompileClass(ph7_gen_state *pGen, sxi32 iFlags) { } /* Install the class */ rc = PH7_VmInstallClass(pGen->pVm, pClass); - if(iP1 || iP2) { - /* Emit the CLASS_INIT instruction only if there is such a need */ - PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_CLASS_INIT, iP1, iP2, pClassInfo, 0); - } + /* Root classes also need their singleton MRO initialized. */ + PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_CLASS_INIT, iP1, iP2, pClassInfo, 0); if(rc != SXRET_OK) { PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory"); } diff --git a/engine/oop.c b/engine/oop.c index 891d346..661f9e0 100644 --- a/engine/oop.c +++ b/engine/oop.c @@ -60,6 +60,8 @@ PH7_PRIVATE ph7_class *PH7_NewRawClass(ph7_vm *pVm, const SyString *pName) { SyHashInit(&pClass->hAttr, &pVm->sAllocator, 0, 0); SyHashInit(&pClass->hDerived, &pVm->sAllocator, 0, 0); SySetInit(&pClass->aInterface, &pVm->sAllocator, sizeof(ph7_class *)); + SySetInit(&pClass->aBase, &pVm->sAllocator, sizeof(ph7_class *)); + SySetInit(&pClass->aMro, &pVm->sAllocator, sizeof(ph7_class *)); /* All done */ return pClass; } @@ -201,6 +203,133 @@ PH7_PRIVATE sxi32 PH7_ClassInstallMethod(ph7_class *pClass, ph7_class_method *pM rc = SyHashInsert(&pClass->hMethod, (const void *)pName->zString, pName->nByte, pMeth); return rc; } +/* + * Build [C] + merge(MRO(B1), ..., MRO(Bn), [B1, ..., Bn]). + * Resolve declarations rather than hDerived: bytecode initialization may not + * have reached a forward-declared base yet. Merge cursors are private so that + * neither shared ancestors nor an autoloader can change a base's MRO. + */ +PH7_PRIVATE sxi32 PH7_ClassBuildMro(ph7_vm *pVm, ph7_class *pClass) { + SySet sBase, sMro; + ph7_class *pBase, *pCandidate; + SySet *pSeq; + sxu32 *aPos = 0; + sxu32 i, j, k, nBase, nSeq; + sxi32 rc = SXRET_OK; + int bPending, bInTail; + if(pClass->iFlags & PH7_CLASS_INTERFACE) { + return SXRET_OK; + } + if(pClass->iMroState == 2) { + return SXRET_OK; + } + if(pClass->iMroState == 1) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Inheritance cycle involving class '%z'", &pClass->sName); + return SXERR_INVALID; + } + /* Set before resolution, which can reenter through an autoloader. */ + pClass->iMroState = 1; + SySetInit(&sBase, &pVm->sAllocator, sizeof(ph7_class *)); + SySetInit(&sMro, &pVm->sAllocator, sizeof(ph7_class *)); + if(pClass->pInfo) { + for(i = 0; i < SySetUsed(&pClass->pInfo->sExtends); i++) { + SyString *pName = (SyString *)SySetAt(&pClass->pInfo->sExtends, i); + pBase = PH7_VmExtractClass(pVm, pName->zString, pName->nByte, FALSE); + if(pBase == 0) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Call to non-existent base class '%z'", pName); + } else if(pBase == pClass) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Class '%z' cannot inherit from itself", &pClass->sName); + } else if(pBase->iFlags & PH7_CLASS_INTERFACE) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Class '%z' cannot inherit from interface '%z'", &pClass->sName, pName); + } else if(pBase->iFlags & PH7_CLASS_FINAL) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Class '%z' cannot inherit from final class '%z'", &pClass->sName, pName); + } + for(j = 0; j < SySetUsed(&sBase); j++) { + if(*(ph7_class **)SySetAt(&sBase, j) == pBase) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Duplicate base class '%z' in class '%z'", &pBase->sName, &pClass->sName); + } + } + rc = SySetPut(&sBase, &pBase); + if(rc != SXRET_OK) { + goto done; + } + rc = PH7_ClassBuildMro(pVm, pBase); + if(rc != SXRET_OK) { + goto done; + } + } + } + nBase = SySetUsed(&sBase); + nSeq = nBase + 1; + aPos = (sxu32 *)SyMemBackendAlloc(&pVm->sAllocator, nSeq * sizeof(sxu32)); + if(aPos == 0) { + rc = SXERR_MEM; + goto done; + } + SyZero(aPos, nSeq * sizeof(sxu32)); + rc = SySetPut(&sMro, &pClass); + if(rc != SXRET_OK) { + goto done; + } + for(;;) { + bPending = FALSE; + pCandidate = 0; + for(i = 0; i < nSeq; i++) { + pSeq = i == nBase ? &sBase : &(*(ph7_class **)SySetAt(&sBase, i))->aMro; + if(aPos[i] >= SySetUsed(pSeq)) { + continue; + } + bPending = TRUE; + pCandidate = *(ph7_class **)SySetAt(pSeq, aPos[i]); + bInTail = FALSE; + for(j = 0; j < nSeq && !bInTail; j++) { + pSeq = j == nBase ? &sBase : &(*(ph7_class **)SySetAt(&sBase, j))->aMro; + for(k = aPos[j] + 1; k < SySetUsed(pSeq); k++) { + if(*(ph7_class **)SySetAt(pSeq, k) == pCandidate) { + bInTail = TRUE; + break; + } + } + } + if(!bInTail) { + break; + } + pCandidate = 0; + } + if(!bPending) { + break; + } + if(pCandidate == 0) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Inconsistent C3 hierarchy for class '%z'", &pClass->sName); + rc = SXERR_INVALID; + goto done; + } + rc = SySetPut(&sMro, &pCandidate); + if(rc != SXRET_OK) { + goto done; + } + for(i = 0; i < nSeq; i++) { + pSeq = i == nBase ? &sBase : &(*(ph7_class **)SySetAt(&sBase, i))->aMro; + if(aPos[i] < SySetUsed(pSeq) && *(ph7_class **)SySetAt(pSeq, aPos[i]) == pCandidate) { + aPos[i]++; + } + } + } + /* Publish only a complete linearization. */ + SySetRelease(&pClass->aBase); + SySetRelease(&pClass->aMro); + pClass->aBase = sBase; + pClass->aMro = sMro; + pClass->iMroState = 2; +done: + SyMemBackendFree(&pVm->sAllocator, aPos); + if(rc != SXRET_OK) { + SySetRelease(&sBase); + SySetRelease(&sMro); + pClass->iMroState = 0; + } + return rc; +} /* * Perform an inheritance operation. * According to the PHP language reference manual diff --git a/engine/vm.c b/engine/vm.c index 50ccc3b..58d17e6 100644 --- a/engine/vm.c +++ b/engine/vm.c @@ -451,7 +451,7 @@ static void VmLeaveFrame(ph7_vm *pVm) { } } /* Forward declaration */ -static int VmInstanceOf(ph7_class *pThis, ph7_class *pClass); +static int VmInstanceOf(ph7_vm *pVm, ph7_class *pThis, ph7_class *pClass); /* * Ranks for general parameter types. Class and interface specificity uses * the shortest inheritance distance instead, which is always lower than @@ -2577,7 +2577,7 @@ static sxi32 VmByteCodeExec( } if(pThis) { /* Perform the query */ - iRes = VmInstanceOf(pThis->pClass, pClass); + iRes = VmInstanceOf(pVm, pThis->pClass, pClass); } } else { rc = PH7_MemObjCmp(pNos, pTos, FALSE, 0); @@ -4195,7 +4195,7 @@ static sxi32 VmByteCodeExec( /* Make sure the loaded object is an instance of the 'Exception' base class. */ pException = PH7_VmExtractClass(&(*pVm), "Exception", sizeof("Exception") - 1, TRUE); - if(pException == 0 || !VmInstanceOf(pThis->pClass, pException)) { + if(pException == 0 || !VmInstanceOf(pVm, pThis->pClass, pException)) { /* Exceptions must be valid objects derived from the Exception base class */ rc = VmUncaughtException(&(*pVm), pThis); if(rc == SXERR_ABORT) { @@ -4307,21 +4307,15 @@ static sxi32 VmByteCodeExec( ph7_class_info *pClassInfo = (ph7_class_info *)pInstr->p3; ph7_class *pClass = PH7_VmExtractClass(pVm, pClassInfo->sName.zString, pClassInfo->sName.nByte, FALSE); ph7_class *pBase = 0; + rc = PH7_ClassBuildMro(pVm, pClass); + if(rc != SXRET_OK) { + goto Abort; + } if(pInstr->iP1) { /* This class inherits from other classes */ - SyString *apExtends; - while(SySetGetNextEntry(&pClassInfo->sExtends, (void **)&apExtends) == SXRET_OK) { - pBase = PH7_VmExtractClass(pVm, apExtends->zString, apExtends->nByte, FALSE); - if(pBase == 0) { - /* Non-existent base class */ - PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Call to non-existent base class '%z'", &apExtends->zString); - } else if(pBase->iFlags & PH7_CLASS_INTERFACE) { - /* Trying to inherit from interface */ - PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Class '%z' cannot inherit from interface '%z'", &pClass->sName.zString, &apExtends->zString); - } else if(pBase->iFlags & PH7_CLASS_FINAL) { - /* Trying to inherit from final class */ - PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Class '%z' cannot inherit from final class '%z'", &pClass->sName.zString, &apExtends->zString); - } + sxu32 i; + for(i = 0; i < SySetUsed(&pClass->aBase); i++) { + pBase = *(ph7_class **)SySetAt(&pClass->aBase, i); rc = PH7_ClassInherit(pVm, pClass, pBase); if(rc != SXRET_OK) { break; @@ -4495,7 +4489,7 @@ static sxi32 VmByteCodeExec( if(sName.nByte > 0) { /* Extract the target method */ if(pNos->iFlags != MEMOBJ_PARENTOBJ) { - if(pScope && VmInstanceOf(pClass, pScope)) { + if(pScope && VmInstanceOf(pVm, pClass, pScope)) { pMeth = PH7_ClassExtractMethod(pScope, sName.zString, sName.nByte); if(pMeth && (pMeth->iProtection != PH7_CLASS_PROT_PRIVATE || pMeth->sFunc.pClass != pScope)) { pMeth = 0; @@ -4544,7 +4538,7 @@ static sxi32 VmByteCodeExec( /* Extract the target attribute */ if(pNos->iFlags != MEMOBJ_PARENTOBJ) { /* Private fields belong to their declaring class, even on a derived object. */ - if(pScope && VmInstanceOf(pClass, pScope)) { + if(pScope && VmInstanceOf(pVm, pClass, pScope)) { pAttr = PH7_ClassExtractAttribute(pScope, sName.zString, sName.nByte); if(pAttr && pAttr->iProtection != PH7_CLASS_PROT_PRIVATE) { pAttr = 0; @@ -5092,7 +5086,7 @@ static sxi32 VmByteCodeExec( } else { ph7_class_instance *pThis = (ph7_class_instance *)pArg->x.pOther; /* Make sure the object is an instance of the given class */ - if(pThis == 0 || !VmInstanceOf(pThis->pClass, pClass)) { + if(pThis == 0 || !VmInstanceOf(pVm, pThis->pClass, pClass)) { PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Argument %u passed to function '%z()' must be an object of type '%z'", n+1, &pVmFunc->sName, pName); @@ -5207,7 +5201,7 @@ static sxi32 VmByteCodeExec( } else { ph7_class_instance *pThis = (ph7_class_instance *)pObj->x.pOther; /* Make sure the object is an instance of the given class */ - if(pThis == 0 || !VmInstanceOf(pThis->pClass, pClass)) { + if(pThis == 0 || !VmInstanceOf(pVm, pThis->pClass, pClass)) { PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Default value for argument %u of '%z()' must be an object of type '%z'", n+1, &pVmFunc->sName, pName); @@ -6258,7 +6252,7 @@ static int VmClassMemberAccess( } else { /* Protected */ /* Must be a derived class */ - if(!VmInstanceOf(pScope, pClass)) { + if(!VmInstanceOf(pVm, pScope, pClass)) { return 0; /* Access is forbidden */ } } @@ -6428,7 +6422,7 @@ static int VmQueryInterfaceSet(ph7_class *pClass, SySet *pSet) { * is an instance of the main class (second argument). * Otherwise FALSE is returned. */ -static int VmInstanceOf(ph7_class *pThis, ph7_class *pClass) { +static int VmInstanceOf(ph7_vm *pVm, ph7_class *pThis, ph7_class *pClass) { SyHashEntry *pEntry; ph7_class *pDerived, *pParent; sxi32 rc; @@ -6436,6 +6430,18 @@ static int VmInstanceOf(ph7_class *pThis, ph7_class *pClass) { /* Instance of the same class */ return TRUE; } + if(!(pThis->iFlags & PH7_CLASS_INTERFACE) && !(pClass->iFlags & PH7_CLASS_INTERFACE)) { + sxu32 i; + if(PH7_ClassBuildMro(pVm, pThis) != SXRET_OK) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "PH7 is running out-of-memory"); + } + for(i = 1; i < SySetUsed(&pThis->aMro); i++) { + if(*(ph7_class **)SySetAt(&pThis->aMro, i) == pClass) { + return TRUE; + } + } + return FALSE; + } /* Check implemented interfaces */ rc = VmQueryInterfaceSet(pClass, &pThis->aInterface); if(rc) { @@ -6478,11 +6484,14 @@ static int VmInstanceOf(ph7_class *pThis, ph7_class *pClass) { * is a subclass of the main class (second argument). * Otherwise FALSE is returned. */ -static int VmSubclassOf(ph7_class *pClass, ph7_class *pBase) { +static int VmSubclassOf(ph7_vm *pVm, ph7_class *pClass, ph7_class *pBase) { SySet *pInterface = &pClass->aInterface; SyHashEntry *pEntry; SyString *pName; sxi32 rc; + if(!(pClass->iFlags & PH7_CLASS_INTERFACE) && !(pBase->iFlags & PH7_CLASS_INTERFACE)) { + return pClass != pBase && VmInstanceOf(pVm, pClass, pBase); + } while(pClass) { pName = &pClass->sName; /* Query the derived hashtable */ @@ -6520,7 +6529,7 @@ static int vm_builtin_is_a(ph7_context *pCtx, int nArg, ph7_value **apArg) { pClass = VmExtractClassFromValue(pCtx->pVm, apArg[1]); if(pClass) { /* Perform the query */ - res = VmInstanceOf(pThis->pClass, pClass); + res = VmInstanceOf(pCtx->pVm, pThis->pClass, pClass); } } /* Query result */ @@ -6548,7 +6557,7 @@ static int vm_builtin_is_subclass_of(ph7_context *pCtx, int nArg, ph7_value **ap pMain = VmExtractClassFromValue(pCtx->pVm, apArg[1]); if(pClass && pMain) { /* Perform the query */ - res = VmSubclassOf(pClass, pMain); + res = VmSubclassOf(pCtx->pVm, pClass, pMain); } } /* Query result */ @@ -8288,7 +8297,7 @@ static sxi32 VmThrowException( /* No such class */ continue; } - if(VmInstanceOf(pThis->pClass, pClass)) { + if(VmInstanceOf(pVm, pThis->pClass, pClass)) { /* Catch block found,break immediately */ pCatch = &aCatch[j]; break; diff --git a/include/ph7int.h b/include/ph7int.h index b1f7b31..bfd7c56 100644 --- a/include/ph7int.h +++ b/include/ph7int.h @@ -1043,6 +1043,10 @@ struct ph7_class { SyHash hAttr; /* Class attributes [i.e: variables and constants] */ SyHash hMethod; /* Class methods */ SySet aInterface; /* Implemented interface container */ + ph7_class_info *pInfo; /* Declaration used to resolve forward bases */ + SySet aBase; /* Direct class bases in extends order */ + SySet aMro; /* C3 linearization, including this class first */ + sxi32 iMroState; /* 0: unbuilt, 1: building, 2: ready */ }; /* Class configuration flags */ #define PH7_CLASS_FINAL 0x001 /* Class is final [cannot be extended] */ @@ -1766,6 +1770,7 @@ PH7_PRIVATE ph7_class_attr *PH7_ClassExtractAttribute(ph7_class *pClass, cons PH7_PRIVATE sxi32 PH7_ClassInstallAttr(ph7_class *pClass, ph7_class_attr *pAttr); 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_ClassBuildMro(ph7_vm *pVm, ph7_class *pClass); 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 ph7_class_instance *PH7_NewClassInstance(ph7_vm *pVm, ph7_class *pClass);