Validate method visibility, final overrides and effective virtual contracts across C3 MRO
Build / AerScript (push) Failing after 40s
Build / AerScript (push) Failing after 40s
This commit is contained in:
+100
-11
@@ -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);
|
rc = SyHashInsert(&pClass->hMethod, (const void *)pName->zString, pName->nByte, pMeth);
|
||||||
return rc;
|
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]).
|
* Build [C] + merge(MRO(B1), ..., MRO(Bn), [B1, ..., Bn]).
|
||||||
* Resolve declarations rather than hDerived: bytecode initialization may not
|
* Resolve declarations rather than hDerived: bytecode initialization may not
|
||||||
@@ -328,7 +423,7 @@ done:
|
|||||||
SySetRelease(&sMro);
|
SySetRelease(&sMro);
|
||||||
pClass->iMroState = 0;
|
pClass->iMroState = 0;
|
||||||
}
|
}
|
||||||
return rc;
|
return rc == SXRET_OK ? ClassValidateMethods(pVm, pClass) : rc;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* Perform an inheritance operation.
|
* 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);
|
SyHashResetLoopCursor(&pBase->hMethod);
|
||||||
while((pEntry = SyHashGetNextEntry(&pBase->hMethod)) != 0) {
|
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;
|
pMeth = (ph7_class_method *)pEntry->pUserData;
|
||||||
|
if(pMeth->iFlags & PH7_CLASS_ATTR_VIRTUAL) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
pName = &pMeth->sFunc.sName;
|
pName = &pMeth->sFunc.sName;
|
||||||
if((pEntry = SyHashGet(&pSub->hMethod, (const void *)pName->zString, pName->nByte)) != 0) {
|
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;
|
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 */
|
/* Install the method */
|
||||||
rc = SyHashInsert(&pSub->hMethod, (const void *)pName->zString, pName->nByte, pMeth);
|
rc = SyHashInsert(&pSub->hMethod, (const void *)pName->zString, pName->nByte, pMeth);
|
||||||
|
|||||||
+1
-2
@@ -895,8 +895,7 @@ static ph7_vm_func *VmOverload(
|
|||||||
for(pLink = pEntry ? (ph7_vm_func *)pEntry->pUserData : 0; pLink; pLink = pLink->pNextName) {
|
for(pLink = pEntry ? (ph7_vm_func *)pEntry->pUserData : 0; pLink; pLink = pLink->pNextName) {
|
||||||
pMeth = (ph7_class_method *)pLink;
|
pMeth = (ph7_class_method *)pLink;
|
||||||
if((pMeth->iFlags & PH7_CLASS_ATTR_VIRTUAL) ||
|
if((pMeth->iFlags & PH7_CLASS_ATTR_VIRTUAL) ||
|
||||||
(bStatic && !(pMeth->iFlags & PH7_CLASS_ATTR_STATIC) &&
|
(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))) {
|
(!bInternal && !VmClassMemberAccess(pVm, pLink->pClass, pMeth->iProtection))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user