diff --git a/engine/oop.c b/engine/oop.c index 661f9e0..bc9a9a9 100644 --- a/engine/oop.c +++ b/engine/oop.c @@ -203,6 +203,101 @@ PH7_PRIVATE sxi32 PH7_ClassInstallMethod(ph7_class *pClass, ph7_class_method *pM rc = SyHashInsert(&pClass->hMethod, (const void *)pName->zString, pName->nByte, pMeth); return rc; } +/* + * Validate actual declarations, not inherited hash entries. Forward-declared + * bases need not have executed CLASS_INIT, and virtual methods are not mounted + * in the VM function table. Input identity remains independent of contracts. + */ +static sxi32 ClassValidateMethods(ph7_vm *pVm, ph7_class *pClass) { + SySet sMethods; + SyHashEntry *pEntry; + ph7_class_method *pMeth, *pOther, *pImpl; + sxu32 i, j, k; + sxi32 rc = SXRET_OK; + SySetInit(&sMethods, &pVm->sAllocator, sizeof(ph7_class_method *)); + for(i = 0; i < SySetUsed(&pClass->aMro); i++) { + ph7_class *pOwner = *(ph7_class **)SySetAt(&pClass->aMro, i); + SyHashResetLoopCursor(&pOwner->hMethod); + while((pEntry = SyHashGetNextEntry(&pOwner->hMethod)) != 0) { + pMeth = (ph7_class_method *)pEntry->pUserData; + if(pMeth->sFunc.pClass == pOwner) { + rc = SySetPut(&sMethods, &pMeth); + if(rc != SXRET_OK) { + goto done; + } + } + } + } + for(i = 0; i < SySetUsed(&sMethods); i++) { + pMeth = *(ph7_class_method **)SySetAt(&sMethods, i); + for(j = i + 1; j < SySetUsed(&sMethods); j++) { + pOther = *(ph7_class_method **)SySetAt(&sMethods, j); + if(!PH7_VmSameSignature(&pMeth->sFunc, &pOther->sFunc)) { + continue; + } + for(k = 0; k < SySetUsed(&pMeth->sFunc.pClass->aMro); k++) { + if(*(ph7_class **)SySetAt(&pMeth->sFunc.pClass->aMro, k) == pOther->sFunc.pClass) { + break; + } + } + /* An unrelated base's private method cannot shadow this declaration. + * A descendant's private declaration can still narrow an override. */ + if(pOther->iProtection == PH7_CLASS_PROT_PRIVATE || + (pMeth->iProtection == PH7_CLASS_PROT_PRIVATE && + k == SySetUsed(&pMeth->sFunc.pClass->aMro))) { + continue; + } + if(pOther->iFlags & PH7_CLASS_ATTR_FINAL) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Cannot overwrite final method '%z:%z()' inside child class '%z'", &pOther->sFunc.pClass->sName, &pOther->sFunc.sName, &pClass->sName); + } + /* Visibility involving an unrelated virtual requirement is checked + * against the effective implementation below, not a hidden method. */ + if(pMeth->iProtection > pOther->iProtection && + (k < SySetUsed(&pMeth->sFunc.pClass->aMro) || + !((pMeth->iFlags | pOther->iFlags) & PH7_CLASS_ATTR_VIRTUAL))) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Cannot narrow visibility of method '%z::%z()' inside class '%z'", &pOther->sFunc.pClass->sName, &pOther->sFunc.sName, &pClass->sName); + } + /* Check real overrides and consistency between requirements here. + * Hidden unrelated implementations do not impose contracts. */ + if((k < SySetUsed(&pMeth->sFunc.pClass->aMro) || + ((pMeth->iFlags & pOther->iFlags) & PH7_CLASS_ATTR_VIRTUAL)) && + (pMeth->sFunc.nType != pOther->sFunc.nType || + ((pMeth->sFunc.iFlags ^ pOther->sFunc.iFlags) & VM_FUNC_REF_RETURN) || + ((pMeth->iFlags ^ pOther->iFlags) & PH7_CLASS_ATTR_STATIC))) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Incompatible method contract '%z::%z()' inside class '%z'", &pOther->sFunc.pClass->sName, &pOther->sFunc.sName, &pClass->sName); + } + } + if(!(pMeth->iFlags & PH7_CLASS_ATTR_VIRTUAL)) { + continue; + } + pImpl = 0; + for(j = 0; j < SySetUsed(&sMethods); j++) { + pOther = *(ph7_class_method **)SySetAt(&sMethods, j); + if(!(pOther->iFlags & PH7_CLASS_ATTR_VIRTUAL) && + pOther->iProtection != PH7_CLASS_PROT_PRIVATE && + PH7_VmSameSignature(&pMeth->sFunc, &pOther->sFunc)) { + pImpl = pOther; + break; + } + } + if(pImpl && pImpl->iProtection > pMeth->iProtection) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Cannot narrow visibility of method '%z::%z()' inside class '%z'", &pMeth->sFunc.pClass->sName, &pMeth->sFunc.sName, &pClass->sName); + } + /* Every requirement uses the same first concrete C3 declaration, not + * a later implementation selected for its compatible contract. */ + if(pImpl && (pImpl->sFunc.nType != pMeth->sFunc.nType || + ((pImpl->sFunc.iFlags ^ pMeth->sFunc.iFlags) & VM_FUNC_REF_RETURN) || + ((pImpl->iFlags ^ pMeth->iFlags) & PH7_CLASS_ATTR_STATIC))) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Incompatible method contract '%z::%z()' inside class '%z'", &pMeth->sFunc.pClass->sName, &pMeth->sFunc.sName, &pClass->sName); + } + if(!pImpl && !(pClass->iFlags & PH7_CLASS_VIRTUAL)) { + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Virtual method '%z::%z()' must be defined inside child class '%z'", &pMeth->sFunc.pClass->sName, &pMeth->sFunc.sName, &pClass->sName); + } + } +done: + SySetRelease(&sMethods); + return rc; +} /* * Build [C] + merge(MRO(B1), ..., MRO(Bn), [B1, ..., Bn]). * Resolve declarations rather than hDerived: bytecode initialization may not @@ -328,7 +423,7 @@ done: SySetRelease(&sMro); pClass->iMroState = 0; } - return rc; + return rc == SXRET_OK ? ClassValidateMethods(pVm, pClass) : rc; } /* * Perform an inheritance operation. @@ -400,20 +495,14 @@ PH7_PRIVATE sxi32 PH7_ClassInherit(ph7_vm *pVm, ph7_class *pSub, ph7_class *pBas } SyHashResetLoopCursor(&pBase->hMethod); while((pEntry = SyHashGetNextEntry(&pBase->hMethod)) != 0) { - /* Make sure the final methods are not redeclared in the subclass */ + /* Method contracts are checked against declarations across the C3 MRO. */ pMeth = (ph7_class_method *)pEntry->pUserData; + if(pMeth->iFlags & PH7_CLASS_ATTR_VIRTUAL) { + continue; + } pName = &pMeth->sFunc.sName; if((pEntry = SyHashGet(&pSub->hMethod, (const void *)pName->zString, pName->nByte)) != 0) { - if(pMeth->iFlags & PH7_CLASS_ATTR_FINAL) { - /* Cannot Overwrite final method */ - PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Cannot overwrite final method '%z:%z()' inside child class '%z'", &pBase->sName, pName, &pSub->sName); - } continue; - } else { - if(pMeth->iFlags & PH7_CLASS_ATTR_VIRTUAL) { - /* Virtual method must be defined in the child class */ - PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Virtual method '%z::%z()' must be defined inside child class '%z'", &pBase->sName, pName, &pSub->sName); - } } /* Install the method */ rc = SyHashInsert(&pSub->hMethod, (const void *)pName->zString, pName->nByte, pMeth); diff --git a/engine/vm.c b/engine/vm.c index a813eba..6191d55 100644 --- a/engine/vm.c +++ b/engine/vm.c @@ -895,8 +895,7 @@ static ph7_vm_func *VmOverload( 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)) || + (bStatic && !(pMeth->iFlags & PH7_CLASS_ATTR_STATIC)) || (!bInternal && !VmClassMemberAccess(pVm, pLink->pClass, pMeth->iProtection))) { continue; }