diff --git a/src/Translator.php b/src/Translator.php index 3b1950de..79aea122 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -1637,7 +1637,7 @@ CODE; } if ($property->arrayInitPlan) { $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"; $initBlock .= $this->wrapArrayInitPlan($property->arrayInitPlan, $init); } else { @@ -1651,43 +1651,22 @@ CODE; : $property->default; $init = "do {\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 .= "} while (0);\n"; $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; - if ($attachHandlers) { - // PHP < 8.4: the custom handlers are attached to the object - // AFTER the standard create_object, so object_properties_init - // runs with the standard handlers (no asymmetric check). Our - // explicit default inits run with the custom handlers - // attached, so we set EG(fake_scope) to the object's own class - // 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_get_std_object_handlers());\n"; - $body .= "object_properties_init(obj, class_type);\n"; - $body .= $initBlock; - $body .= "obj->handlers = &{$handlers};\n"; - } + $body .= "auto obj = typephp_create_object_with_defaults(\n"; + $body .= "class_type, create_object_{$className}, &{$handlers}, "; + $body .= ($delegateToParentAllocator ? 'true' : 'false') . ",\n"; + $body .= "[&](zend_object *obj) {\n"; + $body .= $initBlock; + $body .= "});\n"; $body .= $classDef->ctorClean; return $body . "return obj;\n"; }; @@ -1696,13 +1675,13 @@ CODE; $code .= "#if (PHP_VERSION_ID < 80400)\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 .= $buildCreateBody(true); + $code .= $buildCreateBody(); $code .= "};\n"; if ($classDef->requireCtor || $this->classHasAsymmetricOrHookedProperty($classDef)) { $code .= "#else\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 .= $buildCreateBody(false); + $code .= $buildCreateBody(); $code .= "};\n"; } $code .= "#endif\n"; @@ -1751,6 +1730,36 @@ CODE; 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 { return self::PREFIX . 'register_class_' . $name; diff --git a/tests/compiler/object_property/default-expressions-inheritance.phpt b/tests/compiler/object_property/default-expressions-inheritance.phpt index fc517d24..697ecfe8 100644 --- a/tests/compiler/object_property/default-expressions-inheritance.phpt +++ b/tests/compiler/object_property/default-expressions-inheritance.phpt @@ -18,6 +18,7 @@ class PropertyDefaultsParent public int $sum = 1 + 2; public float $ratio = 2; protected string $label = self::LABEL_PREFIX . '-value'; + protected array $options = ['mode' => 'parent']; public static int $counter = GLOBAL_NUMBER + 1; public static float $staticRatio = 3; @@ -25,6 +26,11 @@ class PropertyDefaultsParent { return $this->label; } + + public function options(): array + { + return $this->options; + } } class PropertyDefaultsChild extends PropertyDefaultsParent @@ -35,7 +41,7 @@ class PropertyDefaultsChild extends PropertyDefaultsParent function main(): void { $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( PropertyDefaultsChild::$counter, PropertyDefaultsChild::$staticRatio, @@ -47,6 +53,10 @@ function main(): void int(3) float(2) string(12) "parent-value" +array(1) { + ["mode"]=> + string(6) "parent" +} int(11) int(5) float(3)