From b513add46b51d607e9b15817f3cdd6e587918a52 Mon Sep 17 00:00:00 2001 From: Rafal Kupiec Date: Sat, 24 Oct 2026 22:51:09 +0200 Subject: [PATCH] Handle module import failures and duplicates safely and add module_loaded --- engine/vm.c | 100 ++++++++++++++++++++++++++++++++++++----------- include/ph7int.h | 1 + 2 files changed, 78 insertions(+), 23 deletions(-) diff --git a/engine/vm.c b/engine/vm.c index 707b814..f169ac4 100644 --- a/engine/vm.c +++ b/engine/vm.c @@ -1503,6 +1503,7 @@ PH7_PRIVATE sxi32 PH7_VmInit( SySetInit(&pVm->aException, &pVm->sAllocator, sizeof(ph7_exception *)); /* Configuration containers */ SySetInit(&pVm->aModules, &pVm->sAllocator, sizeof(VmModule)); + SySetInit(&pVm->aModuleHandles, &pVm->sAllocator, sizeof(VmModule)); SySetInit(&pVm->aFiles, &pVm->sAllocator, sizeof(SyString)); SySetInit(&pVm->aPaths, &pVm->sAllocator, sizeof(SyString)); SySetInit(&pVm->aIncluded, &pVm->sAllocator, sizeof(SyString)); @@ -1717,7 +1718,7 @@ PH7_PRIVATE sxi32 PH7_VmReset(ph7_vm *pVm) { PH7_PRIVATE sxi32 PH7_VmRelease(ph7_vm *pVm) { VmModule *pEntry; /* Iterate through modules list */ - while(SySetGetNextEntry(&pVm->aModules, (void **)&pEntry) == SXRET_OK) { + while(SySetGetNextEntry(&pVm->aModuleHandles, (void **)&pEntry) == SXRET_OK) { /* Unload the module */ #ifdef __WINNT__ FreeLibrary(pEntry->pHandle); @@ -1727,6 +1728,7 @@ PH7_PRIVATE sxi32 PH7_VmRelease(ph7_vm *pVm) { } /* Free up the heap */ SySetRelease(&pVm->aModules); + SySetRelease(&pVm->aModuleHandles); /* Set the stale magic number */ pVm->nMagic = PH7_VM_STALE; /* Release the private memory subsystem */ @@ -2409,6 +2411,18 @@ PH7_PRIVATE sxi32 PH7_VmThrowError( } return rc; } +/* Exact module-name lookup; do not disturb the set's iteration cursor. */ +static sxi32 VmModuleLoaded(ph7_vm *pVm, const char *zName, sxu32 nLen) { + sxu32 i; + VmModule *pModule; + for(i = 0; i < SySetUsed(&pVm->aModules); ++i) { + pModule = (VmModule *)SySetAt(&pVm->aModules, i); + if(pModule->sName.nByte == nLen && SyStrncmp(pModule->sName.zString, zName, nLen) == 0) { + return TRUE; + } + } + return FALSE; +} /* * Execute as much of a PH7 bytecode program as we can then return. * @@ -4473,51 +4487,78 @@ static sxi32 VmByteCodeExec( */ case PH7_OP_IMPORT: { - VmModule pModule, *pSearch; + VmModule pModule; + char bfile[255]; + char *zFile; + int nFile; + sxu32 nSize; + sxi32 (*init)(ph7_vm *, ph7_real *, SyString *); char *zModule = (char *) pInstr->p3; int nLen = SyStrlen(zModule); if(nLen < 1) { break; } - while(SySetGetNextEntry(&pVm->aModules, (void **)&pSearch) == SXRET_OK) { - if(SyStrncmp(pSearch->sName.zString, zModule, (sxu32)(SXMAX((int) pSearch->sName.nByte, nLen))) == 0) { - SySetResetCursor(&pVm->aModules); - break; - } + if(VmModuleLoaded(pVm, zModule, (sxu32)nLen)) { + PH7_VmThrowError(pVm, PH7_CTX_WARNING, "ImportError: Module '%s' already loaded", zModule); + break; } - SySetResetCursor(&pVm->aModules); /* Zero the module entry */ SyZero(&pModule, sizeof(VmModule)); SyStringInitFromBuf(&pModule.sName, zModule, nLen); - char bfile[255] = {0}; - char *file; - snprintf(bfile, sizeof(bfile) - 1, "./binary/%s%s", zModule, PH7_LIBRARY_SUFFIX); - file = bfile; - SyStringInitFromBuf(&pModule.sFile, file, nLen); + nFile = snprintf(bfile, sizeof(bfile), "./binary/%s%s", zModule, PH7_LIBRARY_SUFFIX); + if(nFile < 0 || nFile >= (int)sizeof(bfile)) { + PH7_VmThrowError(pVm, PH7_CTX_WARNING, "ImportError: Module path too long for '%s'", zModule); + break; + } + zFile = SyMemBackendStrDup(&pVm->sAllocator, bfile, (sxu32)nFile); + if(zFile == 0) { + PH7_VmThrowError(pVm, PH7_CTX_WARNING, "ImportError: Out of memory loading '%s'", zModule); + break; + } + SyStringInitFromBuf(&pModule.sFile, zFile, nFile); #ifdef __WINNT__ - pModule.pHandle = LoadLibrary(file); + pModule.pHandle = LoadLibrary(zFile); #else pModule.pHandle = dlopen(pModule.sFile.zString, RTLD_LAZY); #endif if(!pModule.pHandle) { /* Could not load the module library file */ - PH7_VmThrowError(pVm, PH7_CTX_ERR, "ImportError: No module named '%z' found", &pModule.sName); + PH7_VmThrowError(pVm, PH7_CTX_WARNING, "ImportError: No module named '%z' found", &pModule.sName); + SyMemBackendFree(&pVm->sAllocator, zFile); + break; } - void (*init)(ph7_vm *, ph7_real *, SyString *); #ifdef __WINNT__ *(void**)(&init) = GetProcAddress(pModule.pHandle, "initializeModule"); #else *(void**)(&init) = dlsym(pModule.pHandle, "initializeModule"); #endif - if(!init) { - /* Could not find the module entry point */ - PH7_VmThrowError(pVm, PH7_CTX_ERR, "ImportError: Method '%z::initializeModule()' not found", &pModule.sName); + nSize = pVm->aModuleHandles.nSize; + if(!init || SySetPut(&pVm->aModuleHandles, &pModule) != SXRET_OK) { + /* SySetPut may change capacity before allocation fails. */ + pVm->aModuleHandles.nSize = nSize; + /* No callbacks have been published yet; unloading is safe. */ + PH7_VmThrowError(pVm, PH7_CTX_WARNING, "ImportError: Cannot initialize module '%z'", &pModule.sName); +#ifdef __WINNT__ + FreeLibrary(pModule.pHandle); +#else + dlclose(pModule.pHandle); +#endif + SyMemBackendFree(&pVm->sAllocator, zFile); + break; + } + /* Init may publish callbacks even on failure. Retain its handle until + * VM release, but only successful imports enter the lookup registry. */ + if(init(pVm, &pModule.fVer, &pModule.sDesc) != SXRET_OK) { + PH7_VmThrowError(pVm, PH7_CTX_WARNING, "ImportError: Initialization of module '%z' failed", &pModule.sName); + } else { + nSize = pVm->aModules.nSize; + if(SySetPut(&pVm->aModules, &pModule) != SXRET_OK) { + /* Keep the lookup registry retryable after allocation failure. */ + pVm->aModules.nSize = nSize; + PH7_VmThrowError(pVm, PH7_CTX_WARNING, "ImportError: Cannot register module '%z'", &pModule.sName); + } } - /* Initialize the module */ - init(pVm, &pModule.fVer, &pModule.sDesc); - /* Put information about module on top of the modules stack */ - SySetPut(&pVm->aModules, (const void *)&pModule); break; } /* @@ -5825,6 +5866,18 @@ PH7_PRIVATE void PH7_VmExpandConstantValue(ph7_value *pVal, void *pUserData) { * Status: * Stable. */ +/* bool module_loaded(string $name): lookup only, without loading a library. */ +static int vm_builtin_module_loaded(ph7_context *pCtx, int nArg, ph7_value **apArg) { + const char *zName; + int nLen; + if(nArg < 1) { + ph7_result_bool(pCtx, FALSE); + return SXRET_OK; + } + zName = ph7_value_to_string(apArg[0], &nLen); + ph7_result_bool(pCtx, VmModuleLoaded(pCtx->pVm, zName, (sxu32)nLen)); + return SXRET_OK; +} /* * bool function_exists(string $name) * Return TRUE if the given function has been defined. @@ -10054,6 +10107,7 @@ static int vm_builtin_utf8_decode(ph7_context *pCtx, int nArg, ph7_value **apArg } /* Table of built-in VM functions. */ static const ph7_builtin_func aVmFunc[] = { + { "module_loaded", vm_builtin_module_loaded }, { "function_exists", vm_builtin_func_exists }, { "is_callable", vm_builtin_is_callable }, { "register_autoload_handler", vm_builtin_register_autoload_handler }, diff --git a/include/ph7int.h b/include/ph7int.h index 13a4837..7c9e4fd 100644 --- a/include/ph7int.h +++ b/include/ph7int.h @@ -1204,6 +1204,7 @@ struct ph7_vm { SyBlob sConsumer; /* Default VM consumer [i.e Redirect all VM output to this blob] */ SyBlob sArgv; /* $argv[] collector [refer to the [getopt()] implementation for more information] */ SySet aModules; /* Set of loaded modules */ + SySet aModuleHandles; /* Libraries retained after initialization attempts */ SySet aFiles; /* Stack of processed files */ SySet aPaths; /* Set of import paths */ SySet aIncluded; /* Set of included files */