diff --git a/engine/api.c b/engine/api.c index c09d185..797f86d 100644 --- a/engine/api.c +++ b/engine/api.c @@ -464,7 +464,10 @@ static sxi32 ProcessSourceFile( char *pFileDir, fFilePath[PATH_MAX + 1]; char pFilePath[PATH_MAX + 1]; /* Install local import path which is the current directory */ - ph7_vm_config(pVm, PH7_VM_CONFIG_IMPORT_PATH, "./"); + rc = ph7_vm_config(pVm, PH7_VM_CONFIG_IMPORT_PATH, "./"); + if(rc != PH7_OK) { + goto Release; + } if(zFilePath && SyRealPath(zFilePath, fFilePath) == PH7_OK) { rc = snprintf(pFilePath, sizeof(pFilePath) - 1, "%s", fFilePath); if(rc < 0) { @@ -474,7 +477,10 @@ static sxi32 ProcessSourceFile( pFileDir = (char *) PH7_ExtractDirName(fFilePath, SyStrlen(fFilePath), &iFileDir); pFileDir[iFileDir + 1] = '\0'; /* Install local import path which is directory containing entry script */ - ph7_vm_config(pVm, PH7_VM_CONFIG_IMPORT_PATH, pFileDir); + rc = ph7_vm_config(pVm, PH7_VM_CONFIG_IMPORT_PATH, pFileDir); + if(rc != PH7_OK) { + goto Release; + } /* Push processed file path */ PH7_VmPushFilePath(pVm, pFilePath, -1, TRUE, 0); } else { diff --git a/engine/compiler.c b/engine/compiler.c index c9e49ff..29ef859 100644 --- a/engine/compiler.c +++ b/engine/compiler.c @@ -3172,6 +3172,13 @@ static sxi32 PH7_GenStateCompileFunc( } rc = SXRET_OK; if((pFunc->iFlags & VM_FUNC_CLOSURE) == 0) { + SyHashEntry *pEntry = SyHashGet(&pGen->pVm->hFunction, pName->zString, pName->nByte); + ph7_vm_func *pOther = pEntry ? (ph7_vm_func *)pEntry->pUserData : 0; + for(; pOther; pOther = pOther->pNextName) { + if(PH7_VmSameSignature(pFunc, pOther)) { + PH7_GenCompileError(pGen, E_ERROR, nLine, "Duplicate function signature '%z()'", pName); + } + } /* Finally register the function */ rc = PH7_VmInstallUserFunction(pGen->pVm, pFunc, 0); } @@ -3474,6 +3481,17 @@ static sxi32 PH7_GenStateCompileClassMethod( } /* Point beyond method signature */ pGen->pIn = &pEnd[1]; + { + SyHashEntry *pEntry; + SyHashResetLoopCursor(&pClass->hMethod); + while((pEntry = SyHashGetNextEntry(&pClass->hMethod)) != 0) { + ph7_class_method *pOther = (ph7_class_method *)pEntry->pUserData; + if(pOther->sFunc.pClass == pClass && + PH7_VmSameSignature(&pMeth->sFunc, &pOther->sFunc)) { + PH7_GenCompileError(pGen, E_ERROR, nLine, "Duplicate method signature '%z::%z()'", &pClass->sName, pName); + } + } + } if(doBody) { if(pGen->pIn < pGen->pEnd && (pGen->pIn->nType & PH7_TK_OCB /* '{'*/) == 0) { PH7_GenCompileError(pGen, E_ERROR, pGen->pIn->nLine, diff --git a/engine/vm.c b/engine/vm.c index 58d17e6..a813eba 100644 --- a/engine/vm.c +++ b/engine/vm.c @@ -219,6 +219,25 @@ PH7_PRIVATE sxi32 PH7_VmInitFuncState( SyStringInitFromBuf(&pFunc->sName, zName, nByte); return SXRET_OK; } +/* Input identity deliberately excludes return types, defaults and modifiers. */ +PH7_PRIVATE sxi32 PH7_VmSameSignature(ph7_vm_func *pFirst, ph7_vm_func *pSecond) { + ph7_vm_func_arg *aFirst, *aSecond; + sxu32 i; + if(SyStringCmp(&pFirst->sName, &pSecond->sName, SyMemcmp) != 0 || + SySetUsed(&pFirst->aArgs) != SySetUsed(&pSecond->aArgs)) { + return FALSE; + } + aFirst = (ph7_vm_func_arg *)SySetBasePtr(&pFirst->aArgs); + aSecond = (ph7_vm_func_arg *)SySetBasePtr(&pSecond->aArgs); + for(i = 0; i < SySetUsed(&pFirst->aArgs); i++) { + if(aFirst[i].nType != aSecond[i].nType || + ((aFirst[i].iFlags ^ aSecond[i].iFlags) & VM_FUNC_ARG_BY_REF) || + (aFirst[i].nType == SXU32_HIGH && SyStringCmp(&aFirst[i].sClass, &aSecond[i].sClass, SyMemcmp) != 0)) { + return FALSE; + } + } + return TRUE; +} /* * Install a user defined function in the corresponding VM container. */ @@ -466,13 +485,13 @@ static int VmInstanceOf(ph7_vm *pVm, ph7_class *pThis, ph7_class *pClass); typedef struct VmOverloadClassWalk VmOverloadClassWalk; struct VmOverloadClassWalk { ph7_class *pClass; /* Class or interface */ - int nDepth; /* Inheritance distance from the starting class */ + sxi32 nDepth; /* Inheritance distance from the starting class */ }; /* * Enqueue a class or interface for the overload specificity walk unless it * has already been visited. */ -static void VmOverloadEnqueueClass(ph7_vm *pVm, SySet *pQueue, SySet *pVisited, ph7_class *pClass, int nDepth) { +static void VmOverloadEnqueueClass(ph7_vm *pVm, SySet *pQueue, SySet *pVisited, ph7_class *pClass, sxi32 nDepth) { ph7_class **apVisited; VmOverloadClassWalk sEntry; sxu32 i; @@ -497,13 +516,13 @@ static void VmOverloadEnqueueClass(ph7_vm *pVm, SySet *pQueue, SySet *pVisited, * class or implemented interface and -1 if no relation exists. The walk is * read-only. */ -static int VmOverloadClassDistance(ph7_vm *pVm, ph7_class *pClass, ph7_class *pHint) { +static sxi32 VmOverloadClassDistance(ph7_vm *pVm, ph7_class *pClass, ph7_class *pHint) { SySet sQueue, sVisited; VmOverloadClassWalk *pEntry; ph7_class **apInterface, *pCur; SyHashEntry *pHash; sxu32 i, n; - int nDepth, nResult = -1; + sxi32 nDepth, nResult = -1; if(pClass == pHint) { return 0; } @@ -540,7 +559,7 @@ static int VmOverloadClassDistance(ph7_vm *pVm, ph7_class *pClass, ph7_class *pH * parameter. NULL is compatible with every type, so the rank reflects how * specific the formal parameter itself is. */ -static int VmOverloadNullRank(ph7_vm *pVm, ph7_vm_func_arg *pFormal) { +static sxi32 VmOverloadNullRank(ph7_vm *pVm, ph7_vm_func_arg *pFormal) { sxu32 nFormalType = pFormal->nType; SyHashEntry *pEntry; if(nFormalType == SXU32_HIGH) { @@ -573,7 +592,7 @@ static int VmOverloadNullRank(ph7_vm *pVm, ph7_vm_func_arg *pFormal) { * specific the match. A negative value means that the value is not * compatible with the parameter. */ -static int VmOverloadScalarRank(ph7_value *pArg, sxu32 nArgType, sxu32 nFormalType) { +static sxi32 VmOverloadScalarRank(ph7_value *pArg, sxu32 nArgType, sxu32 nFormalType) { if(nArgType == nFormalType) { /* Exact type match */ return 0; @@ -618,12 +637,12 @@ static int VmOverloadScalarRank(ph7_value *pArg, sxu32 nArgType, sxu32 nFormalTy * are compatible and do not affect the rank, so an empty array or an array * made only of NULLs stays rank 1 unless the container type matches exactly. */ -static int VmOverloadArrayRank(ph7_vm *pVm, ph7_value *pArg, sxu32 nFormalElem) { +static sxi32 VmOverloadArrayRank(ph7_vm *pVm, ph7_value *pArg, sxu32 nFormalElem) { ph7_hashmap *pMap; ph7_hashmap_node *pNode; ph7_value sValue; sxu32 nArgElem, nCount; - int nRank, nCur; + sxi32 nRank, nCur; if(nFormalElem == 0) { /* Untyped array parameter accepts any value */ return 1; @@ -673,12 +692,12 @@ static int VmOverloadArrayRank(ph7_vm *pVm, ph7_value *pArg, sxu32 nFormalElem) * parameter. This routine is strictly read-only: it never casts, mutates or * evaluates anything. */ -static int VmOverloadArgRank(ph7_vm *pVm, ph7_value *pArg, ph7_vm_func_arg *pFormal) { +static sxi32 VmOverloadArgRank(ph7_vm *pVm, ph7_value *pArg, ph7_vm_func_arg *pFormal) { ph7_class_instance *pInstance; ph7_class *pHint; SyHashEntry *pEntry; sxu32 nArgType, nFormalType, nFormalElem; - int nDistance; + sxi32 nDistance; nArgType = pArg->nType; nFormalType = pFormal->nType; if((nArgType & MEMOBJ_HASHMAP) == 0 && (nArgType & MEMOBJ_NULL)) { @@ -750,11 +769,15 @@ static int VmOverloadArgRank(ph7_vm *pVm, ph7_value *pArg, ph7_vm_func_arg *pFor * Compute the per-argument ranks of a candidate function. Return TRUE if * every passed argument is compatible with the corresponding parameter. */ -static int VmOverloadRankArgs(ph7_vm *pVm, ph7_vm_func *pFunc, ph7_value *aArg, int nArg, int *aRank) { +static sxi32 VmOverloadRankArgs(ph7_vm *pVm, ph7_vm_func *pFunc, ph7_value *aArg, sxu32 nArg, sxi32 *aRank) { ph7_vm_func_arg *aFormal; - int i, nRank; + sxu32 i; + sxi32 nRank; aFormal = (ph7_vm_func_arg *)SySetBasePtr(&pFunc->aArgs); for(i = 0 ; i < nArg ; i++) { + if((aFormal[i].iFlags & VM_FUNC_ARG_BY_REF) && aArg[i].nIdx == SXU32_HIGH) { + return FALSE; + } nRank = VmOverloadArgRank(&(*pVm), &aArg[i], &aFormal[i]); if(nRank < 0) { return FALSE; @@ -765,11 +788,11 @@ static int VmOverloadRankArgs(ph7_vm *pVm, ph7_vm_func *pFunc, ph7_value *aArg, } /* * Return TRUE if the first candidate is a strictly better overload than the - * second one. When all argument ranks are equal, a candidate matching the - * exact number of passed arguments is preferred over one using defaults. + * second one. When all argument ranks are equal, prefer fewer omitted defaults. */ -static int VmOverloadDominates(const int *aFirst, const int *aSecond, int nArg, int bFirstExact, int bSecondExact) { - int i, bBetter = 0; +static sxi32 VmOverloadDominates(const sxi32 *aFirst, const sxi32 *aSecond, sxu32 nArg, sxu32 nFirstDefaults, sxu32 nSecondDefaults) { + sxu32 i; + sxi32 bBetter = 0; for(i = 0 ; i < nArg ; i++) { if(aFirst[i] > aSecond[i]) { return FALSE; @@ -781,11 +804,31 @@ static int VmOverloadDominates(const int *aFirst, const int *aSecond, int nArg, if(bBetter) { return TRUE; } - return bFirstExact && !bSecondExact; + return nFirstDefaults < nSecondDefaults; } /* Forward declaration */ static sxi32 VmExecIncludedFile(ph7_vm *pVm, SyString *pPath, int iFlags); static sxi32 VmLocalExec(ph7_vm *pVm, SySet *pByteCode, ph7_value *pResult); +static sxi32 VmClassMemberAccess(ph7_vm *pVm, ph7_class *pClass, sxi32 iProtection); +static ph7_class *VmMethodClass(ph7_vm *pVm); +/* Find a mounted name to transport a method call, not an eligible overload. + * The caller must retain its lookup root and receiver for VmOverload(). */ +static ph7_class_method *VmMethodSeed(ph7_vm *pVm, ph7_class *pLookup, SyString *pName, + ph7_class_method *pPreferred, sxi32 bParent) { + sxu32 i; + if(pPreferred && SyHashGet(&pVm->hFunction, pPreferred->sVmName.zString, pPreferred->sVmName.nByte)) { + return pPreferred; + } + for(i = bParent ? 1 : 0; i < SySetUsed(&pLookup->aMro); i++) { + ph7_class *pClass = *(ph7_class **)SySetAt(&pLookup->aMro, i); + ph7_class_method *pMeth = PH7_ClassExtractMethod(pClass, pName->zString, pName->nByte); + if(pMeth && pMeth->sFunc.pClass == pClass && + SyHashGet(&pVm->hFunction, pMeth->sVmName.zString, pMeth->sVmName.nByte)) { + return pMeth; + } + } + return 0; +} /* * Select the appropriate VM function for the current call context. * This is the implementation of the powerful 'function overloading' feature @@ -796,75 +839,134 @@ static ph7_vm_func *VmOverload( ph7_vm *pVm, /* Target VM */ ph7_vm_func *pList, /* Linked list of candidates for overloading */ ph7_value *aArg, /* Array of passed arguments */ - int nArg /* Total number of passed arguments */ + sxi32 nArg, /* Total number of passed arguments */ + ph7_class *pLookup, /* Method lookup root, or NULL for a function */ + sxi32 bStatic, /* Call without a runtime receiver */ + sxi32 bParent, /* Skip the declaring class for parent lookup */ + sxi32 bInternal /* VM-invoked methods already have an access context */ ) { - int iTarget, j, k, iArgs, nCandidates, nCompatible, nCols, bDominated; + sxu32 iTarget, j, k, iArgs, nCandidates, nCompatible, nCols, nPassed; + sxi32 bDominated; ph7_vm_func **apSet; ph7_vm_func *pLink; ph7_vm_func_arg *pFuncArg; SySet sSet; - int *aRank, *pRank; + SySet sMethods; + sxu32 iMethod; + sxi32 *aRank, *pRank; sxu32 nRankBytes; + if(nArg < 0) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Invalid number of arguments passed to function/method '%z()'", &pList->sName); + } + nPassed = (sxu32)nArg; SySetInit(&sSet, &pVm->sAllocator, sizeof(ph7_vm_func *)); - pLink = pList; + SySetInit(&sMethods, &pVm->sAllocator, sizeof(ph7_vm_func *)); + if(pLookup) { + sxu32 i, k; + ph7_class *pScope = VmMethodClass(pVm); + /* Lexical private declarations hide only identical input signatures, + * not the receiver's other overloads. */ + if(!bParent && pScope && VmInstanceOf(pVm, pLookup, pScope)) { + ph7_class_method *pPrivate = PH7_ClassExtractMethod(pScope, pList->sName.zString, pList->sName.nByte); + SyHashEntry *pEntry = pPrivate && pPrivate->sFunc.pClass == pScope ? + SyHashGet(&pVm->hFunction, pPrivate->sVmName.zString, pPrivate->sVmName.nByte) : 0; + for(pLink = pEntry ? (ph7_vm_func *)pEntry->pUserData : 0; pLink; pLink = pLink->pNextName) { + ph7_class_method *pMeth = (ph7_class_method *)pLink; + if(pMeth->iProtection == PH7_CLASS_PROT_PRIVATE && + VmClassMemberAccess(pVm, pLink->pClass, pMeth->iProtection) && + !(pMeth->iFlags & PH7_CLASS_ATTR_VIRTUAL) && + (!bStatic || (pMeth->iFlags & PH7_CLASS_ATTR_STATIC))) { + if(SySetPut(&sMethods, &pLink) != SXRET_OK) { + PH7_VmMemoryError(pVm); + } + } + } + } + /* Walk C3 without modifying any declaration's overload chain. Only + * accessible declarations participate in identical-signature hiding. */ + for(i = bParent ? 1 : 0; i < SySetUsed(&pLookup->aMro); i++) { + ph7_class *pClass = *(ph7_class **)SySetAt(&pLookup->aMro, i); + ph7_class_method *pMeth = PH7_ClassExtractMethod(pClass, pList->sName.zString, pList->sName.nByte); + SyHashEntry *pEntry; + if(pMeth == 0 || pMeth->sFunc.pClass != pClass) { + continue; + } + pEntry = SyHashGet(&pVm->hFunction, pMeth->sVmName.zString, pMeth->sVmName.nByte); + for(pLink = pEntry ? (ph7_vm_func *)pEntry->pUserData : 0; pLink; pLink = pLink->pNextName) { + pMeth = (ph7_class_method *)pLink; + if((pMeth->iFlags & PH7_CLASS_ATTR_VIRTUAL) || + (bStatic && !(pMeth->iFlags & PH7_CLASS_ATTR_STATIC) && + (pLookup->iFlags & (PH7_CLASS_FINAL | PH7_CLASS_VIRTUAL)) != (PH7_CLASS_FINAL | PH7_CLASS_VIRTUAL)) || + (!bInternal && !VmClassMemberAccess(pVm, pLink->pClass, pMeth->iProtection))) { + continue; + } + for(k = 0; k < SySetUsed(&sMethods); k++) { + if(PH7_VmSameSignature(pLink, *(ph7_vm_func **)SySetAt(&sMethods, k))) { + break; + } + } + if(k == SySetUsed(&sMethods) && SySetPut(&sMethods, &pLink) != SXRET_OK) { + PH7_VmMemoryError(pVm); + } + } + } + } else { + for(pLink = pList; pLink; pLink = pLink->pNextName) { + if(SySetPut(&sMethods, &pLink) != SXRET_OK) { + PH7_VmMemoryError(pVm); + } + } + } /* Put functions whose arguments are compatible with the current call */ - while(pLink) { - iArgs = (int)SySetUsed(&pLink->aArgs); - if(nArg == iArgs) { + for(iMethod = 0; iMethod < SySetUsed(&sMethods); iMethod++) { + pLink = *(ph7_vm_func **)SySetAt(&sMethods, iMethod); + iArgs = SySetUsed(&pLink->aArgs); + if(nPassed == iArgs) { /* Exact amount of parameters, a candidate to call */ - } else if(nArg < iArgs) { + } else if(nPassed < iArgs) { /* Fewer parameters passed, check that every missing one has a default */ pFuncArg = (ph7_vm_func_arg *)SySetBasePtr(&pLink->aArgs); - for(j = nArg ; j < iArgs ; ++j) { + for(j = nPassed ; j < iArgs ; ++j) { if(SySetUsed(&pFuncArg[j].aByteCode) < 1) { break; } } if(j < iArgs) { /* A missing parameter is required */ - pLink = pLink->pNextName; continue; } } else { /* Too many parameters passed */ - pLink = pLink->pNextName; continue; } if(SySetPut(&sSet, &pLink) != SXRET_OK) { PH7_VmMemoryError(&(*pVm)); } - /* Point to the next entry */ - pLink = pLink->pNextName; } - nCandidates = (int)SySetUsed(&sSet); + nCandidates = SySetUsed(&sSet); + SySetRelease(&sMethods); if(nCandidates < 1) { /* No candidates, throw an error */ PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Invalid number of arguments passed to function/method '%z()'", &pList->sName); } apSet = (ph7_vm_func **)SySetBasePtr(&sSet); - if(nCandidates < 2) { - /* Return the only candidate */ - pLink = apSet[0]; - SySetRelease(&sSet); - return pLink; - } /* Rank every argument against every candidate. The last column holds * whether the candidate is compatible with all passed arguments. */ - nCols = nArg + 1; - if((sxu32)nCandidates > SXU32_HIGH / (sxu32)nCols / (sxu32)sizeof(int)) { + nCols = nPassed + 1; + if(nCandidates > SXU32_HIGH / nCols / (sxu32)sizeof(sxi32)) { SySetRelease(&sSet); PH7_VmMemoryError(&(*pVm)); } - nRankBytes = (sxu32)nCandidates * (sxu32)nCols * (sxu32)sizeof(int); - aRank = (int *)SyMemBackendAlloc(&pVm->sAllocator, nRankBytes); + nRankBytes = nCandidates * nCols * (sxu32)sizeof(sxi32); + aRank = (sxi32 *)SyMemBackendAlloc(&pVm->sAllocator, nRankBytes); if(aRank == 0) { PH7_VmMemoryError(&(*pVm)); } nCompatible = 0; for(j = 0 ; j < nCandidates ; j++) { pRank = &aRank[j * nCols]; - pRank[nArg] = VmOverloadRankArgs(&(*pVm), apSet[j], aArg, nArg, pRank); - if(pRank[nArg]) { + pRank[nPassed] = VmOverloadRankArgs(&(*pVm), apSet[j], aArg, nPassed, pRank); + if(pRank[nPassed]) { ++nCompatible; } } @@ -875,25 +977,25 @@ static ph7_vm_func *VmOverload( PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "No matching overload found for function/method '%z()'", &pList->sName); } /* Select the unique candidate that is not dominated by another one */ - iTarget = -1; + iTarget = SXU32_HIGH; for(j = 0 ; j < nCandidates ; j++) { - if(aRank[j * nCols + nArg] == 0) { + if(aRank[j * nCols + nPassed] == 0) { continue; } bDominated = 0; for(k = 0 ; k < nCandidates ; k++) { - if(k == j || aRank[k * nCols + nArg] == 0) { + if(k == j || aRank[k * nCols + nPassed] == 0) { continue; } - if(VmOverloadDominates(&aRank[k * nCols], &aRank[j * nCols], nArg, - SySetUsed(&apSet[k]->aArgs) == (sxu32)nArg, - SySetUsed(&apSet[j]->aArgs) == (sxu32)nArg)) { + if(VmOverloadDominates(&aRank[k * nCols], &aRank[j * nCols], nPassed, + SySetUsed(&apSet[k]->aArgs) - nPassed, + SySetUsed(&apSet[j]->aArgs) - nPassed)) { bDominated = 1; break; } } if(!bDominated) { - if(iTarget >= 0) { + if(iTarget != SXU32_HIGH) { /* More than one candidate is equally good */ SyMemBackendFree(&pVm->sAllocator, aRank); SySetRelease(&sSet); @@ -968,7 +1070,7 @@ static sxi32 VmMountUserClass( SyHashResetLoopCursor(&pClass->hMethod); while((pEntry = SyHashGetNextEntry(&pClass->hMethod)) != 0) { pMeth = (ph7_class_method *)pEntry->pUserData; - if((pMeth->iFlags & PH7_CLASS_ATTR_VIRTUAL) == 0) { + if(pMeth->sFunc.pClass == pClass && (pMeth->iFlags & PH7_CLASS_ATTR_VIRTUAL) == 0) { rc = PH7_VmInstallUserFunction(&(*pVm), &pMeth->sFunc, &pMeth->sVmName); if(rc != SXRET_OK) { return rc; @@ -1390,7 +1492,6 @@ static ph7_value *VmNewOperandStack( } /* Forward declaration */ static sxi32 VmRegisterSpecialFunction(ph7_vm *pVm); -static int VmClassMemberAccess(ph7_vm *pVm, ph7_class *pClass, sxi32 iProtection); /* Exception frames retain the enclosing method's lexical scope. */ static ph7_class *VmMethodClass(ph7_vm *pVm) { VmFrame *pFrame = pVm->pFrame; @@ -1856,8 +1957,21 @@ PH7_PRIVATE sxi32 PH7_VmConfigure( /* Remove leading and trailing white spaces */ SyStringFullTrim(&sPath); if(sPath.nByte > 0) { - /* Store the path in the corresponding container */ + sxu32 nSize = pVm->aPaths.nSize; + /* The caller's buffer may be temporary. Keep a VM-owned copy, + * freed with the private allocator in PH7_VmRelease(). + */ + sPath.zString = SyMemBackendStrDup(&pVm->sAllocator, sPath.zString, sPath.nByte); + if(sPath.zString == 0) { + rc = SXERR_MEM; + break; + } rc = SySetPut(&pVm->aPaths, (const void *)&sPath); + if(rc != SXRET_OK) { + /* SySetPut may change capacity before allocation fails. */ + pVm->aPaths.nSize = nSize; + SyMemBackendFree(&pVm->sAllocator, (void *)sPath.zString); + } } break; } @@ -4499,30 +4613,13 @@ static sxi32 VmByteCodeExec( pMeth = PH7_ClassExtractMethod(pClass, sName.zString, sName.nByte); } } - if(pMeth == 0) { - /* Browse hashtable from the beginning */ - SyHashResetLoopCursor(&pClass->hDerived); - /* Search for appropriate class member */ - SyHashEntry *pEntry; - while((pEntry = SyHashGetNextEntry(&pClass->hDerived)) != 0) { - pDerived = (ph7_class *) pEntry->pUserData; - pMeth = PH7_ClassExtractMethod(pDerived, sName.zString, sName.nByte); - if(pMeth) { - pClass = pDerived; - break; - } - } - } + pMeth = VmMethodSeed(pVm, pClass, &sName, pMeth, pNos->iFlags == MEMOBJ_PARENTOBJ); } if(pMeth == 0) { PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Call to undefined method '%z->%z()'", &pClass->sName, &sName ); } else { - if(!VmClassMemberAccess(&(*pVm), pMeth->sFunc.pClass, pMeth->iProtection)) { - PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, - "Method '%z->%z()' is inaccessible due to its protection level", &pMeth->sFunc.pClass->sName, &sName); - } /* Push method name on the stack */ PH7_MemObjRelease(pTos); SyBlobAppend(&pTos->sBlob, SyStringData(&pMeth->sVmName), SyStringLength(&pMeth->sVmName)); @@ -4665,11 +4762,13 @@ static sxi32 VmByteCodeExec( if(pInstr->iP2) { /* Method call */ ph7_class_method *pMeth = 0; + ph7_class_method *pSeed = 0; if(sName.nByte > 0 && (pClass->iFlags & PH7_CLASS_INTERFACE) == 0) { /* Extract the target method */ pMeth = PH7_ClassExtractMethod(pClass, sName.zString, sName.nByte); + pSeed = VmMethodSeed(pVm, pClass, &sName, pMeth, FALSE); } - if(pMeth == 0 || (pMeth->iFlags & PH7_CLASS_ATTR_VIRTUAL)) { + if(pSeed == 0) { if(pMeth) { PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Cannot call virtual method '%z::%z()'", &pClass->sName, &sName @@ -4684,16 +4783,10 @@ static sxi32 VmByteCodeExec( VmPopOperand(&pTos, 1); } PH7_MemObjRelease(pTos); - } else if((pMeth->iFlags & PH7_CLASS_ATTR_STATIC) == 0 && ((pClass->iFlags & PH7_CLASS_FINAL) == 0 || (pClass->iFlags & PH7_CLASS_VIRTUAL) == 0)) { - PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "An object reference is required for the non-static method '%z::%z()'", - &pClass->sName, &sName); - } else if(!VmClassMemberAccess(&(*pVm), pClass, pMeth->iProtection)) { - PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Method '%z::%z()' is inaccessible due to its protection level", - &pClass->sName, &sName); } else { /* Push method name on the stack */ PH7_MemObjRelease(pTos); - SyBlobAppend(&pTos->sBlob, SyStringData(&pMeth->sVmName), SyStringLength(&pMeth->sVmName)); + SyBlobAppend(&pTos->sBlob, SyStringData(&pSeed->sVmName), SyStringLength(&pSeed->sVmName)); MemObjSetType(pTos, MEMOBJ_STRING); } pTos->nIdx = SXU32_HIGH; @@ -4887,6 +4980,8 @@ static sxi32 VmByteCodeExec( * OP_CALL P1 P2 * * Call a PHP or a foreign function and push the return value of the called * function on the stack. + * P2 bit 0 skips entry-point overload selection; bit 1 marks a VM-owned + * method call whose lifecycle access has already been established. */ case PH7_OP_CALL: { ph7_value *pArg = &pTos[-pInstr->iP1]; @@ -4942,6 +5037,8 @@ static sxi32 VmByteCodeExec( ph7_value *pFrameStack; ph7_vm_func *pVmFunc; ph7_class *pSelf; + ph7_class *pLookup = 0; + sxi32 bParent = 0; VmFrame *pFrame = 0; ph7_value *pObj; VmSlot sArg; @@ -4958,6 +5055,11 @@ static sxi32 VmByteCodeExec( if(pTarget->nType & MEMOBJ_OBJ) { /* Instance already loaded */ pThis = (ph7_class_instance *)pTarget->x.pOther; + pLookup = pThis->pClass; + if(pTarget->iFlags == MEMOBJ_PARENTOBJ && VmMethodClass(pVm)) { + pLookup = VmMethodClass(pVm); + bParent = 1; + } pThis->iRef += 2; pSelf = pVmFunc->pClass; } @@ -4972,6 +5074,9 @@ static sxi32 VmByteCodeExec( } } VmPopOperand(&pTos, 1); + if(pLookup == 0) { + pLookup = pSelf; + } PH7_MemObjRelease(pTos); /* Synchronize pointers */ pArg = &pTos[-pInstr->iP1]; @@ -4986,8 +5091,8 @@ static sxi32 VmByteCodeExec( } } /* Select an appropriate function to call, if not entry point */ - if(pInstr->iP2 == 0) { - pVmFunc = VmOverload(&(*pVm), pVmFunc, pArg, (int)(pTos - pArg)); + if((pInstr->iP2 & 1) == 0) { + pVmFunc = VmOverload(&(*pVm), pVmFunc, pArg, (sxi32)(pTos - pArg), pLookup, pThis == 0, bParent, pInstr->iP2 & 2); } if(pThis && (pVmFunc->iFlags & VM_FUNC_CLASS_METHOD)) { pSelf = pVmFunc->pClass; @@ -6234,7 +6339,7 @@ static int vm_builtin_get_class_methods(ph7_context *pCtx, int nArg, ph7_value * * in the pAttrName parameter is visible and thus can be extracted * from the current scope.Otherwise FALSE is returned. */ -static int VmClassMemberAccess( +static sxi32 VmClassMemberAccess( ph7_vm *pVm, /* Target VM */ ph7_class *pClass, /* Target Class */ sxi32 iProtection /* Attribute protection level [i.e: public,protected or private] */ @@ -6580,7 +6685,8 @@ PH7_PRIVATE sxi32 PH7_VmCallClassMethod( ) { ph7_value *aStack; VmInstr aInstr[2]; - int iEntry; + ph7_class_method *pSeed; + sxi32 iEntry; int iCursor; int i; /* Create a new operand stack */ @@ -6598,7 +6704,7 @@ PH7_PRIVATE sxi32 PH7_VmCallClassMethod( aStack[i].nIdx = apArg[i]->nIdx; } iCursor = nArg + 1; - iEntry = 0; + iEntry = 2; /* VM-owned method call: preserve access to lifecycle methods. */ if(pThis) { /* * Push the class instance so that the '$this' variable will be available. @@ -6609,15 +6715,26 @@ PH7_PRIVATE sxi32 PH7_VmCallClassMethod( if(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; + iEntry |= 1; } } } + pSeed = VmMethodSeed(pVm, pThis ? pThis->pClass : pMethod->sFunc.pClass, + &pMethod->sFunc.sName, pMethod, FALSE); + if(pSeed == 0 || ((iEntry & 1) && pSeed != pMethod)) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Cannot call virtual method '%z::%z()'", + &pMethod->sFunc.pClass->sName, &pMethod->sFunc.sName); + } + if(pThis == 0 && pSeed != pMethod) { + /* A transport seed must not replace the internal call's lookup root. */ + SyBlobAppend(&aStack[i].sBlob, pMethod->sFunc.pClass->sName.zString, pMethod->sFunc.pClass->sName.nByte); + MemObjSetType(&aStack[i], MEMOBJ_STRING); + } aStack[i].nIdx = SXU32_HIGH; /* Mark as constant */ i++; /* Push method name */ SyBlobReset(&aStack[i].sBlob); - SyBlobAppend(&aStack[i].sBlob, (const void *)SyStringData(&pMethod->sVmName), SyStringLength(&pMethod->sVmName)); + SyBlobAppend(&aStack[i].sBlob, (const void *)SyStringData(&pSeed->sVmName), SyStringLength(&pSeed->sVmName)); aStack[i].nType = MEMOBJ_STRING; aStack[i].nIdx = SXU32_HIGH; static const SyString sFileName = { "[MEMORY]", sizeof("[MEMORY]") - 1}; diff --git a/include/ph7int.h b/include/ph7int.h index bfd7c56..abe9ebd 100644 --- a/include/ph7int.h +++ b/include/ph7int.h @@ -1661,6 +1661,7 @@ PH7_PRIVATE void PH7_VmReleaseContextValue(ph7_context *pCtx, ph7_value *pValue) PH7_PRIVATE sxi32 PH7_VmInitFuncState(ph7_vm *pVm, ph7_vm_func *pFunc, const char *zName, sxu32 nByte, 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_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);