From 8a2d7d8bd3940df36248145e48d427cd79b929f8 Mon Sep 17 00:00:00 2001 From: Rafal Kupiec Date: Sun, 25 Oct 2026 19:22:50 +0100 Subject: [PATCH] Fix block scope for local declarations --- engine/compiler.c | 41 ++++++- engine/vm.c | 272 ++++++++++++++++++++++++++++++++++-------- include/ph7int.h | 10 ++ tests/base32_test.aer | 6 +- 4 files changed, 277 insertions(+), 52 deletions(-) diff --git a/engine/compiler.c b/engine/compiler.c index c6fe5eb..e20b458 100644 --- a/engine/compiler.c +++ b/engine/compiler.c @@ -1595,6 +1595,35 @@ static sxi32 PH7_GenStateNextChunk(ph7_gen_state *pGen) { pGen->pRawIn++; return SXRET_OK; } +/* Attach lexical ancestry after compilation, so nested bytecode containers + * (closures, initializers and handlers) never inherit the caller's scopes. */ +static void GenStateCloseScope(ph7_gen_state *pGen, sxu32 nFirst) { + VmScopeInfo *pScope; + VmInstr *pInstr; + sxu32 n, nEnd = PH7_VmInstrLength(pGen->pVm); + pScope = (VmScopeInfo *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(VmScopeInfo)); + if(pScope == 0) { + PH7_VmMemoryError(pGen->pVm); + } + pScope->pParent = 0; + for(n = nFirst; n < nEnd; n++) { + VmScopeInfo *pChild; + pInstr = PH7_VmGetInstr(pGen->pVm, n); + if(pInstr->pScope == 0) { + pInstr->pScope = pScope; + } else { + pChild = pInstr->pScope; + while(pChild->pParent && pChild->pParent != pScope) { + pChild = pChild->pParent; + } + if(pChild != pScope) { + pChild->pParent = pScope; + } + } + } + /* A real target outside the scope, including for empty blocks. */ + PH7_VmEmitInstr(pGen->pVm, 0, PH7_OP_NOOP, 0, 0, 0, 0); +} /* * Compile an AerScript block. * A block is simply one or more Aer statements and expressions to compile @@ -1609,6 +1638,8 @@ static sxi32 PH7_CompileBlock( sxi32 rc; if(pGen->pIn->nType & PH7_TK_OCB /* '{' */) { sxu32 nLine = pGen->pIn->nLine; + sxu32 nScope = PH7_VmInstrLength(pGen->pVm); + PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_NOOP, 0, 0, 0, 0); rc = PH7_GenStateEnterBlock(&(*pGen), GEN_BLOCK_STD, PH7_VmInstrLength(pGen->pVm), 0, 0); if(rc != SXRET_OK) { return SXERR_ABORT; @@ -1637,6 +1668,7 @@ static sxi32 PH7_CompileBlock( return SXERR_ABORT; } } + GenStateCloseScope(pGen, nScope); PH7_GenStateLeaveBlock(&(*pGen), 0); } else { /* Compile a single statement */ @@ -1867,6 +1899,7 @@ static sxi32 PH7_CompileDoWhile(ph7_gen_state *pGen) { * of using the for truth expression. */ static sxi32 PH7_CompileFor(ph7_gen_state *pGen) { + sxu32 nScope = PH7_VmInstrLength(pGen->pVm); SyToken *pTmp, *pPostStart, *pEnd = 0; GenBlock *pForBlock = 0; sxu32 nFalseJump; @@ -1993,6 +2026,7 @@ static sxi32 PH7_CompileFor(ph7_gen_state *pGen) { PH7_GenStateFixJumps(pForBlock, -1, PH7_VmInstrLength(pGen->pVm)); /* Release the loop block */ PH7_GenStateLeaveBlock(pGen, 0); + GenStateCloseScope(pGen, nScope); /* Statement successfully compiled */ return SXRET_OK; } @@ -2028,6 +2062,7 @@ static sxi32 GenStateForEachNodeValidator(ph7_gen_state *pGen, ph7_expr_node *pR * first element of the array. This means that you do not need to call reset() before a foreach loop. */ static sxi32 PH7_CompileForeach(ph7_gen_state *pGen) { + sxu32 nScope = PH7_VmInstrLength(pGen->pVm); SyToken *pCur, *pTmp, *pEnd = 0; GenBlock *pForeachBlock = 0; ph7_foreach_info *pInfo; @@ -2193,6 +2228,7 @@ static sxi32 PH7_CompileForeach(ph7_gen_state *pGen) { PH7_GenStateFixJumps(pForeachBlock, -1, PH7_VmInstrLength(pGen->pVm)); /* Release the loop block */ PH7_GenStateLeaveBlock(pGen, 0); + GenStateCloseScope(pGen, nScope); /* Statement successfully compiled */ return SXRET_OK; } @@ -2450,8 +2486,6 @@ static sxi32 PH7_CompileVar(ph7_gen_state *pGen) { if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & (PH7_TK_COMMA/*','*/ | PH7_TK_SEMI/*';'*/ | PH7_TK_EQUAL/*'='*/)) == 0) { PH7_GenCompileError(&(*pGen), E_ERROR, pGen->pIn->nLine, "Unexpected token '%z'", &pGen->pIn->sData); } - /* TODO: Check if static variable exists to avoid redeclaration */ - /* Initialize the structure describing the static variable */ SySetInit(&sStatic.aByteCode, &pGen->pVm->sAllocator, sizeof(VmInstr)); sStatic.nIdx = SXU32_HIGH; /* Not yet created */ @@ -2488,6 +2522,9 @@ static sxi32 PH7_CompileVar(ph7_gen_state *pGen) { sStatic.iFlags = nType; /* Finally save the compiled static variable in the appropriate container */ SySetPut(&pFunc->aStatic, (const void *)&sStatic); + /* Storage belongs to the declaration; visibility starts here. */ + PH7_VmEmitInstr(pGen->pVm, pGen->pIn->nLine, PH7_OP_DECLARE, 2, + SySetUsed(&pFunc->aStatic) - 1, pFunc, 0); } else { zDup = SyMemBackendStrDup(&pGen->pVm->sAllocator, pName->zString, pName->nByte); if(zDup == 0) { diff --git a/engine/vm.c b/engine/vm.c index 2a1e340..75b6b32 100644 --- a/engine/vm.c +++ b/engine/vm.c @@ -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) { diff --git a/include/ph7int.h b/include/ph7int.h index 52f0309..046f621 100644 --- a/include/ph7int.h +++ b/include/ph7int.h @@ -1121,7 +1121,13 @@ struct ph7_class_instance { * is stored in an instance of the following structure. */ typedef struct VmInstr VmInstr; +typedef struct VmScopeInfo VmScopeInfo; +typedef struct VmScope VmScope; +struct VmScopeInfo { + VmScopeInfo *pParent; /* Lexical parent in the same bytecode container */ +}; struct VmInstr { + VmScopeInfo *pScope; /* Lexical scope at this instruction */ sxu8 iOp; /* Operation to preform */ sxi32 iP1; /* First operand */ sxu32 iP2; /* Second operand (Often the jump destination) */ @@ -1161,6 +1167,8 @@ struct ph7_exception { */ SySet sFinally; /* Compiled 'finally' instructions block (VmInstr) */ VmFrame *pFrame; /* Frame that trigger the exception */ + VmScopeInfo *pScope; /* Enclosing lexical context at try entry */ + void *pScopeExec; /* Execution owning that context */ }; /* Forward reference */ typedef struct ph7_case_expr ph7_case_expr; @@ -1244,6 +1252,7 @@ struct ph7_vm { SySet aSelf; /* 'self' stack used for static member access [i.e: self::MyConstant] */ sxi32 iExitStatus; /* Script exit status */ ph7_gen_state sCodeGen; /* Code generator module */ + VmScope *pScope; /* Active lexical scopes (not call frames) */ ph7_vm *pNext, *pPrev; /* List of active VMs */ sxu32 nMagic; /* Sanity check against misuse */ }; @@ -1261,6 +1270,7 @@ struct VmFrame { SyHash hVar; /* Variable hashtable for fast lookup */ SySet sArg; /* Function arguments container */ SySet sRef; /* Local reference table (VmSlot instance) */ + SySet sConstDecl; /* Constants already declared during this invocation */ sxi32 iFlags; /* Frame configuration flags (See below) */ sxu32 iExceptionJump; /* Exception jump destination */ }; diff --git a/tests/base32_test.aer b/tests/base32_test.aer index a461348..e1f7237 100644 --- a/tests/base32_test.aer +++ b/tests/base32_test.aer @@ -18,7 +18,8 @@ class Base32 { if(!$input) return ''; char[] $aInput = str_split($input); string $binaryString = ''; - for(int $i = 0; $i < sizeof($aInput); $i++) { + int $i; + for($i = 0; $i < sizeof($aInput); $i++) { $binaryString += str_pad(base_convert(ord($aInput[$i]), 10, 2), 8, '0', STR_PAD_LEFT); } string[] $fiveBitBinaryArray = str_split($binaryString, 5); @@ -43,7 +44,8 @@ class Base32 { int $paddingCharCount = substr_count($input, Base32::$map[32]); int[] $allowedValues = {6, 4, 3, 1, 0}; if(!in_array($paddingCharCount, $allowedValues)) return ''; - for(int $i = 0; $i < 4; $i++) { + int $i; + for($i = 0; $i < 4; $i++) { if($paddingCharCount == $allowedValues[$i] && substr($input, -($allowedValues[$i])) != str_repeat(Base32::$map[32], $allowedValues[$i])) return ''; }