Preserve class attribute identity and validate inheritance conflicts across C3 MRO
Build / AerScript (push) Successful in 42s

This commit is contained in:
2026-10-24 18:26:58 +02:00
parent cf68edd920
commit d511414453
4 changed files with 278 additions and 73 deletions
+145 -44
View File
@@ -829,6 +829,116 @@ static ph7_class_method *VmMethodSeed(ph7_vm *pVm, ph7_class *pLookup, SyString
}
return 0;
}
/*
* Resolve a class attribute by its exact name. Classes walk their C3
* linearization, interfaces follow their pBase chain. A private declaration of
* the current lexical scope wins, while any other inaccessible candidate is
* remembered but never hides a later visible member. When nothing is
* accessible, pDenied tells the caller to report an access error rather than
* an undefined attribute; ppDeniedAttr receives the first rejected declaration
* and pAmbiguous tells whether more than one declaration was rejected.
*/
static ph7_class_attr *VmResolveClassAttr(ph7_vm *pVm, ph7_class *pClass, const char *zName, sxu32 nByte,
ph7_class *pScope, sxi32 bParent, sxi32 *pDenied, ph7_class_attr **ppDeniedAttr, sxi32 *pAmbiguous) {
ph7_class_attr *pAttr, *pDeniedAttr = 0;
sxi32 bAmbiguous = FALSE;
SyString sName;
sxu32 i;
*pDenied = FALSE;
*ppDeniedAttr = 0;
*pAmbiguous = FALSE;
SyStringInitFromBuf(&sName, zName, nByte);
if(pClass->iFlags & PH7_CLASS_INTERFACE) {
ph7_class *pIt = pClass;
ph7_class_attr *pResult = 0;
SySet aVisited;
SySetInit(&aVisited, &pVm->sAllocator, sizeof(ph7_class *));
while(pIt) {
SyHashEntry *pEntry;
sxu32 n;
for(n = 0; n < SySetUsed(&aVisited); n++) {
if(*(ph7_class **)SySetAt(&aVisited, n) == pIt) {
break;
}
}
if(n < SySetUsed(&aVisited)) {
/* Interface inheritance is cyclic; all reachable members were scanned */
break;
}
if(SySetPut(&aVisited, &pIt) != SXRET_OK) {
SySetRelease(&aVisited);
PH7_VmMemoryError(pVm);
return 0;
}
SyHashResetLoopCursor(&pIt->hAttr);
while((pEntry = SyHashGetNextEntry(&pIt->hAttr)) != 0) {
pAttr = (ph7_class_attr *)pEntry->pUserData;
if(SyStringCmp(&pAttr->sName, &sName, SyMemcmp) == 0) {
pResult = pAttr;
break;
}
}
if(pResult) {
break;
}
pIt = pIt->pBase;
}
SySetRelease(&aVisited);
return pResult;
}
if(!bParent && SySetUsed(&pClass->aMro) == 0) {
/* Static initializers run before CLASS_INIT; only direct declarations
* are available at that point. */
pAttr = PH7_ClassExtractAttribute(pClass, zName, nByte);
if(pAttr) {
if(VmClassMemberAccess(pVm, pAttr->pClass, pAttr->iProtection)) {
return pAttr;
}
*pDenied = TRUE;
*ppDeniedAttr = pAttr;
}
return 0;
}
if(pScope && !bParent && VmInstanceOf(pVm, pClass, pScope)) {
pAttr = PH7_ClassExtractAttribute(pScope, zName, nByte);
if(pAttr && pAttr->iProtection == PH7_CLASS_PROT_PRIVATE) {
/* Methods use their own class declaration */
return pAttr;
}
}
for(i = bParent ? 1 : 0; i < SySetUsed(&pClass->aMro); i++) {
ph7_class *pOwner = *(ph7_class **)SySetAt(&pClass->aMro, i);
pAttr = PH7_ClassExtractAttribute(pOwner, zName, nByte);
if(pAttr == 0) {
continue;
}
if(pAttr->iProtection == PH7_CLASS_PROT_PRIVATE) {
if(pScope == pOwner) {
return pAttr;
}
/* Inaccessible private candidate, keep looking */
*pDenied = TRUE;
if(pDeniedAttr == 0) {
pDeniedAttr = pAttr;
} else if(pDeniedAttr != pAttr) {
bAmbiguous = TRUE;
}
continue;
}
if(VmClassMemberAccess(pVm, pAttr->pClass, pAttr->iProtection)) {
return pAttr;
}
*pDenied = TRUE;
if(pDeniedAttr == 0) {
pDeniedAttr = pAttr;
} else if(pDeniedAttr != pAttr) {
bAmbiguous = TRUE;
}
}
*ppDeniedAttr = pDeniedAttr;
*pAmbiguous = bAmbiguous;
return 0;
}
/*
* Select the appropriate VM function for the current call context.
* This is the implementation of the powerful 'function overloading' feature
@@ -1027,7 +1137,9 @@ static sxi32 VmMountUserClass(
while((pEntry = SyHashGetNextEntry(&pClass->hAttr)) != 0) {
/* Extract the current attribute */
pAttr = (ph7_class_attr *)pEntry->pUserData;
if(pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT | PH7_CLASS_ATTR_STATIC)) {
if((pAttr->iFlags & (PH7_CLASS_ATTR_CONSTANT | PH7_CLASS_ATTR_STATIC)) && pAttr->nIdx == SXU32_HIGH) {
/* Static/constant storage belongs to the declaring declaration and
* is mounted only once, even when the class is mounted again. */
ph7_value *pMemObj, *pResult;
/* Reserve a memory object for this constant/static attribute */
pMemObj = PH7_ReserveMemObj(&(*pVm));
@@ -4578,7 +4690,7 @@ static sxi32 VmByteCodeExec(
if(!pNos->x.pOther) {
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Call to non-instantiated object '$%z'", &sName);
}
ph7_class *pClass, *pDerived, *pScope;
ph7_class *pClass, *pScope;
/* Class already instantiated */
pThis = (ph7_class_instance *)pNos->x.pOther;
/* Point to the instantiated class */
@@ -4628,56 +4740,34 @@ static sxi32 VmByteCodeExec(
} else {
/* Attribute access */
VmClassAttr *pObjAttr = 0;
ph7_class_attr *pAttr = 0;
ph7_class_attr *pAttr = 0, *pDeniedAttr = 0;
SyHashEntry *pEntry;
sxi32 bDenied = FALSE, bAmbiguous = FALSE;
if(sName.nByte > 0) {
/* Extract the target attribute */
if(pNos->iFlags != MEMOBJ_PARENTOBJ) {
/* Private fields belong to their declaring class, even on a derived object. */
if(pScope && VmInstanceOf(pVm, pClass, pScope)) {
pAttr = PH7_ClassExtractAttribute(pScope, sName.zString, sName.nByte);
if(pAttr && pAttr->iProtection != PH7_CLASS_PROT_PRIVATE) {
pAttr = 0;
}
}
if(pAttr == 0) {
pAttr = PH7_ClassExtractAttribute(pClass, sName.zString, sName.nByte);
}
}
if(pAttr == 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;
SyHashResetLoopCursor(&pDerived->hAttr);
while((pEntry = SyHashGetNextEntry(&pDerived->hAttr)) != 0) {
pAttr = (ph7_class_attr *)pEntry->pUserData;
if(SyStrncmp(pAttr->sName.zString, sName.zString, sName.nByte) == 0) {
break;
}
pAttr = 0;
}
if(pAttr) {
break;
}
}
}
/* Resolve the declaration first, then match the object slot by identity */
pAttr = VmResolveClassAttr(pVm, pClass, sName.zString, sName.nByte, pScope,
pNos->iFlags == MEMOBJ_PARENTOBJ, &bDenied, &pDeniedAttr, &bAmbiguous);
if(pAttr) {
SyHashResetLoopCursor(&pThis->hAttr);
while((pEntry = SyHashGetNextEntry(&pThis->hAttr)) != 0) {
pObjAttr = (VmClassAttr *)pEntry->pUserData;
if(pObjAttr->pAttr->pClass == pAttr->pClass) {
if(SyStrncmp(pObjAttr->pAttr->sName.zString, sName.zString, sName.nByte) == 0) {
break;
}
if(pObjAttr->pAttr == pAttr) {
break;
}
pObjAttr = 0;
}
}
}
if(pObjAttr == 0) {
if(bDenied) {
char zAttr[256];
SyString sAttr;
ph7_class *pOwner = (pDeniedAttr && !bAmbiguous) ? pDeniedAttr->pClass : pClass;
PH7_ClassAttrDiagnosticName(pOwner, pDeniedAttr ? pDeniedAttr->iFlags : 0,
&sName, zAttr, sizeof(zAttr), &sAttr);
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR,
"Class attribute '%z' is inaccessible due to its protection level", &sAttr);
}
/* No such attribute,load null */
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Undefined class attribute '%z->%z'",
&pClass->sName, &sName);
@@ -4791,12 +4881,23 @@ static sxi32 VmByteCodeExec(
pTos->nIdx = SXU32_HIGH;
} else {
/* Attribute access */
ph7_class_attr *pAttr = 0;
/* Extract the target attribute */
ph7_class_attr *pAttr = 0, *pDeniedAttr = 0;
ph7_class *pScope = VmMethodClass(pVm);
sxi32 bDenied = FALSE, bAmbiguous = FALSE;
/* Resolve inherited declarations through the C3 MRO */
if(sName.nByte > 0) {
pAttr = PH7_ClassExtractAttribute(pClass, sName.zString, sName.nByte);
pAttr = VmResolveClassAttr(pVm, pClass, sName.zString, sName.nByte, pScope, FALSE,
&bDenied, &pDeniedAttr, &bAmbiguous);
}
if(pAttr == 0) {
if(bDenied) {
char zAttr[256];
SyString sAttr;
ph7_class *pOwner = (pDeniedAttr && !bAmbiguous) ? pDeniedAttr->pClass : pClass;
PH7_ClassAttrDiagnosticName(pOwner, pDeniedAttr ? pDeniedAttr->iFlags : 0,
&sName, zAttr, sizeof(zAttr), &sAttr);
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Class attribute '%z' is inaccessible due to its protection level", &sAttr);
}
/* No such attribute,load null */
PH7_VmThrowError(&(*pVm), PH7_CTX_ERR, "Undefined class attribute '%z::$%z'",
&pClass->sName, &sName);
@@ -4816,7 +4917,7 @@ static sxi32 VmByteCodeExec(
} else {
ph7_value *pValue;
/* Check if the access to the attribute is allowed */
if(VmClassMemberAccess(&(*pVm), pClass, pAttr->iProtection)) {
if(VmClassMemberAccess(&(*pVm), pAttr->pClass, pAttr->iProtection)) {
/* Load the desired attribute */
pValue = (ph7_value *)SySetAt(&pVm->aMemObj, pAttr->nIdx);
if(pValue) {