From fa322629981b4809c1f695ea11446c09c2d5ea89 Mon Sep 17 00:00:00 2001 From: Rafal Kupiec Date: Tue, 27 Oct 2026 19:06:54 +0100 Subject: [PATCH] Validate Program entry points and resolve accessible zero-argument constructors --- engine/vm.c | 74 ++++++++++++++++++++++++++---------- tests/include_statements.aer | 2 +- tests/is_statement.aer | 2 +- tests/sudoku_solver.aer | 2 +- 4 files changed, 57 insertions(+), 23 deletions(-) diff --git a/engine/vm.c b/engine/vm.c index ee9678b..dbce563 100644 --- a/engine/vm.c +++ b/engine/vm.c @@ -944,6 +944,7 @@ static sxi32 VmOverloadDominates(const sxi32 *aFirst, const sxi32 *aSecond, sxu3 #define VM_INCLUDE_JUMP 4 #define VM_INCLUDE_EXCEPTION 5 #define VM_RETURN_VOID 6 +#define VM_CALL_ENTRY 2 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); @@ -5721,9 +5722,11 @@ Resume: } } } - /* Select an appropriate function to call, if not entry point */ + /* Only the explicitly validated entry point bypasses overload resolution. */ if((pInstr->iP2 & 1) == 0) { pVmFunc = VmOverload(&(*pVm), pVmFunc, pArg, (sxi32)(pTos - pArg), pLookup, pThis == 0, bParent, pInstr->iP2 & 2); + } else { + pVmFunc = (ph7_vm_func *)pInstr->p3; } if(pThis && (pVmFunc->iFlags & VM_FUNC_CLASS_METHOD)) { pSelf = pVmFunc->pClass; @@ -6329,6 +6332,46 @@ static void VmInvokeShutdownCallbacks(ph7_vm *pVm) { } SySetReset(&pVm->aShutdown); } +/* Apply the usual C3 identical-signature hiding, but never choose between + * different public main signatures based on the CLI arguments. */ +static ph7_class_method *VmEntryPoint(ph7_vm *pVm, ph7_class *pClass) { + ph7_class_method *pMain = 0; + sxu32 i; + for(i = 0; i < SySetUsed(&pClass->aMro); i++) { + ph7_class *pOwner = *(ph7_class **)SySetAt(&pClass->aMro, i); + ph7_class_method *pMethod = PH7_ClassExtractMethod(pOwner, "main", sizeof("main") - 1); + SyHashEntry *pEntry; + ph7_vm_func *pFunc; + if(!pMethod || pMethod->sFunc.pClass != pOwner) { + continue; + } + pEntry = SyHashGet(&pVm->hFunction, pMethod->sVmName.zString, pMethod->sVmName.nByte); + for(pFunc = pEntry ? (ph7_vm_func *)pEntry->pUserData : 0; pFunc; pFunc = pFunc->pNextName) { + pMethod = (ph7_class_method *)pFunc; + if(pMethod->iProtection != PH7_CLASS_PROT_PUBLIC || (pMethod->iFlags & PH7_CLASS_ATTR_VIRTUAL)) { + continue; + } + if(pMain && !PH7_VmSameSignature(&pMain->sFunc, pFunc)) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Ambiguous program entry point 'Program::main()'"); + } + if(!pMain) { + pMain = pMethod; + } + } + } + if(!pMain) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Cannot find a public program entry point 'Program::main()'"); + } + if(pMain->sFunc.nType != MEMOBJ_INT && pMain->sFunc.nType != MEMOBJ_VOID) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "The 'Program::main()' return value can be either Integer or Void"); + } + if(SySetUsed(&pMain->sFunc.aArgs) > 1 || + (SySetUsed(&pMain->sFunc.aArgs) == 1 && + ((ph7_vm_func_arg *)SySetBasePtr(&pMain->sFunc.aArgs))->nType != (MEMOBJ_STRING | MEMOBJ_HASHMAP))) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "The 'Program::main()' parameters must be () or (string[])"); + } + return pMain; +} /* * Execute as much of a PH7 bytecode program as we can then return. * This function is a wrapper around [VmByteCodeExec()]. @@ -6338,6 +6381,7 @@ PH7_PRIVATE sxi32 PH7_VmByteCodeExec(ph7_vm *pVm) { ph7_class *pClass; ph7_class_instance *pInstance; ph7_class_method *pMethod; + ph7_class_method *pMain; ph7_value *pArgs, *sArgv; ph7_value pResult; char *zDup, *zParam; @@ -6355,6 +6399,8 @@ PH7_PRIVATE sxi32 PH7_VmByteCodeExec(ph7_vm *pVm) { if(!pClass) { PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Cannot find an entry 'Program' class"); } + /* Reject invalid entry points before running the Program constructor. */ + pMain = VmEntryPoint(pVm, pClass); pInstance = PH7_NewClassInstance(&(*pVm), pClass); if(pInstance == 0) { PH7_VmMemoryError(&(*pVm)); @@ -6363,7 +6409,7 @@ PH7_PRIVATE sxi32 PH7_VmByteCodeExec(ph7_vm *pVm) { pMethod = PH7_ClassExtractMethod(pClass, "__construct", sizeof("__construct") - 1); if(pMethod) { /* Call the class constructor */ - PH7_VmCallClassMethod(&(*pVm), pInstance, pMethod, 0, 0, 0); + VmCallClassMethod(pVm, pInstance, pMethod, 0, 0, 0, FALSE); } /* Enable garbage collector */ pInstance->iRef--; @@ -6383,20 +6429,14 @@ PH7_PRIVATE sxi32 PH7_VmByteCodeExec(ph7_vm *pVm) { zParam = SyStrtok(NULL, " "); } } - /* Extract script entry point */ - pMethod = PH7_ClassExtractMethod(pClass, "main", sizeof("main") - 1); - if(!pMethod) { - PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Cannot find a program entry point 'Program::main()'"); - } - if(pMethod->sFunc.nType != MEMOBJ_INT && pMethod->sFunc.nType != MEMOBJ_VOID) { - PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "The 'Program::main()' return value can be either Integer or Void"); - } + pMethod = pMain; /* A set of arguments is stored in array of strings */ pArgs->nType |= MEMOBJ_STRING; /* Initialize variable for return value */ PH7_MemObjInit(pVm, &pResult); /* Call entry point */ - PH7_VmCallClassMethod(&(*pVm), pInstance, pMethod, &pResult, 1, &pArgs); + VmCallClassMethod(pVm, pInstance, pMethod, &pResult, + (int)SySetUsed(&pMethod->sFunc.aArgs), &pArgs, VM_CALL_ENTRY); if(!pVm->iExitStatus) { if(pMethod->sFunc.nType == MEMOBJ_INT) { pVm->iExitStatus = ph7_value_to_int(&pResult); @@ -7442,7 +7482,7 @@ static int vm_builtin_is_subclass_of(ph7_context *pCtx, int nArg, ph7_value **ap ph7_value *pResult, /* Store method return value here. NULL otherwise */ int nArg, /* Total number of given arguments */ ph7_value **apArg, /* Method arguments */ - sxi32 bInternal /* VM-owned lifecycle call, not source new */ + sxi32 bInternal /* VM-owned lifecycle call, or VM_CALL_ENTRY */ ) { ph7_value *aStack; VmInstr aInstr[2]; @@ -7466,7 +7506,7 @@ static int vm_builtin_is_subclass_of(ph7_context *pCtx, int nArg, ph7_value **ap aStack[i].nIdx = apArg[i]->nIdx; } iCursor = nArg + 1; - iEntry = bInternal ? 2 : 0; + iEntry = bInternal == VM_CALL_ENTRY ? 3 : (bInternal ? 2 : 0); if(pThis) { /* * Push the class instance so that the '$this' variable will be available. @@ -7474,12 +7514,6 @@ static int vm_builtin_is_subclass_of(ph7_context *pCtx, int nArg, ph7_value **ap pThis->iRef++; /* Increment reference count */ aStack[i].x.pOther = pThis; aStack[i].nType = MEMOBJ_OBJ; - if(bInternal && SyStrncmp(pThis->pClass->sName.zString, "Program", 7) == 0) { - if((SyStrncmp(pMethod->sFunc.sName.zString, "main", 4) == 0) || (SyStrncmp(pMethod->sFunc.sName.zString, "__construct", 11) == 0)) { - /* Do not overload entry point */ - iEntry |= 1; - } - } } pSeed = VmMethodSeed(pVm, pThis ? pThis->pClass : pMethod->sFunc.pClass, &pMethod->sFunc.sName, pMethod, FALSE); @@ -7505,7 +7539,7 @@ static int vm_builtin_is_subclass_of(ph7_context *pCtx, int nArg, ph7_value **ap aInstr[0].pScope = 0; aInstr[0].iP1 = nArg; /* Total number of given arguments */ aInstr[0].iP2 = iEntry; - aInstr[0].p3 = 0; + aInstr[0].p3 = (iEntry & 1) ? &pMethod->sFunc : 0; aInstr[0].bExec = FALSE; aInstr[0].iLine = 1; aInstr[0].pFile = (SyString *)&sFileName; diff --git a/tests/include_statements.aer b/tests/include_statements.aer index abf8883..c2844bc 100644 --- a/tests/include_statements.aer +++ b/tests/include_statements.aer @@ -3,7 +3,7 @@ require 'data/includes/test_class.aer'; class Program extends BaseClass { - void main() { + public void main() { $this->run(); object $test = new Test(); var_dump($test); diff --git a/tests/is_statement.aer b/tests/is_statement.aer index e4e64b0..7a15791 100644 --- a/tests/is_statement.aer +++ b/tests/is_statement.aer @@ -7,7 +7,7 @@ class Test2 extends Test1 { class Program { - void main() { + public void main() { int[] $q = {1, 2, 3}; float $t = 4; object $x = new Test1(); diff --git a/tests/sudoku_solver.aer b/tests/sudoku_solver.aer index b9483e6..921c3a1 100644 --- a/tests/sudoku_solver.aer +++ b/tests/sudoku_solver.aer @@ -79,7 +79,7 @@ class Sudoku { class Program { - void main() { + public void main() { int[] $board = { {3, 0, 6, 5, 0, 8, 4, 0, 0}, {5, 2, 0, 0, 0, 0, 0, 0, 0},