Fix block scope for local declarations
Build / AerScript (push) Successful in 40s

This commit is contained in:
2026-10-25 19:22:50 +01:00
parent 3e3b795626
commit 8a2d7d8bd3
4 changed files with 277 additions and 52 deletions
+224 -48
View File
@@ -54,6 +54,7 @@ struct SyhttpHeader {
#define HTTP_PROTO_10 1 /* HTTP/1.0 */
#define HTTP_PROTO_11 2 /* HTTP/1.1 */
#define HTTP_PROTO_20 3 /* HTTP/2.0 */
static void VmScopeRecord(ph7_vm *pVm, const SyString *pName, sxi32 bConst);
/*
* Register a constant and it's associated expansion callback so that
* it can be expanded from the target PHP program.
@@ -82,7 +83,11 @@ PH7_PRIVATE sxi32 PH7_VmRegisterConstant(
if(bGlobal) {
pCollection = &pVm->hConstant;
} else {
pCollection = &pVm->pFrame->hConst;
VmFrame *pFrame = pVm->pFrame;
while(pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION)) {
pFrame = pFrame->pParent;
}
pCollection = &pFrame->hConst;
}
pEntry = SyHashGet(pCollection, (const void *)pName->zString, pName->nByte);
if(pEntry) {
@@ -107,6 +112,9 @@ PH7_PRIVATE sxi32 PH7_VmRegisterConstant(
if(rc != SXRET_OK) {
PH7_VmMemoryError(&(*pVm));
}
if(!bGlobal) {
VmScopeRecord(pVm, &pCons->sName, TRUE);
}
/* All done,constant can be invoked from PHP code */
return SXRET_OK;
}
@@ -310,6 +318,7 @@ PH7_PRIVATE sxi32 PH7_VmEmitInstr(
}
/* Fill the VM instruction */
sInstr.iOp = (sxu8)iOp;
sInstr.pScope = 0;
sInstr.iP1 = iP1;
sInstr.iP2 = iP2;
sInstr.p3 = p3;
@@ -386,6 +395,103 @@ PH7_PRIVATE VmInstr *PH7_VmPeekNextInstr(ph7_vm *pVm) {
aInstr = (VmInstr *)SySetBasePtr(pVm->pByteContainer);
return &aInstr[n - 2];
}
/* Names belong to lexical scopes, storage and references to their existing
* owner frame. The execution tag keeps borrowed include/handler frames from
* turning nested bytecode execution into a new variable-visibility boundary. */
typedef struct VmScopeBinding VmScopeBinding;
struct VmScopeBinding {
SyString sName;
sxi32 bConst;
};
struct VmScope {
VmScope *pParent;
VmScopeInfo *pInfo;
VmFrame *pFrame;
void *pExec; /* Execution boundary: includes and handlers borrow their owner */
SySet aBinding;
};
static void VmScopeRecord(ph7_vm *pVm, const SyString *pName, sxi32 bConst) {
VmScope *pScope;
VmFrame *pFrame = pVm->pFrame;
VmScopeBinding sBinding;
while(pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION)) {
pFrame = pFrame->pParent;
}
for(pScope = pVm->pScope; pScope; pScope = pScope->pParent) {
if(pScope->pFrame == pFrame) {
sBinding.sName = *pName;
sBinding.bConst = bConst;
if(SySetPut(&pScope->aBinding, &sBinding) != SXRET_OK) {
PH7_VmMemoryError(pVm);
}
break;
}
}
}
static void VmScopeLeave(ph7_vm *pVm) {
VmScope *pScope = pVm->pScope;
VmScopeBinding *aBinding = (VmScopeBinding *)SySetBasePtr(&pScope->aBinding);
sxu32 n;
for(n = 0; n < SySetUsed(&pScope->aBinding); n++) {
VmScopeBinding *pBinding = &aBinding[n];
SyHash *pHash = pBinding->bConst ? &pScope->pFrame->hConst : &pScope->pFrame->hVar;
SyHashEntry *pEntry = SyHashGet(pHash, pBinding->sName.zString, pBinding->sName.nByte);
if(pEntry) {
if(!pBinding->bConst) {
sxu32 nIdx = (sxu32)SX_PTR_TO_INT(pEntry->pUserData);
VmSlot *aRef = (VmSlot *)SySetBasePtr(&pScope->pFrame->sRef);
sxu32 i;
PH7_VmRefObjRemove(pVm, nIdx, pEntry, 0);
for(i = 0; i < SySetUsed(&pScope->pFrame->sRef); i++) {
if(aRef[i].pUserData == pEntry) {
aRef[i].pUserData = 0;
}
}
}
SyHashDeleteEntry2(pEntry);
}
}
/* Values stay in sLocal until frame cleanup. Escaping references and closure
* copies must not be recycled just because their names became invisible. */
pVm->pScope = pScope->pParent;
SySetRelease(&pScope->aBinding);
SyMemBackendPoolFree(&pVm->sAllocator, pScope);
}
static sxi32 VmScopeContains(VmScopeInfo *pScope, VmScopeInfo *pTarget) {
while(pTarget) {
if(pTarget == pScope) {
return TRUE;
}
pTarget = pTarget->pParent;
}
return FALSE;
}
static void VmScopeEnter(ph7_vm *pVm, VmScopeInfo *pInfo, void *pExec) {
VmScope *pScope;
if(pInfo == 0 || (pVm->pScope && pVm->pScope->pExec == pExec && pVm->pScope->pInfo == pInfo)) {
return;
}
VmScopeEnter(pVm, pInfo->pParent, pExec);
pScope = (VmScope *)SyMemBackendPoolAlloc(&pVm->sAllocator, sizeof(VmScope));
if(pScope == 0) {
PH7_VmMemoryError(pVm);
}
pScope->pParent = pVm->pScope;
pScope->pInfo = pInfo;
pScope->pFrame = pVm->pFrame;
while(pScope->pFrame->pParent && (pScope->pFrame->iFlags & VM_FRAME_EXCEPTION)) {
pScope->pFrame = pScope->pFrame->pParent;
}
pScope->pExec = pExec;
SySetInit(&pScope->aBinding, &pVm->sAllocator, sizeof(VmScopeBinding));
pVm->pScope = pScope;
}
static void VmScopeSync(ph7_vm *pVm, VmScopeInfo *pTarget, void *pExec) {
while(pVm->pScope && pVm->pScope->pExec == pExec && !VmScopeContains(pVm->pScope->pInfo, pTarget)) {
VmScopeLeave(pVm);
}
VmScopeEnter(pVm, pTarget, pExec);
}
/*
* Allocate a new virtual machine frame.
*/
@@ -411,6 +517,7 @@ static VmFrame *VmNewFrame(
SySetInit(&pFrame->sArg, &pVm->sAllocator, sizeof(VmSlot));
SySetInit(&pFrame->sLocal, &pVm->sAllocator, sizeof(VmSlot));
SySetInit(&pFrame->sRef, &pVm->sAllocator, sizeof(VmSlot));
SySetInit(&pFrame->sConstDecl, &pVm->sAllocator, sizeof(void *));
return pFrame;
}
/*
@@ -443,6 +550,9 @@ static sxi32 VmEnterFrame(
static void VmLeaveFrame(ph7_vm *pVm) {
VmFrame *pFrame = pVm->pFrame;
if(pFrame) {
while(pVm->pScope && pVm->pScope->pFrame == pFrame) {
VmScopeLeave(pVm);
}
/* Unlink from the list of active VM frame */
pVm->pFrame = pFrame->pParent;
if(pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) == 0) {
@@ -466,6 +576,7 @@ static void VmLeaveFrame(ph7_vm *pVm) {
SySetRelease(&pFrame->sArg);
SySetRelease(&pFrame->sLocal);
SySetRelease(&pFrame->sRef);
SySetRelease(&pFrame->sConstDecl);
/* Release the whole structure */
SyMemBackendPoolFree(&pVm->sAllocator, pFrame);
}
@@ -1888,8 +1999,12 @@ static ph7_value *VmCreateMemObj(
sxu32 nIdx;
sxi32 rc;
SyHashEntry *pEntry;
VmFrame *pFrame = pVm->pFrame;
while(pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION)) {
pFrame = pFrame->pParent;
}
/* Query the top active frame */
pEntry = SyHashGet(&pVm->pFrame->hVar, (const void *)pName->zString, pName->nByte);
pEntry = SyHashGet(&pFrame->hVar, (const void *)pName->zString, pName->nByte);
if(pEntry) {
/* Variable already exists */
return 0;
@@ -1911,15 +2026,16 @@ static ph7_value *VmCreateMemObj(
}
}
/* Link to the top active VM frame */
rc = SyHashInsert(&pVm->pFrame->hVar, zName, pName->nByte, SX_INT_TO_PTR(nIdx));
rc = SyHashInsert(&pFrame->hVar, zName, pName->nByte, SX_INT_TO_PTR(nIdx));
if(rc != SXRET_OK) {
PH7_VmMemoryError(&(*pVm));
}
/* Register local variable */
sLocal.nIdx = nIdx;
SySetPut(&pVm->pFrame->sLocal, (const void *)&sLocal);
SySetPut(&pFrame->sLocal, (const void *)&sLocal);
/* Install in the reference table */
PH7_VmRefObjInstall(&(*pVm), nIdx, SyHashLastEntry(&pVm->pFrame->hVar), 0, 0);
PH7_VmRefObjInstall(&(*pVm), nIdx, SyHashLastEntry(&pFrame->hVar), 0, 0);
VmScopeRecord(pVm, pName, FALSE);
/* Save object index */
pObj->nIdx = nIdx;
return pObj;
@@ -2462,6 +2578,7 @@ static sxi32 VmByteCodeExec(
sxi32 pc;
sxi32 rc;
sxi32 bReturning = FALSE;
sxbool bScopeStopped = FALSE;
VmFrame *pEntryFrame = pVm->pFrame;
/* Argument container */
SySetInit(&aArg, &pVm->sAllocator, sizeof(ph7_value *));
@@ -2480,6 +2597,13 @@ Resume:
}
/* Fetch the instruction to execute */
pInstr = &aInstr[pc];
/* LF_STOP's following transfer must not recreate the released iteration
* scope. Other jumps also reconcile their source, including jump chains
* that leave and immediately reenter a block without executing its end. */
if(pInstr->iOp != PH7_OP_LF_STOP && !(bScopeStopped && pInstr->iOp == PH7_OP_JMP)) {
VmScopeSync(pVm, pInstr->pScope, &aArg);
}
bScopeStopped = FALSE;
pInstr->bExec = TRUE;
/* Record executed instruction in global container */
SySetPut(&pVm->aInstrSet, (void *)pInstr);
@@ -2518,7 +2642,8 @@ Resume:
VmLeaveFrame(&(*pVm));
}
} else if(!pInstr->p3 && (iExecFlags & VM_EXEC_INCLUDE) == 0 && (pVm->pFrame->iFlags & VM_FRAME_LOOP)) {
while(pVm->pFrame->pParent && (pVm->pFrame->iFlags & (VM_FRAME_ACTIVE | VM_FRAME_CATCH | VM_FRAME_FINALLY)) == 0) {
/* An initializer can borrow an existing iteration frame. */
while(pVm->pFrame != pEntryFrame && pVm->pFrame->pParent && (pVm->pFrame->iFlags & (VM_FRAME_ACTIVE | VM_FRAME_CATCH | VM_FRAME_FINALLY)) == 0) {
VmLeaveFrame(&(*pVm));
}
}
@@ -2607,6 +2732,7 @@ Resume:
pResult->x.iVal = pInstr->iP2;
MemObjSetType(pResult, MEMOBJ_INT);
}
VmScopeSync(pVm, 0, &aArg);
SySetRelease(&aArg);
return VM_INCLUDE_JUMP;
}
@@ -2688,6 +2814,7 @@ Resume:
/* Leave the jump loop frame */
if(pVm->pFrame->iFlags & VM_FRAME_LOOP) {
VmLeaveFrame(&(*pVm));
bScopeStopped = TRUE;
}
break;
}
@@ -2889,13 +3016,75 @@ Resume:
/*
* DECLARE: P1 P2 P3
*
* Create a constant if P1 is set, or variable otherwise. It takes the constant/variable name
* from the the P3 operand. P2 operand is used to provide a variable type.
* P1=0 declares a variable named by P3, with type P2; P1=1 declares
* a constant described by P3. P1=2 binds the static declaration P2
* in the function pointed to by P3.
*/
case PH7_OP_DECLARE: {
if(pInstr->iP1) {
if(pInstr->iP1 == 2) {
ph7_vm_func *pFunc = (ph7_vm_func *)pInstr->p3;
ph7_vm_func_static_var *pStatic = (ph7_vm_func_static_var *)SySetAt(&pFunc->aStatic, pInstr->iP2);
VmFrame *pFrame = pVm->pFrame;
while(pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION)) {
pFrame = pFrame->pParent;
}
if(SyHashGet(&pFrame->hVar, pStatic->sName.zString, pStatic->sName.nByte)) {
PH7_VmThrowError(pVm, PH7_CTX_ERR, "Redeclaration of ‘$%z’ variable", &pStatic->sName);
}
if(pStatic->nIdx == SXU32_HIGH) {
ph7_value *pObj = VmReserveMemObj(pVm, &pStatic->nIdx);
ph7_value sValue;
if(pObj == 0) {
PH7_VmMemoryError(pVm);
}
MemObjSetType(pObj, pStatic->iFlags);
if(SySetUsed(&pStatic->aByteCode) > 0) {
PH7_MemObjInit(pVm, &sValue);
rc = VmLocalExec(pVm, &pStatic->aByteCode, &sValue);
pObj = (ph7_value *)SySetAt(&pVm->aMemObj, pStatic->nIdx);
if(rc == SXRET_OK) {
rc = PH7_MemObjSafeStore(&sValue, pObj);
if(rc != SXRET_OK) {
PH7_VmThrowError(pVm, PH7_CTX_ERR, "Cannot assign a value of incompatible type to variable '$%z'", &pStatic->sName);
}
}
PH7_MemObjRelease(&sValue);
if(rc == PH7_ABORT) {
goto Abort;
} else if(rc == PH7_EXCEPTION) {
goto Exception;
}
} else if(pObj->nType & MEMOBJ_HASHMAP) {
pObj->x.pOther = PH7_NewHashmap(pVm, 0, 0);
if(pObj->x.pOther == 0) {
PH7_VmMemoryError(pVm);
}
}
pObj->nIdx = pStatic->nIdx;
}
if(SyHashInsert(&pFrame->hVar, pStatic->sName.zString, pStatic->sName.nByte,
SX_INT_TO_PTR(pStatic->nIdx)) != SXRET_OK) {
PH7_VmMemoryError(pVm);
}
VmScopeRecord(pVm, &pStatic->sName, FALSE);
} else if(pInstr->iP1) {
/* Constant declaration */
ph7_constant_info *pConstInfo = (ph7_constant_info *) pInstr->p3;
VmFrame *pOwner = pVm->pFrame;
void **apDecl;
sxu32 n;
while(pOwner->pParent && (pOwner->iFlags & VM_FRAME_ACTIVE) == 0) {
pOwner = pOwner->pParent;
}
apDecl = (void **)SySetBasePtr(&pOwner->sConstDecl);
for(n = 0; n < SySetUsed(&pOwner->sConstDecl); n++) {
if(apDecl[n] == pConstInfo) {
PH7_VmThrowError(pVm, PH7_CTX_ERR, "Redeclaration of ‘%z’ constant", &pConstInfo->pName);
}
}
if(SySetPut(&pOwner->sConstDecl, &pConstInfo) != SXRET_OK) {
PH7_VmMemoryError(pVm);
}
rc = PH7_VmRegisterConstant(&(*pVm), &pConstInfo->pName, PH7_VmExpandConstantValue, pConstInfo->pConsCode, FALSE);
if(rc == SXERR_EXISTS) {
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR,
@@ -4425,6 +4614,8 @@ Resume:
ph7_exception *pException = (ph7_exception *)pInstr->p3;
VmFrame *pFrame = 0;
SySetPut(&pVm->aException, (const void *)&pException);
pException->pScope = pVm->pScope ? pVm->pScope->pInfo : 0;
pException->pScopeExec = pVm->pScope ? pVm->pScope->pExec : 0;
/* Create the exception frame */
rc = VmEnterFrame(&(*pVm), 0, 0, &pFrame);
if(rc != SXRET_OK) {
@@ -5220,9 +5411,9 @@ Resume:
ph7_value *pArg = &pTos[-pInstr->iP1];
SyHashEntry *pEntry;
SyString sName;
VmInstr *bInstr = &aInstr[pc - 1];
/* Extract function name */
if(pTos->nType & MEMOBJ_STRING && bInstr->iOp == PH7_OP_LOADV) {
/* Synthetic calls have no preceding instruction at pc == 0. */
if(pTos->nType & MEMOBJ_STRING && pc > 0 && aInstr[pc - 1].iOp == PH7_OP_LOADV) {
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Calling a non-callable object");
} else if((pTos->nType & (MEMOBJ_CALL | MEMOBJ_STRING)) == 0) {
if(pTos->nType & MEMOBJ_HASHMAP) {
@@ -5357,43 +5548,6 @@ Resume:
MemObjSetType(pObj, MEMOBJ_OBJ);
}
}
if(SySetUsed(&pVmFunc->aStatic) > 0) {
ph7_vm_func_static_var *pStatic, *aStatic;
/* Install static variables */
aStatic = (ph7_vm_func_static_var *)SySetBasePtr(&pVmFunc->aStatic);
for(n = 0 ; n < SySetUsed(&pVmFunc->aStatic) ; ++n) {
pStatic = &aStatic[n];
if(pStatic->nIdx == SXU32_HIGH) {
ph7_value *pVal;
/* Initialize the static variables */
pObj = VmReserveMemObj(&(*pVm), &pStatic->nIdx);
pVal = PH7_ReserveMemObj(&(*pVm));
if(pObj == 0 || pVal == 0) {
PH7_VmMemoryError(&(*pVm));
}
MemObjSetType(pObj, pStatic->iFlags);
if(SySetUsed(&pStatic->aByteCode) > 0) {
/* Evaluate initialization expression (Any complex expression) */
VmLocalExec(&(*pVm), &pStatic->aByteCode, pVal);
rc = PH7_MemObjSafeStore(pVal, pObj);
if(rc != SXRET_OK) {
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Cannot assign a value of incompatible type to variable '$%z'", &pStatic->sName);
}
} else if(pObj->nType & MEMOBJ_HASHMAP) {
ph7_hashmap *pMap;
pMap = PH7_NewHashmap(&(*pVm), 0, 0);
if(pMap == 0) {
PH7_VmMemoryError(&(*pVm));
}
pObj->x.pOther = pMap;
}
pObj->nIdx = pStatic->nIdx;
}
/* Install in the current frame */
SyHashInsert(&pFrame->hVar, SyStringData(&pStatic->sName), SyStringLength(&pStatic->sName),
SX_INT_TO_PTR(pStatic->nIdx));
}
}
/* Push arguments in the local frame */
n = 0;
while(pArg < pTos) {
@@ -5715,6 +5869,7 @@ IncludeException:
VmFrame *pFrame = pVm->pFrame;
while(pFrame->pParent) {
if((iExecFlags & VM_EXEC_INCLUDE) && pFrame == pEntryFrame) {
VmScopeSync(pVm, 0, &aArg);
SySetRelease(&aArg);
while(pTos >= pStack) {
PH7_MemObjRelease(pTos);
@@ -5750,9 +5905,11 @@ IncludeReturn:
}
}
Done:
VmScopeSync(pVm, 0, &aArg);
SySetRelease(&aArg);
return bReturning && (iExecFlags & VM_EXEC_INCLUDE) ? VM_INCLUDE_RETURN : SXRET_OK;
Abort:
VmScopeSync(pVm, 0, &aArg);
SySetRelease(&aArg);
while(pTos >= pStack) {
PH7_MemObjRelease(pTos);
@@ -5760,6 +5917,7 @@ Abort:
}
return PH7_ABORT;
Exception:
VmScopeSync(pVm, 0, &aArg);
SySetRelease(&aArg);
while(pTos >= pStack) {
PH7_MemObjRelease(pTos);
@@ -7007,6 +7165,7 @@ static sxi32 VmCallClassMethod(
static const SyString sFileName = { "[MEMORY]", sizeof("[MEMORY]") - 1};
/* Emit the CALL instruction */
aInstr[0].iOp = PH7_OP_CALL;
aInstr[0].pScope = 0;
aInstr[0].iP1 = nArg; /* Total number of given arguments */
aInstr[0].iP2 = iEntry;
aInstr[0].p3 = 0;
@@ -7015,6 +7174,7 @@ static sxi32 VmCallClassMethod(
aInstr[0].pFile = (SyString *)&sFileName;
/* Emit the DONE instruction */
aInstr[1].iOp = PH7_OP_DONE;
aInstr[1].pScope = 0;
aInstr[1].iP1 = 1; /* Extract method return value */
aInstr[1].iP2 = 1;
aInstr[1].p3 = 0;
@@ -7077,6 +7237,7 @@ PH7_PRIVATE sxi32 PH7_VmCallUserFunction(
static const SyString sFileName = { "[MEMORY]", sizeof("[MEMORY]") - 1};
/* Emit the CALL instruction */
aInstr[0].iOp = PH7_OP_CALL;
aInstr[0].pScope = 0;
aInstr[0].iP1 = nArg; /* Total number of given arguments */
aInstr[0].iP2 = 0;
aInstr[0].p3 = 0;
@@ -7085,6 +7246,7 @@ PH7_PRIVATE sxi32 PH7_VmCallUserFunction(
aInstr[0].pFile = (SyString *)&sFileName;
/* Emit the DONE instruction */
aInstr[1].iOp = PH7_OP_DONE;
aInstr[1].pScope = 0;
aInstr[1].iP1 = 1; /* Extract function return value if available */
aInstr[1].iP2 = 1;
aInstr[1].p3 = 0;
@@ -8581,6 +8743,7 @@ static sxi32 VmExecExceptionBlock(ph7_vm *pVm, ph7_exception *pException,
VmFrame *pSavedFrame = pVm->pFrame;
VmFrame *pOwner = pException->pFrame;
VmFrame *pFrame;
VmScope *pScope;
SySet sSelf, sSavedSelf;
sxu32 nSelf = SySetUsed(&pVm->aSelf), i;
sxi32 rc;
@@ -8599,6 +8762,19 @@ static sxi32 VmExecExceptionBlock(ph7_vm *pVm, ph7_exception *pException,
}
sSavedSelf = pVm->aSelf;
pVm->aSelf = sSelf;
/* Handlers run before the throwing bytecode resumes its unwind. Remove
* exited names now, without releasing owner values or enclosing scopes.
* A context already unwound by an earlier exit must not be recreated. */
for(pScope = pVm->pScope; pScope; pScope = pScope->pParent) {
if(pScope->pInfo == pException->pScope && pScope->pExec == pException->pScopeExec) {
break;
}
}
if(pScope || pException->pScope == 0) {
while(pVm->pScope != pScope) {
VmScopeLeave(pVm);
}
}
pVm->pFrame = pOwner;
rc = VmEnterFrame(pVm, 0, 0, &pFrame);
if(rc == SXRET_OK) {