diff --git a/engine/compiler.c b/engine/compiler.c index 4d43c02..248cecf 100644 --- a/engine/compiler.c +++ b/engine/compiler.c @@ -4998,7 +4998,6 @@ static const LangConstruct aLangConstruct[] = { { PH7_KEYWORD_GOTO, PH7_CompileGoto }, /* goto statement */ { PH7_KEYWORD_CONST, PH7_CompileConstant }, /* const statement */ { PH7_KEYWORD_INCLUDE, PH7_CompileInclude }, /* include statement */ - { PH7_KEYWORD_REQUIRE, PH7_CompileInclude }, /* require statement */ }; /* * Return a pointer to the global scope handler routine associated diff --git a/engine/vfs.c b/engine/vfs.c index 9610504..2d61690 100644 --- a/engine/vfs.c +++ b/engine/vfs.c @@ -3024,7 +3024,11 @@ PH7_PRIVATE void *PH7_StreamOpenHandle(ph7_vm *pVm, const ph7_io_stream *pStream if(rc == PH7_OK) { if(bPushInclude) { /* Mark as included */ - PH7_VmPushFilePath(pVm, sFilePath, -1, FALSE, pNew); + rc = PH7_VmPushFilePath(pVm, sFilePath, -1, FALSE, pNew); + if(rc != SXRET_OK) { + PH7_StreamCloseHandle(pStream, pHandle); + return 0; + } } } } else { diff --git a/engine/vm.c b/engine/vm.c index f169ac4..d7be5b3 100644 --- a/engine/vm.c +++ b/engine/vm.c @@ -1507,6 +1507,10 @@ PH7_PRIVATE sxi32 PH7_VmInit( SySetInit(&pVm->aFiles, &pVm->sAllocator, sizeof(SyString)); SySetInit(&pVm->aPaths, &pVm->sAllocator, sizeof(SyString)); SySetInit(&pVm->aIncluded, &pVm->sAllocator, sizeof(SyString)); + rc = SyHashInit(&pVm->hRequired, &pVm->sAllocator, SyStrHash, (int (*)(const void *, const void *, sxu32))SyMemcmp); + if(rc != SXRET_OK) { + goto Err; + } SySetInit(&pVm->aOB, &pVm->sAllocator, sizeof(VmObEntry)); SySetInit(&pVm->aIOstream, &pVm->sAllocator, sizeof(ph7_io_stream *)); /* Error callbacks containers */ @@ -2361,6 +2365,8 @@ PH7_PRIVATE sxi32 PH7_VmThrowError( sxu32 nLine; SyString sFileName; SyString *pFile; + /* Included global code has no execution trace to populate this set. */ + SySetInit(&pDebug, &pVm->sAllocator, sizeof(VmDebugTrace)); if((pVm->nMagic == PH7_VM_EXEC) && (VmExtractDebugTrace(&(*pVm), &pDebug) == SXRET_OK) && (SySetUsed(&pDebug) > 0)) { /* Extract file name and line number from debug trace */ SySetGetNextEntry(&pDebug, (void **)&pTrace); @@ -9329,11 +9335,17 @@ static sxi32 VmEvalChunk( int iFlags /* Code evaluation flag */ ) { SySet *pByteCode, aByteCode; + sxi32 rc = SXRET_OK; + sxi32 bRequire = (iFlags & (PH7_AERSCRIPT_CODE | PH7_AERSCRIPT_FILE)) == + (PH7_AERSCRIPT_CODE | PH7_AERSCRIPT_FILE); ProcConsumer xErr = 0; void *pErrData = 0; /* Initialize bytecode container */ SySetInit(&aByteCode, &pVm->sAllocator, sizeof(VmInstr)); - SySetAlloc(&aByteCode, 0x20); + rc = SySetAlloc(&aByteCode, 0x20); + if(rc != SXRET_OK && bRequire) { + return rc; + } /* Log compile-time errors */ xErr = pVm->pEngine->xConf.xErr; pErrData = pVm->pEngine->xConf.pErrData; @@ -9346,13 +9358,17 @@ static sxi32 VmEvalChunk( PH7_VmPushFilePath(pVm, "[MEMORY]", -1, TRUE, 0); } /* Compile the chunk */ - PH7_CompileAerScript(pVm, pChunk, iFlags); + rc = PH7_CompileAerScript(pVm, pChunk, iFlags); + if(rc != SXRET_OK && bRequire) { + goto Cleanup; + } ph7_value sResult; /* Return value */ SyHashEntry *pEntry; /* Initialize and install static and constants class attributes */ SyHashResetLoopCursor(&pVm->hClass); while((pEntry = SyHashGetNextEntry(&pVm->hClass)) != 0) { - if(VmMountUserClass(&(*pVm), (ph7_class *)pEntry->pUserData) != SXRET_OK) { + rc = VmMountUserClass(&(*pVm), (ph7_class *)pEntry->pUserData); + if(rc != SXRET_OK) { if(pCtx) { ph7_result_bool(pCtx, 0); } @@ -9360,6 +9376,7 @@ static sxi32 VmEvalChunk( } } if(SXRET_OK != PH7_VmEmitInstr(pVm, 0, PH7_OP_DONE, 0, 0, 0, 0)) { + rc = SXERR_MEM; /* Out of memory */ if(pCtx) { ph7_result_bool(pCtx, 0); @@ -9369,7 +9386,7 @@ static sxi32 VmEvalChunk( /* Assume a boolean true return value */ PH7_MemObjInitFromBool(pVm, &sResult, 1); /* Execute the compiled chunk */ - VmLocalExec(pVm, &aByteCode, &sResult); + rc = VmLocalExec(pVm, &aByteCode, &sResult); if(pCtx) { /* Set the execution result */ ph7_result_value(pCtx, &sResult); @@ -9379,7 +9396,7 @@ Cleanup: /* Cleanup the mess left behind */ pVm->pByteContainer = pByteCode; SySetRelease(&aByteCode); - return SXRET_OK; + return bRequire ? rc : SXRET_OK; } /* * value eval(string $code) @@ -9477,7 +9494,14 @@ PH7_PRIVATE sxi32 PH7_VmPushFilePath(ph7_vm *pVm, const char *zPath, int nLen, s *pNew = 1; } } - SySetPut(&pVm->aFiles, (const void *)&sPath); + rc = SySetPut(&pVm->aFiles, (const void *)&sPath); + if(rc != SXRET_OK) { + if(!bMain && *pNew) { + SySetPop(&pVm->aIncluded); + } + SyMemBackendFree(&pVm->sAllocator, zDup); + return rc; + } return SXRET_OK; } /* @@ -9487,8 +9511,8 @@ PH7_PRIVATE sxi32 PH7_VmPushFilePath(ph7_vm *pVm, const char *zPath, int nLen, s * Note that the PHP script to evaluate can be a local or remote file.In * either cases the [PH7_StreamReadWholeFile()] function handle all the underlying * operations. - * Refer to the implementation of the include(),include_once() language - * constructs for more information. + * Include evaluates a method chunk; require evaluates global code once per VM, + * independently of the include history. */ static sxi32 VmExecIncludedFile( ph7_vm *pVm, /* Underlying Virtual Machine */ @@ -9500,6 +9524,8 @@ static sxi32 VmExecIncludedFile( SyBlob sContents; void *pHandle; int isNew; + SyString sRequired; + SyHashEntry *pRequired = 0; /* Initialize fields */ SyBlobInit(&sContents, &pVm->sAllocator); isNew = 0; @@ -9514,21 +9540,44 @@ static sxi32 VmExecIncludedFile( return SXERR_IO; } rc = SXRET_OK; /* Stupid cc warning */ - if(iFlags & PH7_AERSCRIPT_CODE && !isNew) { - /* Already included (required) */ - rc = SXERR_EXISTS; - } else { - /* Read the whole file contents */ - rc = PH7_StreamReadWholeFile(pHandle, pStream, &sContents); + if(iFlags & PH7_AERSCRIPT_CODE) { + /* The loader owns these bytes until VM release. Do not use the spelling + * from the statement, or the independent include history, as the key. */ + sRequired = *(SyString *)SySetPeek(&pVm->aFiles); + pRequired = SyHashGet(&pVm->hRequired, sRequired.zString, sRequired.nByte); + if(pRequired) { + PH7_VmThrowError(pVm, PH7_CTX_WARNING, pRequired->pUserData ? + "File is already being required: '%z'" : "File already required: '%z'", &sRequired); + rc = SXERR_EXISTS; + goto Cleanup; + } + /* Publish before compilation/execution so recursive require cannot + * redeclare the same file. Failed evaluations are not remembered. */ + rc = SyHashInsert(&pVm->hRequired, sRequired.zString, sRequired.nByte, pVm); + if(rc != SXRET_OK) { + goto Cleanup; + } + pRequired = SyHashGet(&pVm->hRequired, sRequired.zString, sRequired.nByte); + } + /* Read the whole file contents */ + rc = PH7_StreamReadWholeFile(pHandle, pStream, &sContents); + if(rc == SXRET_OK) { + SyString sScript; + sxu32 nMagic = pVm->nMagic; + /* Compile and execute the script */ + SyStringInitFromBuf(&sScript, SyBlobData(&sContents), SyBlobLength(&sContents)); + pVm->nMagic = PH7_VM_INCL; + rc = VmEvalChunk(pVm, 0, &sScript, iFlags); + pVm->nMagic = (iFlags & PH7_AERSCRIPT_CODE) ? nMagic : PH7_VM_EXEC; + } + if(pRequired) { if(rc == SXRET_OK) { - SyString sScript; - /* Compile and execute the script */ - SyStringInitFromBuf(&sScript, SyBlobData(&sContents), SyBlobLength(&sContents)); - pVm->nMagic = PH7_VM_INCL; - VmEvalChunk(pVm, 0, &sScript, iFlags); - pVm->nMagic = PH7_VM_EXEC; + pRequired->pUserData = 0; + } else { + SyHashDeleteEntry(&pVm->hRequired, sRequired.zString, sRequired.nByte, 0); } } +Cleanup: /* Close the handle */ PH7_StreamCloseHandle(pStream, pHandle); /* Release the working buffer */ diff --git a/include/ph7int.h b/include/ph7int.h index 7c9e4fd..d1426a0 100644 --- a/include/ph7int.h +++ b/include/ph7int.h @@ -1208,6 +1208,7 @@ struct ph7_vm { SySet aFiles; /* Stack of processed files */ SySet aPaths; /* Set of import paths */ SySet aIncluded; /* Set of included files */ + SyHash hRequired; /* Resolved require paths, including pending evaluations */ SySet aOB; /* Stackable output buffers */ SySet aAutoLoad; /* Stack of class autoload callbacks */ SySet aShutdown; /* Stack of shutdown user callbacks */ diff --git a/tests/include_statements.aer b/tests/include_statements.aer index 480099e..abf8883 100644 --- a/tests/include_statements.aer +++ b/tests/include_statements.aer @@ -1,10 +1,10 @@ require 'data/includes/base_class.aer'; +require 'data/includes/test_class.aer'; class Program extends BaseClass { void main() { $this->run(); - require 'data/includes/test_class.aer'; object $test = new Test(); var_dump($test); }