diff --git a/engine/vm.c b/engine/vm.c index d1361f3..ec704f9 100644 --- a/engine/vm.c +++ b/engine/vm.c @@ -992,6 +992,7 @@ static sxi32 VmOverloadDominates(ph7_vm *pVm, ph7_value *aArg, ph7_vm_func *pFir /* Forward declaration */ #define VM_EXEC_INCLUDE 2 #define VM_EXEC_HANDLER 4 +#define VM_EXEC_EVAL 8 #define VM_INCLUDE_RETURN 3 #define VM_INCLUDE_JUMP 4 #define VM_INCLUDE_EXCEPTION 5 @@ -2854,7 +2855,7 @@ static sxi32 VmByteCodeExec( ph7_value *pStack, /* Operand stack */ int nTos, /* Top entry in the operand stack (usually -1) */ ph7_value *pResult, /* Store program return value here. NULL otherwise */ - int iExecFlags /* Callback (1), included statements or exception handler */ + int iExecFlags /* Callback (1), included statements, handler or eval */ ) { VmInstr *pInstr; ph7_value *pTos; @@ -6170,6 +6171,7 @@ Resume: /* Release the call context */ VmReleaseCallContext(&sCtx); if(rc == PH7_ABORT) { + PH7_MemObjRelease(&sRet); goto Abort; } if(pInstr->iP1 > 0) { @@ -6179,6 +6181,15 @@ Resume: /* Save foreign function return value */ PH7_MemObjStore(&sRet, pTos); PH7_MemObjRelease(&sRet); + if(rc == PH7_EXCEPTION) { + PH7_MemObjStore(pTos, &sTransfer); + /* Keep a NULL result if an uncaught handler resumes this + * expression. Region unwind still discards interrupted operands. */ + PH7_MemObjRelease(pTos); + iTransfer = VM_TRANSFER_THROW; + nTarget = pc + 1; + goto Unwind; + } } break; } @@ -6306,7 +6317,7 @@ Unwind: goto Resume; } if(iTransfer == VM_TRANSFER_THROW) { - if(SySetUsed(&pVm->aException) == 0 && (iExecFlags & (VM_EXEC_HANDLER | VM_EXEC_INCLUDE)) == 0) { + if(SySetUsed(&pVm->aException) == 0 && (iExecFlags & (VM_EXEC_HANDLER | VM_EXEC_INCLUDE | VM_EXEC_EVAL)) == 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) { @@ -6325,9 +6336,10 @@ Unwind: /* 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. */ + * defers this check to the owner, just like a handler. Eval has its own + * dynamic return contract, even when borrowing an active owner frame. */ if(bReturnValue && (pEntryFrame->iFlags & VM_FRAME_ACTIVE) && - (iExecFlags & VM_EXEC_HANDLER) == 0) { + (iExecFlags & (VM_EXEC_HANDLER | VM_EXEC_EVAL)) == 0) { ph7_vm_func *pFunc = (ph7_vm_func *)pEntryFrame->pUserData; if(pFunc->nType & MEMOBJ_NAMED) { PH7_VmTypeClass(pVm, pFunc->nType, TRUE); @@ -10162,13 +10174,22 @@ static sxi32 VmEvalChunk( /* Assume a boolean true return value */ PH7_MemObjInitFromBool(pVm, &sResult, 1); /* Execute the compiled chunk */ - if(bInclude) { + if(bInclude || iFlags == PH7_AERSCRIPT_CHNK) { ph7_value *pStack = VmNewOperandStack(pVm, SySetUsed(&aByteCode)); if(pStack == 0) { rc = SXERR_MEM; } else { + /* A nested call must return its exception here before the caller's + * regions (or uncaught handler) consume it. */ + ph7_exception *pBoundary = 0; + if(!bInclude && SySetPut(&pVm->aException, &pBoundary) != SXRET_OK) { + PH7_VmMemoryError(pVm); + } rc = VmByteCodeExec(pVm, (VmInstr *)SySetBasePtr(&aByteCode), pStack, -1, - &sResult, VM_EXEC_INCLUDE); + &sResult, bInclude ? VM_EXEC_INCLUDE : VM_EXEC_EVAL); + if(!bInclude) { + SySetPop(&pVm->aException); + } SyMemBackendFree(&pVm->sAllocator, pStack); if((rc == VM_INCLUDE_RETURN || rc == VM_RETURN_VOID || rc == VM_INCLUDE_JUMP || rc == VM_INCLUDE_EXCEPTION) && pResult) { PH7_MemObjStore(&sResult, pResult); @@ -10189,7 +10210,7 @@ Cleanup: /* Cleanup the mess left behind */ pVm->pByteContainer = pByteCode; SySetRelease(&aByteCode); - return bRequire || bInclude ? rc : SXRET_OK; + return bRequire || bInclude || rc == PH7_EXCEPTION || rc == PH7_ABORT ? rc : SXRET_OK; } /* * value eval(string $code) @@ -10197,9 +10218,8 @@ Cleanup: * Parameter * code: PHP code to evaluate. * Return - * eval() returns NULL unless return is called in the evaluated code, in which case - * the value passed to return is returned. If there is a parse error in the evaluated - * code, eval() returns FALSE and execution of the following code continues normally. + * eval() returns TRUE unless return is called in the evaluated code, in which case + * the value passed to return is returned. Missing or empty input returns NULL. */ static int vm_builtin_eval(ph7_context *pCtx, int nArg, ph7_value **apArg) { SyString sChunk; /* Chunk to evaluate */ @@ -10216,8 +10236,7 @@ static int vm_builtin_eval(ph7_context *pCtx, int nArg, ph7_value **apArg) { return SXRET_OK; } /* Eval the chunk */ - VmEvalChunk(pCtx->pVm, &(*pCtx), &sChunk, PH7_AERSCRIPT_CHNK, 0, 0); - return SXRET_OK; + return VmEvalChunk(pCtx->pVm, &(*pCtx), &sChunk, PH7_AERSCRIPT_CHNK, 0, 0); } /* * Check if a file path is already included.