Implement cooperative parent calls across runtime C3 MRO and enforce lifecycle method rules
Build / AerScript (push) Successful in 41s

This commit is contained in:
2026-10-24 19:20:52 +02:00
parent d511414453
commit c6e7151e72
3 changed files with 96 additions and 50 deletions
+89 -49
View File
@@ -811,6 +811,23 @@ static sxi32 VmExecIncludedFile(ph7_vm *pVm, SyString *pPath, int iFlags);
static sxi32 VmLocalExec(ph7_vm *pVm, SySet *pByteCode, ph7_value *pResult);
static sxi32 VmClassMemberAccess(ph7_vm *pVm, ph7_class *pClass, sxi32 iProtection);
static ph7_class *VmMethodClass(ph7_vm *pVm);
static sxi32 VmCallClassMethod(ph7_vm *pVm, ph7_class_instance *pThis, ph7_class_method *pMethod,
ph7_value *pResult, int nArg, ph7_value **apArg, sxi32 bInternal);
/* Cooperative calls continue after the lexical owner in the receiver's C3 order. */
static sxu32 VmMethodStart(ph7_vm *pVm, ph7_class *pLookup, sxi32 bParent) {
sxu32 i;
ph7_class *pScope;
if(!bParent) {
return 0;
}
pScope = VmMethodClass(pVm);
for(i = 0; i < SySetUsed(&pLookup->aMro); i++) {
if(*(ph7_class **)SySetAt(&pLookup->aMro, i) == pScope) {
return i + 1;
}
}
return SySetUsed(&pLookup->aMro);
}
/* Find a mounted name to transport a method call, not an eligible overload.
* The caller must retain its lookup root and receiver for VmOverload(). */
static ph7_class_method *VmMethodSeed(ph7_vm *pVm, ph7_class *pLookup, SyString *pName,
@@ -819,7 +836,7 @@ static ph7_class_method *VmMethodSeed(ph7_vm *pVm, ph7_class *pLookup, SyString
if(pPreferred && SyHashGet(&pVm->hFunction, pPreferred->sVmName.zString, pPreferred->sVmName.nByte)) {
return pPreferred;
}
for(i = bParent ? 1 : 0; i < SySetUsed(&pLookup->aMro); i++) {
for(i = VmMethodStart(pVm, pLookup, bParent); i < SySetUsed(&pLookup->aMro); i++) {
ph7_class *pClass = *(ph7_class **)SySetAt(&pLookup->aMro, i);
ph7_class_method *pMeth = PH7_ClassExtractMethod(pClass, pName->zString, pName->nByte);
if(pMeth && pMeth->sFunc.pClass == pClass &&
@@ -994,7 +1011,7 @@ static ph7_vm_func *VmOverload(
}
/* Walk C3 without modifying any declaration's overload chain. Only
* accessible declarations participate in identical-signature hiding. */
for(i = bParent ? 1 : 0; i < SySetUsed(&pLookup->aMro); i++) {
for(i = VmMethodStart(pVm, pLookup, bParent); i < SySetUsed(&pLookup->aMro); i++) {
ph7_class *pClass = *(ph7_class **)SySetAt(&pLookup->aMro, i);
ph7_class_method *pMeth = PH7_ClassExtractMethod(pClass, pList->sName.zString, pList->sName.nByte);
SyHashEntry *pEntry;
@@ -2461,13 +2478,14 @@ static sxi32 VmByteCodeExec(
if(pLastRef) {
*pLastRef = pTos->nIdx;
}
/* Ensure we are in active frame. Force abort all loops */
/* Unwind local loops, but let VmExecExceptionBlock() release
* the handler without freeing its borrowed owner frames. */
if(pInstr->p3) { /* Called from a return statement */
while(pVm->pFrame->pParent && (pVm->pFrame->iFlags & VM_FRAME_ACTIVE) == 0) {
while(pVm->pFrame->pParent && (pVm->pFrame->iFlags & (VM_FRAME_ACTIVE | VM_FRAME_CATCH | VM_FRAME_FINALLY)) == 0) {
VmLeaveFrame(&(*pVm));
}
} else if(pVm->pFrame->iFlags & VM_FRAME_LOOP) {
while(pVm->pFrame->pParent && (pVm->pFrame->iFlags & VM_FRAME_ACTIVE) == 0) {
while(pVm->pFrame->pParent && (pVm->pFrame->iFlags & (VM_FRAME_ACTIVE | VM_FRAME_CATCH | VM_FRAME_FINALLY)) == 0) {
VmLeaveFrame(&(*pVm));
}
}
@@ -4696,8 +4714,8 @@ static sxi32 VmByteCodeExec(
/* Point to the instantiated class */
pClass = pThis->pClass;
pScope = VmMethodClass(pVm);
if(pNos->iFlags == MEMOBJ_PARENTOBJ) {
/* Parent lookup starts at the declaring class, not the receiver. */
if(pNos->iFlags == MEMOBJ_PARENTOBJ && !pInstr->iP2) {
/* Preserve lexical parent lookup for fields. */
if(pScope) {
pClass = pScope;
}
@@ -4987,7 +5005,7 @@ static sxi32 VmByteCodeExec(
SySetPut(&aArg, (const void *)&pArg);
pArg++;
}
PH7_VmCallClassMethod(&(*pVm), pNew, pCons, 0, (int)SySetUsed(&aArg), (ph7_value **)SySetBasePtr(&aArg));
VmCallClassMethod(&(*pVm), pNew, pCons, 0, (int)SySetUsed(&aArg), (ph7_value **)SySetBasePtr(&aArg), FALSE);
/* TICKET 1433-52: Unsetting $this in the constructor body */
if(pNew->iRef < 1) {
pNew->iRef = 1;
@@ -5156,8 +5174,7 @@ static sxi32 VmByteCodeExec(
/* Instance already loaded */
pThis = (ph7_class_instance *)pTarget->x.pOther;
pLookup = pThis->pClass;
if(pTarget->iFlags == MEMOBJ_PARENTOBJ && VmMethodClass(pVm)) {
pLookup = VmMethodClass(pVm);
if(pTarget->iFlags == MEMOBJ_PARENTOBJ) {
bParent = 1;
}
pThis->iRef += 2;
@@ -6775,13 +6792,14 @@ static int vm_builtin_is_subclass_of(ph7_context *pCtx, int nArg, ph7_value **ap
* Return SXRET_OK if the method was successfully called.Any other
* return value indicates failure.
*/
PH7_PRIVATE sxi32 PH7_VmCallClassMethod(
static sxi32 VmCallClassMethod(
ph7_vm *pVm, /* Target VM */
ph7_class_instance *pThis, /* Target class instance [i.e: Object in the PHP jargon]*/
ph7_class_method *pMethod, /* Method name */
ph7_value *pResult, /* Store method return value here. NULL otherwise */
int nArg, /* Total number of given arguments */
ph7_value **apArg /* Method arguments */
ph7_value **apArg, /* Method arguments */
sxi32 bInternal /* VM-owned lifecycle call, not source new */
) {
ph7_value *aStack;
VmInstr aInstr[2];
@@ -6804,7 +6822,7 @@ PH7_PRIVATE sxi32 PH7_VmCallClassMethod(
aStack[i].nIdx = apArg[i]->nIdx;
}
iCursor = nArg + 1;
iEntry = 2; /* VM-owned method call: preserve access to lifecycle methods. */
iEntry = bInternal ? 2 : 0;
if(pThis) {
/*
* Push the class instance so that the '$this' variable will be available.
@@ -6812,7 +6830,7 @@ PH7_PRIVATE sxi32 PH7_VmCallClassMethod(
pThis->iRef++; /* Increment reference count */
aStack[i].x.pOther = pThis;
aStack[i].nType = MEMOBJ_OBJ;
if(SyStrncmp(pThis->pClass->sName.zString, "Program", 7) == 0) {
if(bInternal && SyStrncmp(pThis->pClass->sName.zString, "Program", 7) == 0) {
if((SyStrncmp(pMethod->sFunc.sName.zString, "main", 4) == 0) || (SyStrncmp(pMethod->sFunc.sName.zString, "__construct", 11) == 0)) {
/* Do not overload entry point */
iEntry |= 1;
@@ -6860,6 +6878,10 @@ PH7_PRIVATE sxi32 PH7_VmCallClassMethod(
SyMemBackendFree(&pVm->sAllocator, aStack);
return PH7_OK;
}
PH7_PRIVATE sxi32 PH7_VmCallClassMethod(ph7_vm *pVm, ph7_class_instance *pThis, ph7_class_method *pMethod,
ph7_value *pResult, int nArg, ph7_value **apArg) {
return VmCallClassMethod(pVm, pThis, pMethod, pResult, nArg, apArg, TRUE);
}
/*
* Call a user defined or foreign function where the name of the function
* is stored in the pFunc parameter and the given arguments are stored
@@ -8400,27 +8422,61 @@ static int vm_builtin_debug_backtrace(ph7_context *pCtx, int nArg, ph7_value **a
*/
return PH7_OK;
}
/* Handlers run synchronously before the throwing calls unwind. Borrow their
* owner's context without releasing the suspended calls or overwriting their
* self stack slots when a handler invokes another method. */
static sxi32 VmExecExceptionBlock(ph7_vm *pVm, ph7_exception *pException,
ph7_exception_block *pCatch, ph7_class_instance *pThis) {
VmFrame *pSavedFrame = pVm->pFrame;
VmFrame *pOwner = pException->pFrame;
VmFrame *pFrame;
SySet sSelf, sSavedSelf;
sxu32 nSelf = SySetUsed(&pVm->aSelf), i;
sxi32 rc;
for(pFrame = pSavedFrame; pFrame != pOwner; pFrame = pFrame->pParent) {
ph7_vm_func *pFunc = (ph7_vm_func *)pFrame->pUserData;
if((pFrame->iFlags & VM_FRAME_ACTIVE) && pFunc && (pFunc->iFlags & VM_FUNC_CLASS_METHOD)) {
nSelf--;
}
}
SySetInit(&sSelf, &pVm->sAllocator, sizeof(ph7_class *));
for(i = 0; i < nSelf; i++) {
if(SySetPut(&sSelf, SySetAt(&pVm->aSelf, i)) != SXRET_OK) {
SySetRelease(&sSelf);
return SXERR_MEM;
}
}
sSavedSelf = pVm->aSelf;
pVm->aSelf = sSelf;
pVm->pFrame = pOwner;
rc = VmEnterFrame(pVm, 0, 0, &pFrame);
if(rc == SXRET_OK) {
if(pCatch) {
ph7_value *pObj = VmCreateMemObj(pVm, &pCatch->sThis, FALSE);
pFrame->iFlags |= VM_FRAME_CATCH;
if(pObj) {
pThis->iRef++;
pObj->x.pOther = pThis;
MemObjSetType(pObj, MEMOBJ_OBJ);
}
} else {
pFrame->iFlags |= VM_FRAME_FINALLY;
}
rc = VmLocalExec(pVm, pCatch ? &pCatch->sByteCode : &pException->sFinally, 0);
while(pVm->pFrame != pOwner) {
VmLeaveFrame(pVm);
}
}
pVm->pFrame = pSavedFrame;
SySetRelease(&pVm->aSelf);
pVm->aSelf = sSavedSelf;
return rc;
}
static sxi32 VmExecFinallyBlock(
ph7_vm *pVm, /* Target VM */
ph7_exception *pException /* Exception thrown */
) {
sxi32 rc;
VmFrame *pFrame = pVm->pFrame;
while(pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION)) {
/* Safely ignore the exception frame */
pFrame = pFrame->pParent;
}
/* Create a private frame first */
rc = VmEnterFrame(&(*pVm), 0, 0, &pFrame);
if(rc == SXRET_OK) {
/* Mark as 'finally' frame */
pFrame->iFlags |= VM_FRAME_FINALLY;
/* Execute the block */
rc = VmLocalExec(&(*pVm), &pException->sFinally, 0);
/* Leave the frame */
VmLeaveFrame(&(*pVm));
}
return rc;
return VmExecExceptionBlock(pVm, pException, 0, 0);
}
/*
* The following routine is invoked by the engine when an uncaught
@@ -8491,7 +8547,7 @@ static sxi32 VmThrowException(
ph7_exception_block *pCatch; /* Catch block to execute */
ph7_exception **apException;
ph7_exception *pException;
sxi32 rc, rcc, rcf;
sxi32 rcc, rcf;
/* Point to the stack of loaded exceptions */
apException = (ph7_exception **)SySetBasePtr(&pVm->aException);
pException = 0;
@@ -8532,23 +8588,7 @@ static sxi32 VmThrowException(
/* Tell the upper layer that the exception was caught */
pFrame->iFlags &= ~VM_FRAME_THROW;
}
/* Create a private frame first */
rc = VmEnterFrame(&(*pVm), 0, 0, &pFrame);
if(rc == SXRET_OK) {
/* Mark as catch frame */
ph7_value *pObj = VmCreateMemObj(&(*pVm), &pCatch->sThis, FALSE);
pFrame->iFlags |= VM_FRAME_CATCH;
if(pObj) {
/* Install the exception instance */
pThis->iRef++; /* Increment reference count */
pObj->x.pOther = pThis;
MemObjSetType(pObj, MEMOBJ_OBJ);
}
/* Execute the block */
rcc = VmLocalExec(&(*pVm), &pCatch->sByteCode, 0);
/* Leave the frame */
VmLeaveFrame(&(*pVm));
}
rcc = VmExecExceptionBlock(pVm, pException, pCatch, pThis);
}
/* Execute the 'finally' block if available */
if(pException && SySetUsed(&pException->sFinally)) {