fix(runtime): correct property inheritance handling in object creation

- Fixed zend_update_property call to use correct class entry variable
- Refactored object creation logic to delegate to parent allocator when needed
- Added parentHasCustomCreateObjectOnPhp84 method to detect parent class properties
- Simplified create object handler implementation using typephp_create_object_with_defaults
- Updated property initialization to work properly with asymmetric visibility
- Modified test case to include options property verification in inheritance scenario
pull/34/head v0.4.0
韩天峰 2 months ago
parent 9009a0b5fa
commit 90b61fbc42
  1. 75
      src/Translator.php
  2. 12
      tests/compiler/object_property/default-expressions-inheritance.phpt

@ -1637,7 +1637,7 @@ CODE;
} }
if ($property->arrayInitPlan) { if ($property->arrayInitPlan) {
$init = "auto value = {$property->arrayInitPlan->expr};\n"; $init = "auto value = {$property->arrayInitPlan->expr};\n";
$init .= 'zend_update_property(obj->ce, obj, ' . $this->genZendStrl($property->name) . ", value.ptr());\n"; $init .= 'zend_update_property(' . $ce . ', obj, ' . $this->genZendStrl($property->name) . ", value.ptr());\n";
$init .= "php::throwErrorIfOccurred();\n"; $init .= "php::throwErrorIfOccurred();\n";
$initBlock .= $this->wrapArrayInitPlan($property->arrayInitPlan, $init); $initBlock .= $this->wrapArrayInitPlan($property->arrayInitPlan, $init);
} else { } else {
@ -1651,43 +1651,22 @@ CODE;
: $property->default; : $property->default;
$init = "do {\n"; $init = "do {\n";
$init .= "auto value = php::Var({$default});\n"; $init .= "auto value = php::Var({$default});\n";
$init .= 'zend_update_property(obj->ce, obj, ' . $this->genZendStrl($property->name) . ", value.ptr());\n"; $init .= 'zend_update_property(' . $ce . ', obj, ' . $this->genZendStrl($property->name) . ", value.ptr());\n";
$init .= "php::throwErrorIfOccurred();\n"; $init .= "php::throwErrorIfOccurred();\n";
$init .= "} while (0);\n"; $init .= "} while (0);\n";
$initBlock .= $init; $initBlock .= $init;
} }
} }
$buildCreateBody = function (bool $attachHandlers) use ($classDef, $className, $handlers, $ce, $initBlock): string { $delegateToParentAllocator = $this->parentHasCustomCreateObjectOnPhp84($classDef);
$buildCreateBody = function () use ($classDef, $className, $handlers, $initBlock, $delegateToParentAllocator): string {
$body = $classDef->ctorInit; $body = $classDef->ctorInit;
if ($attachHandlers) { $body .= "auto obj = typephp_create_object_with_defaults(\n";
// PHP < 8.4: the custom handlers are attached to the object $body .= "class_type, create_object_{$className}, &{$handlers}, ";
// AFTER the standard create_object, so object_properties_init $body .= ($delegateToParentAllocator ? 'true' : 'false') . ",\n";
// runs with the standard handlers (no asymmetric check). Our $body .= "[&](zend_object *obj) {\n";
// explicit default inits run with the custom handlers $body .= $initBlock;
// attached, so we set EG(fake_scope) to the object's own class $body .= "});\n";
// to satisfy asymmetric visibility for the class's own
// properties.
$body .= "auto obj = create_object_{$className}(class_type);\n";
$body .= "typephp_attach_property_handlers(obj, &{$handlers});\n";
$body .= "zend_class_entry *__typephp_saved_fake_scope = EG(fake_scope);\n";
$body .= "EG(fake_scope) = obj->ce;\n";
$body .= $initBlock;
$body .= "EG(fake_scope) = __typephp_saved_fake_scope;\n";
} else {
// PHP >= 8.4: the custom handlers live in
// default_object_handlers, so the object already carries the
// asymmetric write_property hook at creation time and
// object_properties_init would reject private(set)/protected(set)
// default values (including inherited ones). Create the object
// with the standard handlers, run the default initialization
// (no visibility check), then attach the custom handlers.
$body .= "auto obj = zend_objects_new(class_type);\n";
$body .= "obj->handlers = const_cast<zend_object_handlers *>(zend_get_std_object_handlers());\n";
$body .= "object_properties_init(obj, class_type);\n";
$body .= $initBlock;
$body .= "obj->handlers = &{$handlers};\n";
}
$body .= $classDef->ctorClean; $body .= $classDef->ctorClean;
return $body . "return obj;\n"; return $body . "return obj;\n";
}; };
@ -1696,13 +1675,13 @@ CODE;
$code .= "#if (PHP_VERSION_ID < 80400)\n"; $code .= "#if (PHP_VERSION_ID < 80400)\n";
$code .= "create_object_{$className} = php_get_create_object_fn({$ce});\n"; $code .= "create_object_{$className} = php_get_create_object_fn({$ce});\n";
$code .= "{$ce}->create_object = [](zend_class_entry *class_type) -> zend_object* {\n"; $code .= "{$ce}->create_object = [](zend_class_entry *class_type) -> zend_object* {\n";
$code .= $buildCreateBody(true); $code .= $buildCreateBody();
$code .= "};\n"; $code .= "};\n";
if ($classDef->requireCtor || $this->classHasAsymmetricOrHookedProperty($classDef)) { if ($classDef->requireCtor || $this->classHasAsymmetricOrHookedProperty($classDef)) {
$code .= "#else\n"; $code .= "#else\n";
$code .= "create_object_{$className} = php_get_create_object_fn({$ce});\n"; $code .= "create_object_{$className} = php_get_create_object_fn({$ce});\n";
$code .= "{$ce}->create_object = [](zend_class_entry *class_type) -> zend_object* {\n"; $code .= "{$ce}->create_object = [](zend_class_entry *class_type) -> zend_object* {\n";
$code .= $buildCreateBody(false); $code .= $buildCreateBody();
$code .= "};\n"; $code .= "};\n";
} }
$code .= "#endif\n"; $code .= "#endif\n";
@ -1751,6 +1730,36 @@ CODE;
return false; return false;
} }
private function parentHasCustomCreateObjectOnPhp84(ClassDef $classDef): bool
{
if ($classDef->extends === '') {
return false;
}
if ($classDef->inheritedFromInternalClass) {
return true;
}
$parent = $this->getClassDef($classDef->extends);
while ($parent !== null) {
foreach ($parent->properties as $property) {
if (!$property->isStatic() && $property->default !== null) {
return true;
}
}
if ($this->classHasAsymmetricOrHookedProperty($parent)) {
return true;
}
if ($parent->extends === '') {
break;
}
if ($parent->inheritedFromInternalClass) {
return true;
}
$parent = $this->getClassDef($parent->extends);
}
return false;
}
protected function getRegisterClassFunction(string $name): string protected function getRegisterClassFunction(string $name): string
{ {
return self::PREFIX . 'register_class_' . $name; return self::PREFIX . 'register_class_' . $name;

@ -18,6 +18,7 @@ class PropertyDefaultsParent
public int $sum = 1 + 2; public int $sum = 1 + 2;
public float $ratio = 2; public float $ratio = 2;
protected string $label = self::LABEL_PREFIX . '-value'; protected string $label = self::LABEL_PREFIX . '-value';
protected array $options = ['mode' => 'parent'];
public static int $counter = GLOBAL_NUMBER + 1; public static int $counter = GLOBAL_NUMBER + 1;
public static float $staticRatio = 3; public static float $staticRatio = 3;
@ -25,6 +26,11 @@ class PropertyDefaultsParent
{ {
return $this->label; return $this->label;
} }
public function options(): array
{
return $this->options;
}
} }
class PropertyDefaultsChild extends PropertyDefaultsParent class PropertyDefaultsChild extends PropertyDefaultsParent
@ -35,7 +41,7 @@ class PropertyDefaultsChild extends PropertyDefaultsParent
function main(): void function main(): void
{ {
$value = new PropertyDefaultsChild(); $value = new PropertyDefaultsChild();
var_dump($value->sum, $value->ratio, $value->label(), $value->traitValue); var_dump($value->sum, $value->ratio, $value->label(), $value->options(), $value->traitValue);
var_dump( var_dump(
PropertyDefaultsChild::$counter, PropertyDefaultsChild::$counter,
PropertyDefaultsChild::$staticRatio, PropertyDefaultsChild::$staticRatio,
@ -47,6 +53,10 @@ function main(): void
int(3) int(3)
float(2) float(2)
string(12) "parent-value" string(12) "parent-value"
array(1) {
["mode"]=>
string(6) "parent"
}
int(11) int(11)
int(5) int(5)
float(3) float(3)

Loading…
Cancel
Save