Implement C3 class linearization and validate inheritance graphs
Build / AerScript (push) Successful in 2m13s

This commit is contained in:
2026-10-24 01:16:34 +02:00
parent 323793153c
commit bc5e5f6a8e
4 changed files with 172 additions and 30 deletions
+129
View File
@@ -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