diff --git a/engine/compiler.c b/engine/compiler.c index 2530e02..2f704aa 100644 --- a/engine/compiler.c +++ b/engine/compiler.c @@ -1450,6 +1450,33 @@ static sxi32 PH7_CompileBreak(ph7_gen_state *pGen) { /* Statement successfully compiled */ return SXRET_OK; } +/* Out-of-stream handlers and includes share the ordinary loop cleanup and + * fixups through small bridges in their immediately enclosing bytecode. */ +static void GenStateCompileLoopBridge(ph7_gen_state *pGen, ph7_include *pBridge, + GenBlock *pLoop, sxu32 nLine) { + sxu32 nSkip; + SyToken sToken; + SyToken *pSavedIn = pGen->pIn, *pSavedEnd = pGen->pEnd; + pBridge->nBreak = pBridge->nContinue = SXU32_HIGH; + if(pLoop == 0) { + return; + } + PH7_VmEmitInstr(pGen->pVm, 0, PH7_OP_JMP, 0, 0, 0, &nSkip); + pBridge->nBreak = PH7_VmInstrLength(pGen->pVm); + SyZero(&sToken, sizeof(sToken)); + sToken.nLine = nLine; + pGen->pIn = &sToken; + pGen->pEnd = &sToken + 1; + PH7_CompileBreak(pGen); + if((pLoop->iFlags & GEN_BLOCK_SWITCH) == 0) { + pBridge->nContinue = PH7_VmInstrLength(pGen->pVm); + pGen->pIn = &sToken; + PH7_CompileContinue(pGen); + } + pGen->pIn = pSavedIn; + pGen->pEnd = pSavedEnd; + PH7_VmGetInstr(pGen->pVm, nSkip)->iP2 = PH7_VmInstrLength(pGen->pVm); +} /* * Compile or record a label. * A label is a target point that is specified by an identifier followed by a colon. @@ -2784,7 +2811,6 @@ static sxi32 PH7_CompileInclude(ph7_gen_state *pGen) { ph7_include *pInclude = (ph7_include *)SyMemBackendAlloc(&pGen->pVm->sAllocator, sizeof(ph7_include)); GenBlock *pBlock = pGen->pCurrent; GenBlock *pLoop = PH7_GenStateFetchBlock(pBlock, GEN_BLOCK_LOOP, 0); - sxu32 nSkip; if(pInclude == 0) { PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory"); } @@ -2800,26 +2826,7 @@ static sxi32 PH7_CompileInclude(ph7_gen_state *pGen) { } pInclude->pFunc = pBlock ? (ph7_vm_func *)pBlock->pUserData : 0; PH7_VmEmitInstr(pGen->pVm, nLine, PH7_OP_INCLUDE, 0, 1, pInclude, 0); - if(pLoop) { - PH7_VmEmitInstr(pGen->pVm, 0, PH7_OP_JMP, 0, 0, 0, &nSkip); - pInclude->nBreak = PH7_VmInstrLength(pGen->pVm); - /* Reuse the ordinary compiler's loop cleanup and forward fixups. */ - SyToken sToken; - SyToken *pSavedIn = pGen->pIn, *pSavedEnd = pGen->pEnd; - SyZero(&sToken, sizeof(sToken)); - sToken.nLine = nLine; - pGen->pIn = &sToken; - pGen->pEnd = &sToken + 1; - PH7_CompileBreak(pGen); - if((pLoop->iFlags & GEN_BLOCK_SWITCH) == 0) { - pInclude->nContinue = PH7_VmInstrLength(pGen->pVm); - pGen->pIn = &sToken; - PH7_CompileContinue(pGen); - } - pGen->pIn = pSavedIn; - pGen->pEnd = pSavedEnd; - PH7_VmGetInstr(pGen->pVm, nSkip)->iP2 = PH7_VmInstrLength(pGen->pVm); - } + GenStateCompileLoopBridge(pGen, pInclude, pLoop, nLine); } return SXRET_OK; } @@ -4275,6 +4282,8 @@ static sxi32 PH7_CompileThrow(ph7_gen_state *pGen) { */ static sxi32 PH7_CompileCatch(ph7_gen_state *pGen, ph7_exception *pException) { ph7_exception_block sCatch; + ph7_include sLoopBridge; + GenBlock *pLoop = PH7_GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, 0); SySet *pInstrContainer; GenBlock *pCatch; SyToken *pToken; @@ -4337,6 +4346,7 @@ static sxi32 PH7_CompileCatch(ph7_gen_state *pGen, ph7_exception *pException) { /* Compile the block */ pGen->pIn++; /* Jump the right parenthesis */ /* Create the catch block */ + GenStateCompileLoopBridge(pGen, &sLoopBridge, pLoop, pGen->pIn->nLine); rc = PH7_GenStateEnterBlock(&(*pGen), GEN_BLOCK_EXCEPTION, PH7_VmInstrLength(pGen->pVm), 0, &pCatch); if(rc != SXRET_OK) { return SXERR_ABORT; @@ -4344,8 +4354,15 @@ static sxi32 PH7_CompileCatch(ph7_gen_state *pGen, ph7_exception *pException) { /* Swap bytecode container */ pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm); PH7_VmSetByteCodeContainer(pGen->pVm, &sCatch.sByteCode); + if(pLoop) { + PH7_GenStateEnterBlock(pGen, GEN_BLOCK_LOOP | GEN_BLOCK_INCLUDE | + (pLoop->iFlags & GEN_BLOCK_SWITCH), 0, &sLoopBridge, 0); + } /* Compile the block */ PH7_CompileBlock(&(*pGen)); + if(pLoop) { + PH7_GenStateLeaveBlock(pGen, 0); + } /* Fix forward jumps now the destination is resolved */ PH7_GenStateFixJumps(pCatch, -1, PH7_VmInstrLength(pGen->pVm)); /* Emit the DONE instruction */ @@ -4371,8 +4388,11 @@ static sxi32 PH7_CompileCatch(ph7_gen_state *pGen, ph7_exception *pException) { static sxi32 PH7_CompileFinally(ph7_gen_state *pGen, ph7_exception *pException) { SySet *pInstrContainer; GenBlock *pFinally; + ph7_include sLoopBridge; + GenBlock *pLoop = PH7_GenStateFetchBlock(pGen->pCurrent, GEN_BLOCK_LOOP, 0); sxi32 rc; pGen->pIn++; /* Jump the 'finally' keyword */ + GenStateCompileLoopBridge(pGen, &sLoopBridge, pLoop, pGen->pIn->nLine); rc = PH7_GenStateEnterBlock(&(*pGen), GEN_BLOCK_EXCEPTION, PH7_VmInstrLength(pGen->pVm), 0, &pFinally); if(rc != SXRET_OK) { return SXERR_ABORT; @@ -4380,8 +4400,15 @@ static sxi32 PH7_CompileFinally(ph7_gen_state *pGen, ph7_exception *pException) /* Swap bytecode container */ pInstrContainer = PH7_VmGetByteCodeContainer(pGen->pVm); PH7_VmSetByteCodeContainer(pGen->pVm, &pException->sFinally); + if(pLoop) { + PH7_GenStateEnterBlock(pGen, GEN_BLOCK_LOOP | GEN_BLOCK_INCLUDE | + (pLoop->iFlags & GEN_BLOCK_SWITCH), 0, &sLoopBridge, 0); + } /* Compile the block */ PH7_CompileBlock(&(*pGen)); + if(pLoop) { + PH7_GenStateLeaveBlock(pGen, 0); + } /* Fix forward jumps now the destination is resolved */ PH7_GenStateFixJumps(pFinally, -1, PH7_VmInstrLength(pGen->pVm)); /* Emit the DONE instruction */ diff --git a/engine/oop.c b/engine/oop.c index 49f9eb1..768add0 100644 --- a/engine/oop.c +++ b/engine/oop.c @@ -948,19 +948,29 @@ static ph7_class_instance *NewClassInstance(ph7_vm *pVm, ph7_class *pClass) { * See the block comment above for more information. */ PH7_PRIVATE ph7_class_instance *PH7_NewClassInstance(ph7_vm *pVm, ph7_class *pClass) { + ph7_class_instance *pNew; + PH7_NewClassInstanceWithResult(pVm, pClass, &pNew, 0); + return pNew; +} +/* Source construction needs both the status and the initializer's exception, + * while existing internal callers retain the pointer-returning interface. */ +PH7_PRIVATE sxi32 PH7_NewClassInstanceWithResult(ph7_vm *pVm, ph7_class *pClass, + ph7_class_instance **ppNew, ph7_value *pResult) { ph7_class_instance *pNew; sxi32 rc; + *ppNew = 0; pNew = NewClassInstance(&(*pVm), &(*pClass)); if(pNew == 0) { - return 0; + return SXERR_MEM; } /* Associate a private VM frame with this class instance */ - rc = PH7_VmCreateClassInstanceFrame(&(*pVm), pNew); + rc = PH7_VmCreateClassInstanceFrame(&(*pVm), pNew, pResult); if(rc != SXRET_OK) { - SyMemBackendPoolFree(&pVm->sAllocator, pNew); - return 0; + PH7_ClassInstanceAbort(pNew); + return rc; } - return pNew; + *ppNew = pNew; + return SXRET_OK; } /* * Extract the value of a class instance [i.e: Object in the PHP jargon] attribute. @@ -1057,23 +1067,25 @@ static ph7_value *ExtractClassAttrValue(ph7_vm *pVm, VmClassAttr *pAttr) { * ) */ PH7_PRIVATE ph7_class_instance *PH7_CloneClassInstance(ph7_class_instance *pSrc) { + ph7_class_instance *pClone; + PH7_CloneClassInstanceWithResult(pSrc, &pClone, 0); + return pClone; +} +/* Preserve the pointer interface while source clone can propagate completion. */ +PH7_PRIVATE sxi32 PH7_CloneClassInstanceWithResult(ph7_class_instance *pSrc, + ph7_class_instance **ppClone, ph7_value *pResult) { ph7_class_instance *pClone; ph7_class_method *pMethod; SyHashEntry *pEntry2; SyHashEntry *pEntry; ph7_vm *pVm; sxi32 rc; - /* Allocate a new instance */ + *ppClone = 0; + /* Reuse construction's initializer completion and partial-instance cleanup. */ pVm = pSrc->pVm; - pClone = NewClassInstance(pVm, pSrc->pClass); - if(pClone == 0) { - return 0; - } - /* Associate a private VM frame with this class instance */ - rc = PH7_VmCreateClassInstanceFrame(pVm, pClone); + rc = PH7_NewClassInstanceWithResult(pVm, pSrc->pClass, &pClone, pResult); if(rc != SXRET_OK) { - SyMemBackendPoolFree(&pVm->sAllocator, pClone); - return 0; + return rc; } /* Duplicate object values */ SyHashResetLoopCursor(&pSrc->hAttr); @@ -1096,19 +1108,30 @@ PH7_PRIVATE ph7_class_instance *PH7_CloneClassInstance(ph7_class_instance *pSrc) if(pMethod) { if(pMethod->iCloneDepth < 16) { pMethod->iCloneDepth++; - PH7_VmCallClassMethod(pVm, pClone, pMethod, 0, 0, 0); + rc = PH7_VmCallClassMethod(pVm, pClone, pMethod, pResult, 0, 0); } else { /* Nesting limit reached */ PH7_VmThrowError(pVm, PH7_CTX_ERR, "Object clone limit reached"); } /* Reset the cursor */ pMethod->iCloneDepth = 0; + if(rc != SXRET_OK) { + PH7_ClassInstanceUnref(pClone); + return rc; + } } /* Return the cloned object */ - return pClone; + *ppClone = pClone; + return SXRET_OK; } #define CLASS_INSTANCE_DESTROYED 0x001 /* Instance is released */ #define CLASS_INSTANCE_FINALIZED 0x002 /* Destructor has already been invoked */ +/* Failed construction releases installed fields, but must not invoke the + * destructor on an instance whose initialization did not finish. */ +PH7_PRIVATE void PH7_ClassInstanceAbort(ph7_class_instance *pThis) { + pThis->iFlags |= CLASS_INSTANCE_FINALIZED; + PH7_ClassInstanceUnref(pThis); +} /* Run user finalization separately from storage release so VM teardown can * keep every closure environment valid until destructors have finished. */ PH7_PRIVATE sxi32 PH7_ClassInstanceFinalize(ph7_class_instance *pThis) { diff --git a/engine/vm.c b/engine/vm.c index b3d6f15..5bd5c43 100644 --- a/engine/vm.c +++ b/engine/vm.c @@ -939,9 +939,11 @@ static sxi32 VmOverloadDominates(const sxi32 *aFirst, const sxi32 *aSecond, sxu3 } /* Forward declaration */ #define VM_EXEC_INCLUDE 2 +#define VM_EXEC_HANDLER 4 #define VM_INCLUDE_RETURN 3 #define VM_INCLUDE_JUMP 4 #define VM_INCLUDE_EXCEPTION 5 +#define VM_RETURN_VOID 6 static sxi32 VmExecIncludedFile(ph7_vm *pVm, SyString *pPath, int iFlags, ph7_value *pResult, ph7_include *pInclude); static sxi32 VmLocalExec(ph7_vm *pVm, SySet *pByteCode, ph7_value *pResult); static sxi32 VmClassMemberAccess(ph7_vm *pVm, ph7_class *pClass, sxi32 iProtection); @@ -1348,7 +1350,8 @@ static sxi32 VmMountUserClass( */ PH7_PRIVATE sxi32 PH7_VmCreateClassInstanceFrame( ph7_vm *pVm, /* Target VM */ - ph7_class_instance *pObj /* Class instance */ + ph7_class_instance *pObj, /* Class instance */ + ph7_value *pResult /* Initializer exception, if any */ ) { ph7_class *pClass = pObj->pClass; ph7_class_attr *pAttr; @@ -1366,19 +1369,28 @@ PH7_PRIVATE sxi32 PH7_VmCreateClassInstanceFrame( } pVmAttr->pAttr = pAttr; if((pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT | PH7_CLASS_ATTR_STATIC)) == 0) { - ph7_value *pMemObj, *pResult; + ph7_value *pMemObj, sValue; /* Reserve a memory object for this attribute */ pMemObj = PH7_ReserveMemObj(&(*pVm)); - pResult = PH7_ReserveMemObj(&(*pVm)); - if(pMemObj == 0 || pResult == 0) { + if(pMemObj == 0) { SyMemBackendPoolFree(&pVm->sAllocator, pVmAttr); return SXERR_MEM; } MemObjSetType(pMemObj, pAttr->nType); + PH7_MemObjInit(pVm, &sValue); if(SySetUsed(&pAttr->aByteCode) > 0) { /* Initialize attribute default value (any complex expression) */ - VmLocalExec(&(*pVm), &pAttr->aByteCode, pResult); - rc = PH7_MemObjSafeStore(pResult, pMemObj); + rc = VmLocalExec(&(*pVm), &pAttr->aByteCode, &sValue); + if(rc != SXRET_OK) { + if(rc == PH7_EXCEPTION && pResult) { + PH7_MemObjStore(&sValue, pResult); + } + PH7_MemObjRelease(&sValue); + PH7_VmDestroyMemObj(pVm, pMemObj); + SyMemBackendPoolFree(&pVm->sAllocator, pVmAttr); + return rc; + } + rc = PH7_MemObjSafeStore(&sValue, pMemObj); if(rc != SXRET_OK) { PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Cannot assign a value of incompatible type to variable '%z::$%z'", &pClass->sName, &pAttr->sName); } @@ -1391,7 +1403,7 @@ PH7_PRIVATE sxi32 PH7_VmCreateClassInstanceFrame( pMemObj->x.pOther = pMap; } /* Free up memory */ - PH7_MemObjRelease(pResult); + PH7_MemObjRelease(&sValue); /* Record attribute index */ pVmAttr->nIdx = pMemObj->nIdx; rc = SyHashInsert(&pObj->hAttr, SyStringData(&pAttr->sName), SyStringLength(&pAttr->sName), pVmAttr); @@ -2508,9 +2520,10 @@ PH7_PRIVATE sxi32 PH7_VmConfigure( } /* Forward declaration */ static int VmObConsumer(const void *pData, unsigned int nDataLen, void *pUserData); -static sxi32 VmExecFinallyBlock(ph7_vm *pVm, ph7_exception *pException); +static sxi32 VmExecExceptionBlock(ph7_vm *pVm, ph7_exception *pException, + ph7_exception_block *pCatch, ph7_class_instance *pThis, ph7_value *pResult); static sxi32 VmUncaughtException(ph7_vm *pVm, ph7_class_instance *pThis); -static sxi32 VmThrowException(ph7_vm *pVm, ph7_class_instance *pThis); +static ph7_exception_block *VmFindCatch(ph7_vm *pVm, ph7_exception *pException, ph7_class_instance *pThis); /* * Consume a generated run-time error message by invoking the VM output * consumer callback. @@ -2662,6 +2675,19 @@ static sxi32 VmModuleLoaded(ph7_vm *pVm, const char *zName, sxu32 nLen) { * should be used respectively to clean up the mess that was left behind * or to reset the VM to it's initial state. */ +/* Region state belongs to an execution, not to reusable compiled bytecode. */ +typedef struct VmExceptionRegion VmExceptionRegion; +struct VmExceptionRegion { + ph7_exception sException; + VmFrame *pFrame; + sxi32 nOperand; + sxi32 nStart; + sxi32 nEnd; +}; +#define VM_TRANSFER_NORMAL 0 +#define VM_TRANSFER_RETURN 1 +#define VM_TRANSFER_THROW 2 +#define VM_TRANSFER_JUMP 3 static sxi32 VmByteCodeExec( ph7_vm *pVm, /* Target VM */ VmInstr *aInstr, /* PH7 bytecode program */ @@ -2669,18 +2695,23 @@ static sxi32 VmByteCodeExec( int nTos, /* Top entry in the operand stack (usually -1) */ ph7_value *pResult, /* Store program return value here. NULL otherwise */ sxu32 *pLastRef, /* Last referenced ph7_value index */ - int iExecFlags /* Callback (1) or included statements (VM_EXEC_INCLUDE) */ + int iExecFlags /* Callback (1), included statements or exception handler */ ) { VmInstr *pInstr; ph7_value *pTos; SySet aArg; sxi32 pc; sxi32 rc; - sxi32 bReturning = FALSE; + sxi32 iTransfer = VM_TRANSFER_NORMAL, nTarget = 0; + sxbool bReturnValue = FALSE, bLoopStop = FALSE, bExternalJump = FALSE; + ph7_value sTransfer; + SySet aRegion; sxbool bScopeStopped = FALSE; VmFrame *pEntryFrame = pVm->pFrame; /* Argument container */ SySetInit(&aArg, &pVm->sAllocator, sizeof(ph7_value *)); + SySetInit(&aRegion, &pVm->sAllocator, sizeof(VmExceptionRegion)); + PH7_MemObjInit(pVm, &sTransfer); if(nTos < 0) { pTos = &pStack[-1]; } else { @@ -2722,7 +2753,24 @@ Resume: * and return immediately. */ case PH7_OP_DONE: - bReturning = pInstr->p3 != 0; + if(pInstr->p3) { + bReturnValue = pInstr->iP1 != 0; + PH7_MemObjRelease(&sTransfer); + if(bReturnValue) { + if(pTos < pStack) { + goto Abort; + } + PH7_MemObjStore(pTos, &sTransfer); + if(pLastRef) { + *pLastRef = pTos->nIdx; + } + VmPopOperand(&pTos, 1); + } else if(pLastRef) { + *pLastRef = SXU32_HIGH; + } + iTransfer = VM_TRANSFER_RETURN; + goto Unwind; + } /* Only normal fallthrough of a function body requires a result. * Keep the call's NULL result unconverted, unlike explicit return. */ if(!pInstr->iP1 && pInstr->iP2 && (pEntryFrame->iFlags & VM_FRAME_ACTIVE) && (pEntryFrame->iFlags & VM_FRAME_THROW) == 0) { @@ -2731,10 +2779,6 @@ Resume: PH7_VmThrowError(&(*pVm), PH7_CTX_WARNING, "Missing return value in closure/method returning non-void"); } } - if(bReturning && !pInstr->iP1 && (iExecFlags & VM_EXEC_INCLUDE) && pResult) { - PH7_MemObjRelease(pResult); - MemObjSetType(pResult, MEMOBJ_NULL); - } if(pInstr->iP1) { if(pTos < pStack) { goto Abort; @@ -2742,13 +2786,7 @@ Resume: if(pLastRef) { *pLastRef = pTos->nIdx; } - /* Unwind local loops, but let VmExecExceptionBlock() release - * the handler without freeing its borrowed owner frames. */ - if(pInstr->p3 && (iExecFlags & VM_EXEC_INCLUDE) == 0) { /* Called from a return statement */ - while(pVm->pFrame->pParent && (pVm->pFrame->iFlags & (VM_FRAME_ACTIVE | VM_FRAME_CATCH | VM_FRAME_FINALLY)) == 0) { - VmLeaveFrame(&(*pVm)); - } - } else if(!pInstr->p3 && (iExecFlags & VM_EXEC_INCLUDE) == 0 && (pVm->pFrame->iFlags & VM_FRAME_LOOP)) { + if((iExecFlags & VM_EXEC_INCLUDE) == 0 && (pVm->pFrame->iFlags & VM_FRAME_LOOP)) { /* 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)); @@ -2757,42 +2795,12 @@ Resume: if(pResult) { /* Execution result */ PH7_MemObjStore(pTos, pResult); - VmFrame *pOwner = pVm->pFrame; - if(iExecFlags & VM_EXEC_INCLUDE) { - while(pOwner->pParent && (pOwner->iFlags & VM_FRAME_ACTIVE) == 0) { - pOwner = pOwner->pParent; - } - } - if(!pInstr->iP2 && (pOwner->iFlags & VM_FRAME_ACTIVE)) { - ph7_vm_func *pFunc = (ph7_vm_func *)pOwner->pUserData; - if(pFunc->nType) { - if((pFunc->nType & MEMOBJ_MIXED) == 0) { - if(pFunc->nType & MEMOBJ_VOID) { - PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Return with a value in closure/method returning void"); - } else if(pFunc->nType != pResult->nType) { - if(PH7_CheckVarCompat(pResult, pFunc->nType) == SXRET_OK) { - ProcMemObjCast xCast = PH7_MemObjCastMethod(pFunc->nType); - xCast(pResult); - } else if((pFunc->iFlags & MEMOBJ_HASHMAP) && (pResult->nType & MEMOBJ_HASHMAP)) { - if(PH7_HashmapCast(pResult, pFunc->iFlags ^ MEMOBJ_HASHMAP) != SXRET_OK) { - PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Incompatible type when returning data by closure/method"); - } - } else { - PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Incompatible type when returning data by closure/method"); - } - } - } - } - } } VmPopOperand(&pTos, 1); } else if(pLastRef) { /* Nothing referenced */ *pLastRef = SXU32_HIGH; } - if(bReturning && (iExecFlags & VM_EXEC_INCLUDE)) { - goto IncludeReturn; - } goto Done; /* * HALT: P1 * * @@ -2833,18 +2841,16 @@ Resume: * P1 marks an include-to-owner transfer rather than a local jump. */ case PH7_OP_JMP: - if(pInstr->iP1 && (iExecFlags & VM_EXEC_INCLUDE)) { - if(pResult) { - PH7_MemObjRelease(pResult); - pResult->x.iVal = pInstr->iP2; - MemObjSetType(pResult, MEMOBJ_INT); - } - VmScopeSync(pVm, 0, &aArg); - SySetRelease(&aArg); - return VM_INCLUDE_JUMP; + bExternalJump = FALSE; + if(pInstr->iP1 && (iExecFlags & (VM_EXEC_INCLUDE | VM_EXEC_HANDLER))) { + iTransfer = VM_TRANSFER_JUMP; + nTarget = pInstr->iP2; + bExternalJump = TRUE; + goto Unwind; } - pc = pInstr->iP2 - 1; - break; + iTransfer = VM_TRANSFER_JUMP; + nTarget = pInstr->iP2; + goto Unwind; /* * JMPZ: P1 P2 * * @@ -2918,6 +2924,12 @@ Resume: * as well as on 'break' and 'continue' instructions. */ case PH7_OP_LF_STOP: { + if(aInstr[pc + 1].iOp == PH7_OP_JMP && pVm->pFrame != pEntryFrame && + (pVm->pFrame->iFlags & (VM_FRAME_LOOP | VM_FRAME_EXCEPTION))) { + bLoopStop = TRUE; + bScopeStopped = TRUE; + break; + } /* Leave the jump loop frame */ if(pVm->pFrame->iFlags & VM_FRAME_LOOP) { VmLeaveFrame(&(*pVm)); @@ -3155,11 +3167,16 @@ Resume: PH7_VmThrowError(pVm, PH7_CTX_ERR, "Cannot assign a value of incompatible type to variable '$%z'", &pStatic->sName); } } + if(rc == PH7_EXCEPTION) { + PH7_MemObjStore(&sValue, &sTransfer); + } PH7_MemObjRelease(&sValue); if(rc == PH7_ABORT) { goto Abort; } else if(rc == PH7_EXCEPTION) { - goto Exception; + iTransfer = VM_TRANSFER_THROW; + nTarget = pc + 1; + goto Unwind; } } else if(pObj->nType & MEMOBJ_HASHMAP) { pObj->x.pOther = PH7_NewHashmap(pVm, 0, 0); @@ -3209,7 +3226,10 @@ Resume: } else if(rc == PH7_ABORT) { goto Abort; } else if(rc == PH7_EXCEPTION) { - goto Exception; + PH7_MemObjStore(&pConst->sValue, &sTransfer); + iTransfer = VM_TRANSFER_THROW; + nTarget = pc + 1; + goto Unwind; } } else { /* Variable declaration */ @@ -4765,15 +4785,20 @@ Resume: } /* * OP_LOAD_EXCEPTION * P2 P3 - * Push an exception in the corresponding container so that - * it can be thrown later by the OP_THROW instruction. + * Enter an invocation-owned region. The VM-wide stack records active + * regions/boundaries so calls defer uncaught handling until unwinding. */ case PH7_OP_LOAD_EXCEPTION: { ph7_exception *pException = (ph7_exception *)pInstr->p3; + VmExceptionRegion sRegion; 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; + sRegion.sException = *pException; + sRegion.sException.pScope = pVm->pScope ? pVm->pScope->pInfo : 0; + sRegion.sException.pScopeExec = pVm->pScope ? pVm->pScope->pExec : 0; + sRegion.sException.pFrame = pVm->pFrame; + sRegion.nStart = pc; + sRegion.nEnd = pInstr->iP2; + sRegion.nOperand = (sxi32)(pTos - pStack + 1); /* Create the exception frame */ rc = VmEnterFrame(&(*pVm), 0, 0, &pFrame); if(rc != SXRET_OK) { @@ -4782,12 +4807,13 @@ Resume: /* Mark the special frame */ pFrame->iFlags |= VM_FRAME_EXCEPTION; pFrame->iExceptionJump = pInstr->iP2; - /* Point to the frame that trigger the exception */ - pFrame = pFrame->pParent; - while(pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION)) { - pFrame = pFrame->pParent; + sRegion.pFrame = pFrame; + if(SySetPut(&aRegion, &sRegion) != SXRET_OK) { + PH7_VmMemoryError(pVm); + } + if(SySetPut(&pVm->aException, &pException) != SXRET_OK) { + PH7_VmMemoryError(pVm); } - pException->pFrame = pFrame; break; } /* @@ -4795,47 +4821,18 @@ Resume: * Pop a previously pushed exception from the corresponding container. */ case PH7_OP_POP_EXCEPTION: { - ph7_exception *pException = (ph7_exception *)pInstr->p3; - if(SySetUsed(&pVm->aException) > 0) { - ph7_exception **apException; - /* Pop the loaded exception */ - apException = (ph7_exception **)SySetBasePtr(&pVm->aException); - if(pException && pException == apException[SySetUsed(&pVm->aException) - 1]) { - if(SySetUsed(&pException->sFinally)) { - /* Execute the 'finally' block */ - rc = VmExecFinallyBlock(&(*pVm), pException); - if(rc == SXERR_ABORT) { - /* Abort processing immediately */ - goto Abort; - } - } - (void)SySetPop(&pVm->aException); - } - } - pException->pFrame = 0; - /* Leave the exception frame */ - VmLeaveFrame(&(*pVm)); - if(bReturning) { - goto IncludeReturn; - } - break; + iTransfer = VM_TRANSFER_NORMAL; + nTarget = pc + 1; + goto Unwind; } /* * OP_THROW * P2 * * Throw an user exception. */ case PH7_OP_THROW: { - VmFrame *pFrame = pVm->pFrame; - sxu32 nJump = pInstr->iP2; if(pTos < pStack) { goto Abort; } - while(pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION)) { - /* Safely ignore the exception frame */ - pFrame = pFrame->pParent; - } - /* Tell the upper layer that an exception was thrown */ - pFrame->iFlags |= VM_FRAME_THROW; if(pTos->nType & MEMOBJ_OBJ) { ph7_class_instance *pThis = (ph7_class_instance *)pTos->x.pOther; ph7_class *pException; @@ -4849,13 +4846,6 @@ Resume: /* Abort processing immediately */ goto Abort; } - } else { - /* Throw the exception */ - rc = VmThrowException(&(*pVm), pThis); - if(rc == SXERR_ABORT) { - /* Abort processing immediately */ - goto Abort; - } } } else { /* Expecting a class instance */ @@ -4865,14 +4855,11 @@ Resume: goto Abort; } } - /* Pop the top entry */ + PH7_MemObjStore(pTos, &sTransfer); VmPopOperand(&pTos, 1); - if(iExecFlags & VM_EXEC_INCLUDE) { - goto IncludeException; - } - /* Perform an unconditional jump */ - pc = nJump - 1; - break; + iTransfer = VM_TRANSFER_THROW; + nTarget = pc + 1; + goto Unwind; } /* * OP_IMPORT * * * @@ -4973,22 +4960,27 @@ Resume: PH7_MemObjInit(pVm, &sIncludeResult); rc = VmExecIncludedFile(&(*pVm), &sFile, iFlags | PH7_AERSCRIPT_FILE, &sIncludeResult, pInclude); if(rc == VM_INCLUDE_JUMP) { - pc = (sxi32)sIncludeResult.x.iVal - 1; + nTarget = (sxi32)sIncludeResult.x.iVal; PH7_MemObjRelease(&sIncludeResult); - break; + iTransfer = VM_TRANSFER_JUMP; + bExternalJump = FALSE; + goto Unwind; } - if(!pInstr->iP1 && rc == VM_INCLUDE_RETURN) { - if(pResult) { - PH7_MemObjStore(&sIncludeResult, pResult); - } + if(!pInstr->iP1 && (rc == VM_INCLUDE_RETURN || rc == VM_RETURN_VOID)) { + PH7_MemObjStore(&sIncludeResult, &sTransfer); PH7_MemObjRelease(&sIncludeResult); - bReturning = TRUE; - goto IncludeReturn; + bReturnValue = rc == VM_INCLUDE_RETURN; + iTransfer = VM_TRANSFER_RETURN; + goto Unwind; + } + if(rc == VM_INCLUDE_EXCEPTION || rc == PH7_EXCEPTION) { + PH7_MemObjStore(&sIncludeResult, &sTransfer); + PH7_MemObjRelease(&sIncludeResult); + iTransfer = VM_TRANSFER_THROW; + nTarget = pc + 1; + goto Unwind; } PH7_MemObjRelease(&sIncludeResult); - if(rc == VM_INCLUDE_EXCEPTION) { - goto IncludeException; - } if(!pInstr->iP1 && rc == SXERR_NOTFOUND) { PH7_VmThrowError(pVm, PH7_CTX_WARNING, "IO error while including file: '%z'", &sFile); break; @@ -4996,9 +4988,6 @@ Resume: if(!pInstr->iP1 && rc == PH7_ABORT) { goto Abort; } - if(!pInstr->iP1 && rc == PH7_EXCEPTION) { - goto Exception; - } if(rc != SXRET_OK && rc != SXERR_EXISTS) { PH7_VmThrowError(pVm, PH7_CTX_ERR, "IO error while including file: '%z'", &sFile); } @@ -5456,25 +5445,37 @@ Resume: } ph7_class_method *pCons; /* Create a new class instance */ - pNew = PH7_NewClassInstance(&(*pVm), pClass); - if(pNew == 0) { - PH7_VmMemoryError(&(*pVm)); - } + rc = PH7_NewClassInstanceWithResult(pVm, pClass, &pNew, &sTransfer); /* Check if a constructor is available */ pCons = PH7_ClassExtractMethod(pClass, "__construct", sizeof("__construct") - 1); - if(pCons) { + if(rc == SXRET_OK && pCons) { /* Call the class constructor */ SySetReset(&aArg); while(pArg < pTos) { SySetPut(&aArg, (const void *)&pArg); pArg++; } - VmCallClassMethod(&(*pVm), pNew, pCons, 0, (int)SySetUsed(&aArg), (ph7_value **)SySetBasePtr(&aArg), FALSE); + rc = VmCallClassMethod(&(*pVm), pNew, pCons, &sTransfer, (int)SySetUsed(&aArg), (ph7_value **)SySetBasePtr(&aArg), FALSE); /* TICKET 1433-52: Unsetting $this in the constructor body */ if(pNew->iRef < 1) { pNew->iRef = 1; } } + if(rc != SXRET_OK) { + if(pNew) { + PH7_ClassInstanceAbort(pNew); + } + VmPopOperand(&pTos, pInstr->iP1 + 1); + if(rc == PH7_EXCEPTION) { + iTransfer = VM_TRANSFER_THROW; + nTarget = pc + 1; + goto Unwind; + } + if(rc == SXERR_MEM) { + PH7_VmMemoryError(pVm); + } + goto Abort; + } if(pInstr->iP1 > 0) { /* Pop given arguments */ VmPopOperand(&pTos, pInstr->iP1); @@ -5502,15 +5503,23 @@ Resume: /* Point to the source */ pSrc = (ph7_class_instance *)pTos->x.pOther; /* Perform the clone operation */ - pClone = PH7_CloneClassInstance(pSrc); - PH7_MemObjRelease(pTos); - if(pClone == 0) { - PH7_VmMemoryError(&(*pVm)); - } else { - /* Load the cloned object */ - pTos->x.pOther = pClone; - MemObjSetType(pTos, MEMOBJ_OBJ); + rc = PH7_CloneClassInstanceWithResult(pSrc, &pClone, &sTransfer); + if(rc != SXRET_OK) { + VmPopOperand(&pTos, 1); + if(rc == PH7_EXCEPTION) { + iTransfer = VM_TRANSFER_THROW; + nTarget = pc + 1; + goto Unwind; + } + if(rc == SXERR_MEM) { + PH7_VmMemoryError(pVm); + } + goto Abort; } + PH7_MemObjRelease(pTos); + /* Load the cloned object */ + pTos->x.pOther = pClone; + MemObjSetType(pTos, MEMOBJ_OBJ); break; } /* @@ -5535,7 +5544,19 @@ Resume: pCase = &aCase[n]; PH7_MemObjLoad(pTos, &sValue); /* Execute the case expression first */ - VmLocalExec(pVm, &pCase->aByteCode, &sCaseValue); + rc = VmLocalExec(pVm, &pCase->aByteCode, &sCaseValue); + if(rc == PH7_EXCEPTION || rc == PH7_ABORT) { + PH7_MemObjStore(&sCaseValue, &sTransfer); + PH7_MemObjRelease(&sValue); + PH7_MemObjRelease(&sCaseValue); + VmPopOperand(&pTos, 1); + if(rc == PH7_ABORT) { + goto Abort; + } + iTransfer = VM_TRANSFER_THROW; + nTarget = pc + 1; + goto Unwind; + } /* Compare the two expression */ rc = PH7_MemObjCmp(&sValue, &sCaseValue, FALSE, 0); PH7_MemObjRelease(&sValue); @@ -5878,6 +5899,16 @@ Resume: if(rc == PH7_ABORT) { goto Abort; } + if(rc == PH7_EXCEPTION) { + PH7_MemObjStore(pObj, &sTransfer); + PH7_MemObjRelease(pTos); + pTos = &pTos[-pInstr->iP1]; + VmPopOperand(&pTos, 1); + VmLeaveFrame(pVm); + iTransfer = VM_TRANSFER_THROW; + nTarget = pc + 1; + goto Unwind; + } if(aFormalArg[n].nType == SXU32_HIGH) { /* Argument must be a class instance [i.e: object] */ SyString *pName = &aFormalArg[n].sClass; @@ -5961,37 +5992,19 @@ Resume: } pTos->nIdx = n; } - /* Cleanup the mess left behind */ - if(rc != PH7_ABORT && ((pFrame->iFlags & VM_FRAME_THROW) || rc == PH7_EXCEPTION)) { - /* An exception was throw in this frame */ - pFrame = pFrame->pParent; - if((iExecFlags & 1) == 0 && pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION) && pFrame->iExceptionJump > 0) { - /* Pop the result */ - VmPopOperand(&pTos, 1); - /* Jump to this destination */ - pc = pFrame->iExceptionJump - 1; - rc = (iExecFlags & VM_EXEC_INCLUDE) ? VM_INCLUDE_EXCEPTION : PH7_OK; - } else { - if(pFrame->pParent) { - rc = PH7_EXCEPTION; - } else { - /* Continue normal execution */ - rc = PH7_OK; - } - } - } /* Free the operand stack */ SyMemBackendFree(&pVm->sAllocator, pFrameStack); /* Leave the frame */ VmLeaveFrame(&(*pVm)); - if(rc == VM_INCLUDE_EXCEPTION) { - goto IncludeException; - } if(rc == PH7_ABORT) { /* Abort processing immediately */ goto Abort; } else if(rc == PH7_EXCEPTION) { - goto Exception; + PH7_MemObjStore(pTos, &sTransfer); + VmPopOperand(&pTos, 1); + iTransfer = VM_TRANSFER_THROW; + nTarget = pc + 1; + goto Unwind; } } else { ph7_user_func *pFunc; @@ -6064,68 +6077,173 @@ Resume: } /* Switch() */ pc++; /* Next instruction in the stream */ } /* For(;;) */ -IncludeException: - /* The catch has already run. Transfer to its owning bytecode's epilogue, - * forwarding across included chunks without throwing/catching again. */ - { - 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); - pTos--; - } - return VM_INCLUDE_EXCEPTION; - } - if(pFrame->iFlags & VM_FRAME_EXCEPTION) { - while(pVm->pFrame != pFrame) { - VmLeaveFrame(pVm); - } - pc = pFrame->iExceptionJump; - goto Resume; - } - if(pFrame == pEntryFrame) { - /* A called method still has its own execution boundary. */ - goto Exception; - } - pFrame = pFrame->pParent; +Unwind: + /* Freeze a completion before running handlers. Each invocation consumes its + * own regions inside-out; a handler completion replaces, never combines with, + * the pending completion. The compiled finally remains reusable. */ + while(SySetUsed(&aRegion)) { + VmExceptionRegion sRegion = *(VmExceptionRegion *)SySetPeek(&aRegion); + ph7_exception_block *pCatch = 0; + ph7_value sHandler; + if(iTransfer == VM_TRANSFER_JUMP && !bExternalJump && nTarget > sRegion.nStart && nTarget <= sRegion.nEnd) { + break; } - } - goto Exception; -IncludeReturn: - /* The include has no return boundary. Let the owner's existing exception - * epilogue run finally before completing the pending return. */ - if((iExecFlags & VM_EXEC_INCLUDE) == 0 || pVm->pFrame != pEntryFrame) { - while(pVm->pFrame->pParent && pVm->pFrame != pEntryFrame && (pVm->pFrame->iFlags & (VM_FRAME_ACTIVE | VM_FRAME_CATCH | VM_FRAME_FINALLY | VM_FRAME_EXCEPTION)) == 0) { + /* The selected completion is already frozen. Release interrupted + * expressions before handlers, preserving operands below this region. */ + if(pTos - pStack + 1 > sRegion.nOperand) { + VmPopOperand(&pTos, (sxi32)(pTos - pStack + 1) - sRegion.nOperand); + } + SySetPop(&aRegion); + SySetPop(&pVm->aException); + if(iTransfer == VM_TRANSFER_THROW) { + nTarget = sRegion.nEnd + 1; + } + while(pVm->pFrame != sRegion.pFrame) { VmLeaveFrame(pVm); } - if(((iExecFlags & VM_EXEC_INCLUDE) == 0 || pVm->pFrame != pEntryFrame) && (pVm->pFrame->iFlags & VM_FRAME_EXCEPTION) && pVm->pFrame->iExceptionJump > 0) { - pc = pVm->pFrame->iExceptionJump; + PH7_MemObjInit(pVm, &sHandler); + if(iTransfer == VM_TRANSFER_THROW && (sTransfer.nType & MEMOBJ_OBJ)) { + pCatch = VmFindCatch(pVm, &sRegion.sException, (ph7_class_instance *)sTransfer.x.pOther); + } + if(pCatch) { + rc = VmExecExceptionBlock(pVm, &sRegion.sException, pCatch, + (ph7_class_instance *)sTransfer.x.pOther, &sHandler); + if(rc == PH7_ABORT) { + PH7_MemObjRelease(&sHandler); + goto Abort; + } + iTransfer = rc == PH7_EXCEPTION ? VM_TRANSFER_THROW : + (rc == VM_INCLUDE_RETURN || rc == VM_RETURN_VOID ? VM_TRANSFER_RETURN : VM_TRANSFER_NORMAL); + bReturnValue = rc == VM_INCLUDE_RETURN; + PH7_MemObjStore(&sHandler, &sTransfer); + if(pLastRef) { + *pLastRef = SXU32_HIGH; + } + nTarget = sRegion.nEnd + 1; + if(rc == VM_INCLUDE_JUMP) { + iTransfer = VM_TRANSFER_JUMP; + nTarget = (sxi32)sHandler.x.iVal; + bExternalJump = bLoopStop = FALSE; + } + } + if(SySetUsed(&sRegion.sException.sFinally)) { + rc = VmExecExceptionBlock(pVm, &sRegion.sException, 0, 0, &sHandler); + if(rc == PH7_ABORT) { + PH7_MemObjRelease(&sHandler); + goto Abort; + } + if(rc == PH7_EXCEPTION || rc == VM_INCLUDE_RETURN || rc == VM_RETURN_VOID || rc == VM_INCLUDE_JUMP) { + iTransfer = rc == PH7_EXCEPTION ? VM_TRANSFER_THROW : VM_TRANSFER_RETURN; + bReturnValue = rc == VM_INCLUDE_RETURN; + PH7_MemObjStore(&sHandler, &sTransfer); + if(rc == PH7_EXCEPTION) { + nTarget = sRegion.nEnd + 1; + } + if(pLastRef) { + *pLastRef = SXU32_HIGH; + } + if(rc == VM_INCLUDE_JUMP) { + iTransfer = VM_TRANSFER_JUMP; + nTarget = (sxi32)sHandler.x.iVal; + bExternalJump = bLoopStop = FALSE; + } + } + } + PH7_MemObjRelease(&sHandler); + VmLeaveFrame(pVm); + if(iTransfer == VM_TRANSFER_NORMAL) { + bLoopStop = FALSE; + bExternalJump = FALSE; + pc = nTarget; goto Resume; } } + if(iTransfer == VM_TRANSFER_JUMP) { + if(bLoopStop && pVm->pFrame != pEntryFrame && (pVm->pFrame->iFlags & VM_FRAME_LOOP)) { + VmLeaveFrame(pVm); + } + bLoopStop = FALSE; + if(bExternalJump) { + PH7_MemObjRelease(&sTransfer); + sTransfer.x.iVal = nTarget; + MemObjSetType(&sTransfer, MEMOBJ_INT); + rc = VM_INCLUDE_JUMP; + goto Complete; + } + pc = nTarget; + goto Resume; + } + if(iTransfer == VM_TRANSFER_THROW) { + if(SySetUsed(&pVm->aException) == 0 && (iExecFlags & (VM_EXEC_HANDLER | VM_EXEC_INCLUDE)) == 0) { + /* A nonfatal handler resumes this iteration with its bindings intact. */ + rc = VmUncaughtException(pVm, (sTransfer.nType & MEMOBJ_OBJ) ? (ph7_class_instance *)sTransfer.x.pOther : 0); + if(rc == PH7_ABORT) { + goto Abort; + } + iTransfer = VM_TRANSFER_NORMAL; + pc = nTarget; + goto Resume; + } + rc = (iExecFlags & VM_EXEC_INCLUDE) ? VM_INCLUDE_EXCEPTION : PH7_EXCEPTION; + goto Complete; + } + while(pVm->pFrame != pEntryFrame) { + VmLeaveFrame(pVm); + } + /* Only the selected explicit value is checked against the return contract. + * A bare return keeps NULL. An include directly borrowing the active method + * frame has no enclosing region left to override its result; otherwise it + * defers this check to the owner, just like a handler. */ + if(bReturnValue && (pEntryFrame->iFlags & VM_FRAME_ACTIVE) && + (iExecFlags & VM_EXEC_HANDLER) == 0) { + ph7_vm_func *pFunc = (ph7_vm_func *)pEntryFrame->pUserData; + if(pFunc->nType && (pFunc->nType & MEMOBJ_MIXED) == 0) { + if(pFunc->nType & MEMOBJ_VOID) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Return with a value in closure/method returning void"); + } else if(pFunc->nType != sTransfer.nType) { + if(PH7_CheckVarCompat(&sTransfer, pFunc->nType) == SXRET_OK) { + ProcMemObjCast xCast = PH7_MemObjCastMethod(pFunc->nType); + xCast(&sTransfer); + } else if((pFunc->iFlags & MEMOBJ_HASHMAP) && (sTransfer.nType & MEMOBJ_HASHMAP)) { + if(PH7_HashmapCast(&sTransfer, pFunc->iFlags ^ MEMOBJ_HASHMAP) != SXRET_OK) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Incompatible type when returning data by closure/method"); + } + } else { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Incompatible type when returning data by closure/method"); + } + } + } + } + rc = (iExecFlags & (VM_EXEC_INCLUDE | VM_EXEC_HANDLER)) ? + (bReturnValue ? VM_INCLUDE_RETURN : VM_RETURN_VOID) : SXRET_OK; +Complete: + if(pResult) { + PH7_MemObjStore(&sTransfer, pResult); + } + goto Cleanup; Done: - VmScopeSync(pVm, 0, &aArg); - SySetRelease(&aArg); - return bReturning && (iExecFlags & VM_EXEC_INCLUDE) ? VM_INCLUDE_RETURN : SXRET_OK; + rc = SXRET_OK; + goto Cleanup; Abort: + rc = PH7_ABORT; + goto Cleanup; +Cleanup: VmScopeSync(pVm, 0, &aArg); SySetRelease(&aArg); + while(SySetUsed(&aRegion)) { + SySetPop(&aRegion); + SySetPop(&pVm->aException); + } + SySetRelease(&aRegion); + while(pVm->pFrame != pEntryFrame) { + VmLeaveFrame(pVm); + } + PH7_MemObjRelease(&sTransfer); while(pTos >= pStack) { PH7_MemObjRelease(pTos); pTos--; } - return PH7_ABORT; -Exception: - VmScopeSync(pVm, 0, &aArg); - SySetRelease(&aArg); - while(pTos >= pStack) { - PH7_MemObjRelease(pTos); - pTos--; - } - return PH7_EXCEPTION; + return rc; } /* * Execute as much of a local PH7 bytecode program as we can then return. @@ -7299,7 +7417,7 @@ 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. */ -static sxi32 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 */ @@ -7312,6 +7430,7 @@ static sxi32 VmCallClassMethod( VmInstr aInstr[2]; ph7_class_method *pSeed; sxi32 iEntry; + sxi32 rc; int iCursor; int i; /* Create a new operand stack */ @@ -7382,10 +7501,10 @@ static sxi32 VmCallClassMethod( aInstr[1].iLine = 1; aInstr[1].pFile = (SyString *)&sFileName; /* Execute the method body (if available) */ - VmByteCodeExec(&(*pVm), aInstr, aStack, iCursor, pResult, 0, TRUE); + rc = VmByteCodeExec(&(*pVm), aInstr, aStack, iCursor, pResult, 0, TRUE); /* Clean up the mess left behind */ SyMemBackendFree(&pVm->sAllocator, aStack); - return PH7_OK; + return rc; } PH7_PRIVATE sxi32 PH7_VmCallClassMethod(ph7_vm *pVm, ph7_class_instance *pThis, ph7_class_method *pMethod, ph7_value *pResult, int nArg, ph7_value **apArg) { @@ -8947,11 +9066,10 @@ 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. */ +/* Borrow the region owner's context without releasing its frames or + * overwriting its 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) { + ph7_exception_block *pCatch, ph7_class_instance *pThis, ph7_value *pResult) { VmFrame *pSavedFrame = pVm->pFrame; VmFrame *pOwner = pException->pFrame; VmFrame *pFrame; @@ -8969,13 +9087,13 @@ static sxi32 VmExecExceptionBlock(ph7_vm *pVm, ph7_exception *pException, for(i = 0; i < nSelf; i++) { if(SySetPut(&sSelf, SySetAt(&pVm->aSelf, i)) != SXRET_OK) { SySetRelease(&sSelf); - return SXERR_MEM; + PH7_VmMemoryError(pVm); + return PH7_ABORT; } } 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. + /* Remove exited names 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) { @@ -9001,22 +9119,35 @@ static sxi32 VmExecExceptionBlock(ph7_vm *pVm, ph7_exception *pException, } else { pFrame->iFlags |= VM_FRAME_FINALLY; } - rc = VmLocalExec(pVm, pCatch ? &pCatch->sByteCode : &pException->sFinally, 0); + SySet *pCode = pCatch ? &pCatch->sByteCode : &pException->sFinally; + ph7_value *pStack = VmNewOperandStack(pVm, SySetUsed(pCode)); + if(pStack == 0) { + PH7_VmMemoryError(pVm); + rc = PH7_ABORT; + } else { + /* Calls made by a handler must propagate a throw back here before + * invoking an uncaught handler: the pending finally still has priority. */ + ph7_exception *pBoundary = 0; + if(SySetPut(&pVm->aException, &pBoundary) != SXRET_OK) { + PH7_VmMemoryError(pVm); + } + rc = VmByteCodeExec(pVm, (VmInstr *)SySetBasePtr(pCode), pStack, -1, + pResult, 0, VM_EXEC_HANDLER); + SySetPop(&pVm->aException); + SyMemBackendFree(&pVm->sAllocator, pStack); + } while(pVm->pFrame != pOwner) { VmLeaveFrame(pVm); } + } else { + PH7_VmMemoryError(pVm); + rc = PH7_ABORT; } pVm->pFrame = pSavedFrame; SySetRelease(&pVm->aSelf); pVm->aSelf = sSavedSelf; return rc; } -static sxi32 VmExecFinallyBlock( - ph7_vm *pVm, /* Target VM */ - ph7_exception *pException /* Exception thrown */ -) { - return VmExecExceptionBlock(pVm, pException, 0, 0); -} /* * The following routine is invoked by the engine when an uncaught * exception is triggered. @@ -9077,83 +9208,24 @@ static sxi32 VmUncaughtException( return rc; } /* - * Throw an user exception. + * Find a catch in one region. Unwinding and completion selection belong to + * that region's executing bytecode, not to a global exception stack. */ -static sxi32 VmThrowException( - ph7_vm *pVm, /* Target VM */ - ph7_class_instance *pThis /* Exception class instance [i.e: Exception $e] */ +static ph7_exception_block *VmFindCatch( + ph7_vm *pVm, + ph7_exception *pException, + ph7_class_instance *pThis ) { - ph7_exception_block *pCatch; /* Catch block to execute */ - ph7_exception **apException; - ph7_exception *pException; - sxi32 rcc, rcf; - /* Point to the stack of loaded exceptions */ - apException = (ph7_exception **)SySetBasePtr(&pVm->aException); - pException = 0; - pCatch = 0; - rcc = SXRET_OK; - rcf = SXRET_OK; - if(SySetUsed(&pVm->aException) > 0) { - ph7_exception_block *aCatch; - ph7_class *pClass; - sxu32 j; - /* Locate the appropriate block to execute */ - pException = apException[SySetUsed(&pVm->aException) - 1]; - (void)SySetPop(&pVm->aException); - aCatch = (ph7_exception_block *)SySetBasePtr(&pException->sEntry); - for(j = 0 ; j < SySetUsed(&pException->sEntry) ; ++j) { - SyString *pName = &aCatch[j].sClass; - /* Extract the target class */ - pClass = PH7_VmExtractClass(&(*pVm), pName->zString, pName->nByte, TRUE); - if(pClass == 0) { - /* No such class */ - continue; - } - if(VmInstanceOf(pVm, pThis->pClass, pClass)) { - /* Catch block found,break immediately */ - pCatch = &aCatch[j]; - break; - } + ph7_exception_block *aCatch = (ph7_exception_block *)SySetBasePtr(&pException->sEntry); + sxu32 j; + for(j = 0; j < SySetUsed(&pException->sEntry); j++) { + SyString *pName = &aCatch[j].sClass; + ph7_class *pClass = PH7_VmExtractClass(pVm, pName->zString, pName->nByte, TRUE); + if(pClass && VmInstanceOf(pVm, pThis->pClass, pClass)) { + return &aCatch[j]; } } - /* Execute the 'catch' block if available */ - if(pCatch) { - VmFrame *pFrame = pVm->pFrame; - while(pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION)) { - /* Safely ignore the exception frame */ - pFrame = pFrame->pParent; - } - if(pException->pFrame == pFrame) { - /* Tell the upper layer that the exception was caught */ - pFrame->iFlags &= ~VM_FRAME_THROW; - } - rcc = VmExecExceptionBlock(pVm, pException, pCatch, pThis); - } - /* Execute the 'finally' block if available */ - if(pException && SySetUsed(&pException->sFinally)) { - rcf = VmExecFinallyBlock(&(*pVm), pException); - /* Release the bytecode container */ - SySetRelease(&pException->sFinally); - } - /* No matching 'catch' block found */ - if(pCatch == 0) { - rcc = VmUncaughtException(&(*pVm), pThis); - if(rcc == SXRET_OK && pException) { - VmFrame *pFrame = pVm->pFrame; - while(pFrame->pParent && (pFrame->iFlags & VM_FRAME_EXCEPTION)) { - /* Safely ignore the exception frame */ - pFrame = pFrame->pParent; - } - if(pException->pFrame == pFrame) { - /* Tell the upper layer that the exception was caught */ - pFrame->iFlags &= ~VM_FRAME_THROW; - } - } - } - /* TICKET 1433-60: Do not release the 'pException' pointer since it may - * be used again if a 'goto' statement is executed. - */ - return rcc | rcf; + return 0; } /* * Section: @@ -9909,12 +9981,15 @@ static sxi32 VmEvalChunk( rc = VmByteCodeExec(pVm, (VmInstr *)SySetBasePtr(&aByteCode), pStack, -1, &sResult, 0, VM_EXEC_INCLUDE); SyMemBackendFree(&pVm->sAllocator, pStack); - if((rc == VM_INCLUDE_RETURN || rc == VM_INCLUDE_JUMP) && pResult) { + if((rc == VM_INCLUDE_RETURN || rc == VM_RETURN_VOID || rc == VM_INCLUDE_JUMP || rc == VM_INCLUDE_EXCEPTION) && pResult) { PH7_MemObjStore(&sResult, pResult); } } } else { rc = VmLocalExec(pVm, &aByteCode, &sResult); + if(rc == PH7_EXCEPTION && pResult) { + PH7_MemObjStore(&sResult, pResult); + } } if(pCtx) { /* Set the execution result */ diff --git a/include/ph7int.h b/include/ph7int.h index 57eeaeb..a402e9e 100644 --- a/include/ph7int.h +++ b/include/ph7int.h @@ -1686,7 +1686,7 @@ PH7_PRIVATE sxi32 PH7_VmInitFuncState(ph7_vm *pVm, ph7_vm_func *pFunc, const cha sxi32 iFlags, void *pUserData); PH7_PRIVATE sxi32 PH7_VmInstallUserFunction(ph7_vm *pVm, ph7_vm_func *pFunc, SyString *pName); PH7_PRIVATE sxi32 PH7_VmSameSignature(ph7_vm_func *pFirst, ph7_vm_func *pSecond); -PH7_PRIVATE sxi32 PH7_VmCreateClassInstanceFrame(ph7_vm *pVm, ph7_class_instance *pObj); +PH7_PRIVATE sxi32 PH7_VmCreateClassInstanceFrame(ph7_vm *pVm, ph7_class_instance *pObj, ph7_value *pResult); PH7_PRIVATE sxi32 PH7_VmRefObjRemove(ph7_vm *pVm, sxu32 nIdx, SyHashEntry *pEntry, ph7_hashmap_node *pMapEntry); PH7_PRIVATE sxi32 PH7_VmRefObjInstall(ph7_vm *pVm, sxu32 nIdx, SyHashEntry *pEntry, ph7_hashmap_node *pMapEntry, sxi32 iFlags); PH7_PRIVATE sxi32 PH7_VmPushFilePath(ph7_vm *pVm, const char *zPath, int nLen, sxu8 bMain, sxi32 *pNew); @@ -1803,7 +1803,10 @@ PH7_PRIVATE sxi32 PH7_ClassCollectInterfaces(ph7_vm *pVm, ph7_class *pClass, SyS 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); +PH7_PRIVATE sxi32 PH7_NewClassInstanceWithResult(ph7_vm *pVm, ph7_class *pClass, ph7_class_instance **ppNew, ph7_value *pResult); +PH7_PRIVATE void PH7_ClassInstanceAbort(ph7_class_instance *pThis); PH7_PRIVATE ph7_class_instance *PH7_CloneClassInstance(ph7_class_instance *pSrc); +PH7_PRIVATE sxi32 PH7_CloneClassInstanceWithResult(ph7_class_instance *pSrc, ph7_class_instance **ppClone, ph7_value *pResult); PH7_PRIVATE sxi32 PH7_ClassInstanceCmp(ph7_class_instance *pLeft, ph7_class_instance *pRight, int bStrict, int iNest); PH7_PRIVATE void PH7_ClassInstanceUnref(ph7_class_instance *pThis); PH7_PRIVATE sxi32 PH7_ClassInstanceFinalize(ph7_class_instance *pThis);