From d5114144538bd2fa902f508314bd64311bed19d4 Mon Sep 17 00:00:00 2001 From: Rafal Kupiec Date: Sat, 24 Oct 2026 18:26:58 +0200 Subject: [PATCH] Preserve class attribute identity and validate inheritance conflicts across C3 MRO --- engine/compiler.c | 4 +- engine/oop.c | 154 ++++++++++++++++++++++++++++++------- engine/vm.c | 189 +++++++++++++++++++++++++++++++++++----------- include/ph7int.h | 4 +- 4 files changed, 278 insertions(+), 73 deletions(-) diff --git a/engine/compiler.c b/engine/compiler.c index 29ef859..0c4ff39 100644 --- a/engine/compiler.c +++ b/engine/compiler.c @@ -3275,7 +3275,7 @@ loop: return SXERR_ABORT; } /* All done,install the constant */ - rc = PH7_ClassInstallAttr(pClass, pCons); + rc = PH7_ClassInstallAttr(pGen, pClass, pCons); if(rc != SXRET_OK) { PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory"); } @@ -3367,7 +3367,7 @@ loop: PH7_VmSetByteCodeContainer(pGen->pVm, pInstrContainer); } /* All done,install the attribute */ - rc = PH7_ClassInstallAttr(pClass, pAttr); + rc = PH7_ClassInstallAttr(pGen, pClass, pAttr); if(rc != SXRET_OK) { PH7_GenCompileError(pGen, E_ERROR, nLine, "PH7 is running out-of-memory"); } diff --git a/engine/oop.c b/engine/oop.c index bc9a9a9..eaedc3c 100644 --- a/engine/oop.c +++ b/engine/oop.c @@ -163,32 +163,67 @@ PH7_PRIVATE ph7_class_method *PH7_ClassExtractMethod(ph7_class *pClass, const ch return (ph7_class_method *)pEntry->pUserData; } /* - * Check if the given name is a class attribute. + * Check if the given name is an attribute declared directly by the given class. + * Names are compared exactly (length and bytes), inherited copies are ignored. * Return the desired attribute [i.e: ph7_class_attr instance] on success.NULL otherwise. */ PH7_PRIVATE ph7_class_attr *PH7_ClassExtractAttribute(ph7_class *pClass, const char *zName, sxu32 nByte) { SyHashEntry *pEntry; ph7_class_attr *pAttr; + SyString sName; + /* Attribute names match exactly, including their length */ + SyStringInitFromBuf(&sName, zName, nByte); /* Perform a hash lookup */ SyHashResetLoopCursor(&pClass->hAttr); while((pEntry = SyHashGetNextEntry(&pClass->hAttr)) != 0) { /* Point to the desired method */ pAttr = (ph7_class_attr *)pEntry->pUserData; - if(pAttr->pClass == pClass && SyStrncmp(pAttr->sName.zString, zName, nByte) == 0) { + if(pAttr->pClass == pClass && SyStringCmp(&pAttr->sName, &sName, SyMemcmp) == 0) { return pAttr; } } /* No such entry */ return 0; } +/* + * Format an attribute reference for diagnostics. Instance fields use + * 'Class->name', static fields 'Class::$name' and constants 'Class::NAME'. + */ +PH7_PRIVATE void PH7_ClassAttrDiagnosticName(ph7_class *pClass, sxi32 iFlags, const SyString *pName, + char *zBuf, sxu32 nBuf, SyString *pOut) { + const char *zSeparator; + if(iFlags & PH7_CLASS_ATTR_CONSTANT) { + zSeparator = "::"; + } else if(iFlags & PH7_CLASS_ATTR_STATIC) { + zSeparator = "::$"; + } else { + zSeparator = "->"; + } + nBuf = SyBufferFormat(zBuf, nBuf, "%z%s%z", &pClass->sName, zSeparator, pName); + SyStringInitFromBuf(pOut, zBuf, nBuf); +} /* * Install a class attribute in the corresponding container. * Return SXRET_OK on success. Any other return value indicates failure. + * All class attributes (instance fields, static fields and constants) share + * a single name space. Duplicates inside one class are rejected at compile + * time; conflicts across the inheritance chain are checked at class init. */ -PH7_PRIVATE sxi32 PH7_ClassInstallAttr(ph7_class *pClass, ph7_class_attr *pAttr) { +PH7_PRIVATE sxi32 PH7_ClassInstallAttr(ph7_gen_state *pGen, ph7_class *pClass, ph7_class_attr *pAttr) { + SyHashEntry *pEntry; SyString *pName = &pAttr->sName; sxi32 rc; pAttr->pClass = pClass; + SyHashResetLoopCursor(&pClass->hAttr); + while((pEntry = SyHashGetNextEntry(&pClass->hAttr)) != 0) { + ph7_class_attr *pOther = (ph7_class_attr *)pEntry->pUserData; + if(pOther->pClass == pClass && SyStringCmp(&pOther->sName, pName, SyMemcmp) == 0) { + char zAttr[256]; + SyString sAttr; + PH7_ClassAttrDiagnosticName(pClass, pAttr->iFlags, &pAttr->sName, zAttr, sizeof(zAttr), &sAttr); + PH7_GenCompileError(pGen, E_ERROR, pAttr->nLine, "Duplicate class attribute '%z' in class '%z'", &sAttr, &pClass->sName); + } + } rc = SyHashInsert(&pClass->hAttr, (const void *)pName->zString, pName->nByte, pAttr); return rc; } @@ -203,6 +238,71 @@ 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; } +/* + * Return TRUE if the first class is a descendant of the second one according + * to its already built C3 linearization. + */ +static sxi32 ClassIsDescendant(ph7_class *pClass, ph7_class *pAncestor) { + sxu32 i; + for(i = 1; i < SySetUsed(&pClass->aMro); i++) { + if(*(ph7_class **)SySetAt(&pClass->aMro, i) == pAncestor) { + return TRUE; + } + } + return FALSE; +} +/* + * Validate attribute declarations across the C3 linearization. Two visible + * declarations with the same name conflict even when they are identical. + * Private declarations of different classes are independent, even along an + * inheritance chain, but a private declaration cannot narrow a visible member + * inherited from a base. + */ +static sxi32 ClassValidateAttributes(ph7_vm *pVm, ph7_class *pClass) { + SyHashEntry *pEntry, *pOtherEntry; + sxu32 i, j; + for(i = 0; i < SySetUsed(&pClass->aMro); i++) { + ph7_class *pOwner = *(ph7_class **)SySetAt(&pClass->aMro, i); + SyHashResetLoopCursor(&pOwner->hAttr); + while((pEntry = SyHashGetNextEntry(&pOwner->hAttr)) != 0) { + ph7_class_attr *pAttr = (ph7_class_attr *)pEntry->pUserData; + if(pAttr->pClass != pOwner) { + /* Inherited copy, not a declaration of this class */ + continue; + } + for(j = i + 1; j < SySetUsed(&pClass->aMro); j++) { + ph7_class *pOtherOwner = *(ph7_class **)SySetAt(&pClass->aMro, j); + SyHashResetLoopCursor(&pOtherOwner->hAttr); + while((pOtherEntry = SyHashGetNextEntry(&pOtherOwner->hAttr)) != 0) { + ph7_class_attr *pOther = (ph7_class_attr *)pOtherEntry->pUserData; + if(pOther->pClass != pOtherOwner || SyStringCmp(&pAttr->sName, &pOther->sName, SyMemcmp) != 0) { + continue; + } + if(pAttr->iProtection == PH7_CLASS_PROT_PRIVATE && pOther->iProtection == PH7_CLASS_PROT_PRIVATE) { + /* Private declarations of different classes are independent */ + continue; + } + if(pAttr->iProtection == PH7_CLASS_PROT_PRIVATE) { + if(!ClassIsDescendant(pOwner, pOtherOwner)) { + /* A private member of an unrelated base cannot hide a visible one */ + continue; + } + } else if(pOther->iProtection == PH7_CLASS_PROT_PRIVATE && !ClassIsDescendant(pOtherOwner, pOwner)) { + /* An inherited private member does not hide a new visible declaration */ + continue; + } + char zAttr[256], zOther[256]; + SyString sAttr, sOther; + PH7_ClassAttrDiagnosticName(pAttr->pClass, pAttr->iFlags, &pAttr->sName, zAttr, sizeof(zAttr), &sAttr); + PH7_ClassAttrDiagnosticName(pOther->pClass, pOther->iFlags, &pOther->sName, zOther, sizeof(zOther), &sOther); + PH7_VmThrowError(pVm, PH7_CTX_ERR, "Class attribute '%z' conflicts with '%z' in class '%z'", + &sAttr, &sOther, &pClass->sName); + } + } + } + } + return SXRET_OK; +} /* * Validate actual declarations, not inherited hash entries. Forward-declared * bases need not have executed CLASS_INIT, and virtual methods are not mounted @@ -423,7 +523,13 @@ done: SySetRelease(&sMro); pClass->iMroState = 0; } - return rc == SXRET_OK ? ClassValidateMethods(pVm, pClass) : rc; + if(rc == SXRET_OK) { + rc = ClassValidateAttributes(pVm, pClass); + } + if(rc == SXRET_OK) { + rc = ClassValidateMethods(pVm, pClass); + } + return rc; } /* * Perform an inheritance operation. @@ -472,20 +578,31 @@ PH7_PRIVATE sxi32 PH7_ClassInherit(ph7_vm *pVm, ph7_class *pSub, ph7_class *pBas SyHashEntry *pEntry; SyString *pName; sxi32 rc; + SXUNUSED(pVm); /* Install in the derived hashtable */ rc = SyHashInsert(&pSub->hDerived, (const void *)SyStringData(&pBase->sName), SyStringLength(&pBase->sName), pBase); if(rc != SXRET_OK) { return rc; } - /* Copy all attributes from the base class */ + /* Copy all attributes from the base class. A diamond shares the same + * declaration pointer, which must yield a single inherited entry. */ SyHashResetLoopCursor(&pBase->hAttr); while((pEntry = SyHashGetNextEntry(&pBase->hAttr)) != 0) { - /* Check if attributes are not being redeclared in the subclass and emit WARNING */ + sxi32 bInstalled; + SyHashEntry *pSubEntry; pAttr = (ph7_class_attr *)pEntry->pUserData; pName = &pAttr->sName; - if((pEntry = SyHashGet(&pSub->hAttr, (const void *)pName->zString, pName->nByte)) != 0) { - ph7_class_attr *pSubAttr = (ph7_class_attr *)pEntry->pUserData; - PH7_VmThrowError(pVm, PH7_CTX_WARNING, "Attribute '%z::$%z' hides inherited member '%z::$%z'", &pSubAttr->pClass->sName, pName, &pBase->sName, pName); + bInstalled = FALSE; + SyHashResetLoopCursor(&pSub->hAttr); + while((pSubEntry = SyHashGetNextEntry(&pSub->hAttr)) != 0) { + if(pSubEntry->pUserData == pAttr) { + bInstalled = TRUE; + break; + } + } + if(bInstalled) { + /* Already inherited through another base */ + continue; } /* Install the attribute */ rc = SyHashInsert(&pSub->hAttr, (const void *)pName->zString, pName->nByte, pAttr); @@ -586,25 +703,10 @@ PH7_PRIVATE sxi32 PH7_ClassInterfaceInherit(ph7_class *pSub, ph7_class *pBase) { */ PH7_PRIVATE sxi32 PH7_ClassImplement(ph7_vm *pVm, ph7_class *pMain, ph7_class *pInterface) { ph7_class_method *pMeth; - ph7_class_attr *pAttr; SyHashEntry *pEntry; SyString *pName; - sxi32 rc; - /* First off,copy all constants declared inside the interface */ - SyHashResetLoopCursor(&pInterface->hAttr); - while((pEntry = SyHashGetNextEntry(&pInterface->hAttr)) != 0) { - /* Point to the constant declaration */ - pAttr = (ph7_class_attr *)pEntry->pUserData; - pName = &pAttr->sName; - /* Make sure the attribute is not redeclared in the main class */ - if(SyHashGet(&pMain->hAttr, pName->zString, pName->nByte) == 0) { - /* Install the attribute */ - rc = SyHashInsert(&pMain->hAttr, pName->zString, pName->nByte, pAttr); - if(rc != SXRET_OK) { - return rc; - } - } - } + /* Interface constants are not inherited by the implementing class. + * They remain reachable through the interface name only. */ SyHashResetLoopCursor(&pInterface->hMethod); while((pEntry = SyHashGetNextEntry(&pInterface->hMethod)) != 0) { pMeth = (ph7_class_method *)pEntry->pUserData; diff --git a/engine/vm.c b/engine/vm.c index 6191d55..dd1781b 100644 --- a/engine/vm.c +++ b/engine/vm.c @@ -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) { diff --git a/include/ph7int.h b/include/ph7int.h index abe9ebd..fd47b99 100644 --- a/include/ph7int.h +++ b/include/ph7int.h @@ -1768,7 +1768,9 @@ PH7_PRIVATE ph7_class_method *PH7_NewClassMethod(ph7_vm *pVm, ph7_class *pClass, sxi32 iProtection, sxi32 iFlags, sxi32 iFuncFlags); PH7_PRIVATE ph7_class_method *PH7_ClassExtractMethod(ph7_class *pClass, const char *zName, sxu32 nByte); PH7_PRIVATE ph7_class_attr *PH7_ClassExtractAttribute(ph7_class *pClass, const char *zName, sxu32 nByte); -PH7_PRIVATE sxi32 PH7_ClassInstallAttr(ph7_class *pClass, ph7_class_attr *pAttr); +PH7_PRIVATE void PH7_ClassAttrDiagnosticName(ph7_class *pClass, sxi32 iFlags, const SyString *pName, + char *zBuf, sxu32 nBuf, SyString *pOut); +PH7_PRIVATE sxi32 PH7_ClassInstallAttr(ph7_gen_state *pGen, ph7_class *pClass, ph7_class_attr *pAttr); PH7_PRIVATE sxi32 PH7_ClassInstallMethod(ph7_class *pClass, ph7_class_method *pMeth); PH7_PRIVATE sxi32 PH7_ClassInherit(ph7_vm *pVm, ph7_class *pSub, ph7_class *pBase); PH7_PRIVATE sxi32 PH7_ClassBuildMro(ph7_vm *pVm, ph7_class *pClass);