From d0f794cf04576b5656da5d2d829dba43bb792f6d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 21 Jan 2026 20:26:47 +0100 Subject: [PATCH 1/9] Allow promoted readonly property to be reassigned once in constructor --- .../readonly_props/cpp_reassign_basic.phpt | 28 ++++ .../cpp_reassign_child_class.phpt | 68 ++++++++++ .../cpp_reassign_conditional.phpt | 23 ++++ .../cpp_reassign_different_object.phpt | 66 ++++++++++ .../cpp_reassign_indirect_fail.phpt | 50 ++++++++ .../cpp_reassign_indirect_ops.phpt | 68 ++++++++++ .../cpp_reassign_multiple_fail.phpt | 25 ++++ .../cpp_reassign_nonpromoted.phpt | 49 +++++++ .../cpp_reassign_outside_ctor.phpt | 39 ++++++ .../cpp_reassign_validation.phpt | 30 +++++ Zend/zend_execute.c | 12 +- Zend/zend_object_handlers.c | 24 +++- Zend/zend_object_handlers.h | 14 ++ Zend/zend_types.h | 1 + Zend/zend_vm_def.h | 4 +- Zend/zend_vm_execute.h | 120 +++++++++--------- ext/opcache/jit/zend_jit_helpers.c | 73 ++++++----- ext/opcache/jit/zend_jit_ir.c | 22 ++-- 18 files changed, 603 insertions(+), 113 deletions(-) create mode 100644 Zend/tests/readonly_props/cpp_reassign_basic.phpt create mode 100644 Zend/tests/readonly_props/cpp_reassign_child_class.phpt create mode 100644 Zend/tests/readonly_props/cpp_reassign_conditional.phpt create mode 100644 Zend/tests/readonly_props/cpp_reassign_different_object.phpt create mode 100644 Zend/tests/readonly_props/cpp_reassign_indirect_fail.phpt create mode 100644 Zend/tests/readonly_props/cpp_reassign_indirect_ops.phpt create mode 100644 Zend/tests/readonly_props/cpp_reassign_multiple_fail.phpt create mode 100644 Zend/tests/readonly_props/cpp_reassign_nonpromoted.phpt create mode 100644 Zend/tests/readonly_props/cpp_reassign_outside_ctor.phpt create mode 100644 Zend/tests/readonly_props/cpp_reassign_validation.phpt diff --git a/Zend/tests/readonly_props/cpp_reassign_basic.phpt b/Zend/tests/readonly_props/cpp_reassign_basic.phpt new file mode 100644 index 0000000000000..3725baec13b0e --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_basic.phpt @@ -0,0 +1,28 @@ +--TEST-- +Promoted readonly property reassignment in constructor - basic +--FILE-- +x = abs($x); + $this->y = abs($y); + } +} + +$point = new Point(); +var_dump($point->x, $point->y); + +$point2 = new Point(-5.0, -3.0); +var_dump($point2->x, $point2->y); + +?> +--EXPECT-- +float(0) +float(0) +float(5) +float(3) diff --git a/Zend/tests/readonly_props/cpp_reassign_child_class.phpt b/Zend/tests/readonly_props/cpp_reassign_child_class.phpt new file mode 100644 index 0000000000000..c9f8dc2f867e6 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_child_class.phpt @@ -0,0 +1,68 @@ +--TEST-- +Promoted readonly property reassignment in constructor - child class cannot reassign parent's property +--FILE-- +prop = 'parent set'; + } +} + +class Child extends Parent_ { + public function __construct() { + parent::__construct(); + // Child cannot reassign parent's promoted property + try { + $this->prop = 'child override'; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } +} + +$parent = new Parent_(); +var_dump($parent->prop); + +$child = new Child(); +var_dump($child->prop); + +// Even when child has its own promoted property +class Parent2 { + public function __construct( + public readonly string $parentProp = 'parent default', + ) { + $this->parentProp = 'parent set'; + } +} + +class Child2 extends Parent2 { + public function __construct( + public readonly string $childProp = 'child default', + ) { + parent::__construct(); + // Child can reassign its own promoted property + $this->childProp = 'child set'; + // But cannot reassign parent's promoted property + try { + $this->parentProp = 'child override'; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } +} + +$child2 = new Child2(); +var_dump($child2->parentProp, $child2->childProp); + +?> +--EXPECT-- +string(10) "parent set" +Cannot modify readonly property Parent_::$prop +string(10) "parent set" +Cannot modify readonly property Parent2::$parentProp +string(10) "parent set" +string(9) "child set" diff --git a/Zend/tests/readonly_props/cpp_reassign_conditional.phpt b/Zend/tests/readonly_props/cpp_reassign_conditional.phpt new file mode 100644 index 0000000000000..6c751d72af7d4 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_conditional.phpt @@ -0,0 +1,23 @@ +--TEST-- +Promoted readonly property reassignment in constructor - conditional initialization +--FILE-- +cacheDir ??= sys_get_temp_dir() . '/app_cache'; + } +} + +$config1 = new Config(); +var_dump(str_contains($config1->cacheDir, 'app_cache')); + +$config2 = new Config('/custom/cache'); +var_dump($config2->cacheDir); + +?> +--EXPECT-- +bool(true) +string(13) "/custom/cache" diff --git a/Zend/tests/readonly_props/cpp_reassign_different_object.phpt b/Zend/tests/readonly_props/cpp_reassign_different_object.phpt new file mode 100644 index 0000000000000..da81c17be8ae5 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_different_object.phpt @@ -0,0 +1,66 @@ +--TEST-- +Promoted readonly property reassignment in constructor - different object fails +--FILE-- +x = abs($x); + } + + public static function createFrom(Point $other): Point { + $new = new self(); + // Cannot modify another object's readonly property + try { + $other->x = 999.0; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + return $new; + } +} + +$p1 = new Point(-5.0); +var_dump($p1->x); + +$p2 = Point::createFrom($p1); +var_dump($p1->x); // Unchanged + +// Also test: constructor cannot modify another instance of the same class +class Counter { + private static ?Counter $last = null; + + public function __construct( + public readonly int $value = 0, + ) { + $this->value = $value + 1; // Allowed: own property + + // Cannot modify previous instance + if (self::$last !== null) { + try { + self::$last->value = 999; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } + self::$last = $this; + } +} + +$c1 = new Counter(10); +var_dump($c1->value); + +$c2 = new Counter(20); +var_dump($c1->value, $c2->value); // $c1 unchanged + +?> +--EXPECT-- +float(5) +Cannot modify readonly property Point::$x +float(5) +int(11) +Cannot modify readonly property Counter::$value +int(11) +int(21) diff --git a/Zend/tests/readonly_props/cpp_reassign_indirect_fail.phpt b/Zend/tests/readonly_props/cpp_reassign_indirect_fail.phpt new file mode 100644 index 0000000000000..64400c0abb5fd --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_indirect_fail.phpt @@ -0,0 +1,50 @@ +--TEST-- +Promoted readonly property reassignment in constructor - indirect reassignment not allowed +--FILE-- +initProp(); + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } + + private function initProp(): void { + $this->prop = 'from method'; + } +} + +$cm = new CalledMethod(); +var_dump($cm->prop); + +// Reassignment is NOT allowed in closures called by the constructor +class ClosureInConstructor { + public function __construct( + public readonly string $prop = 'default', + ) { + $fn = function() { + $this->prop = 'from closure'; + }; + try { + $fn(); + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } +} + +$cc = new ClosureInConstructor(); +var_dump($cc->prop); + +?> +--EXPECT-- +Cannot modify readonly property CalledMethod::$prop +string(7) "default" +Cannot modify readonly property ClosureInConstructor::$prop +string(7) "default" diff --git a/Zend/tests/readonly_props/cpp_reassign_indirect_ops.phpt b/Zend/tests/readonly_props/cpp_reassign_indirect_ops.phpt new file mode 100644 index 0000000000000..da51ad6be4661 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_indirect_ops.phpt @@ -0,0 +1,68 @@ +--TEST-- +Promoted readonly property reassignment in constructor - indirect operations (++, --, +=) +--FILE-- +count++; + } +} + +$c = new Counter(5); +var_dump($c->count); + +// Multiple operations count as reassignments - second fails +class MultiOp { + public function __construct( + public readonly int $value = 10, + ) { + $this->value += 5; // First modification - allowed + try { + $this->value++; // Second modification - should fail + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } +} + +$m = new MultiOp(); +var_dump($m->value); + +// Decrement works too +class Decrement { + public function __construct( + public readonly int $value = 100, + ) { + $this->value--; + } +} + +$d = new Decrement(); +var_dump($d->value); + +// Assignment operators work +class AssignOps { + public function __construct( + public readonly string $text = 'hello', + ) { + $this->text .= ' world'; + } +} + +$a = new AssignOps(); +var_dump($a->text); + +?> +--EXPECT-- +int(6) +Cannot modify readonly property MultiOp::$value +int(15) +int(99) +string(11) "hello world" diff --git a/Zend/tests/readonly_props/cpp_reassign_multiple_fail.phpt b/Zend/tests/readonly_props/cpp_reassign_multiple_fail.phpt new file mode 100644 index 0000000000000..f66ef39aebb63 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_multiple_fail.phpt @@ -0,0 +1,25 @@ +--TEST-- +Promoted readonly property reassignment in constructor - multiple reassignments fail +--FILE-- +value = 'first'; // OK - first reassignment + try { + $this->value = 'second'; // Error - second reassignment + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } +} + +$ex = new Example(); +var_dump($ex->value); + +?> +--EXPECT-- +Cannot modify readonly property Example::$value +string(5) "first" diff --git a/Zend/tests/readonly_props/cpp_reassign_nonpromoted.phpt b/Zend/tests/readonly_props/cpp_reassign_nonpromoted.phpt new file mode 100644 index 0000000000000..0b0c0f311e8c8 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_nonpromoted.phpt @@ -0,0 +1,49 @@ +--TEST-- +Promoted readonly property reassignment in constructor - non-promoted properties unchanged +--FILE-- +prop = 'first'; + try { + $this->prop = 'second'; // Should fail - not a promoted property + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } +} + +$np = new NonPromoted(); +var_dump($np->prop); + +// Test mixed: promoted and non-promoted in same class +class MixedProps { + public readonly string $nonPromoted; + + public function __construct( + public readonly string $promoted = 'default', + ) { + $this->nonPromoted = 'first'; + $this->promoted = 'reassigned'; // Allowed (promoted, first reassignment) + try { + $this->nonPromoted = 'second'; // Should fail (non-promoted) + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } +} + +$m = new MixedProps(); +var_dump($m->promoted, $m->nonPromoted); + +?> +--EXPECT-- +Cannot modify readonly property NonPromoted::$prop +string(5) "first" +Cannot modify readonly property MixedProps::$nonPromoted +string(10) "reassigned" +string(5) "first" diff --git a/Zend/tests/readonly_props/cpp_reassign_outside_ctor.phpt b/Zend/tests/readonly_props/cpp_reassign_outside_ctor.phpt new file mode 100644 index 0000000000000..eb503f1db18e8 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_outside_ctor.phpt @@ -0,0 +1,39 @@ +--TEST-- +Promoted readonly property reassignment in constructor - outside constructor fails +--FILE-- +x = abs($x); + } + + public function tryModify(): void { + $this->x = 999.0; + } +} + +$point = new Point(-5.0); +var_dump($point->x); + +// Cannot reassign from outside constructor +try { + $point->x = 100.0; +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +// Cannot reassign from a method +try { + $point->tryModify(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +float(5) +Cannot modify readonly property Point::$x +Cannot modify readonly property Point::$x diff --git a/Zend/tests/readonly_props/cpp_reassign_validation.phpt b/Zend/tests/readonly_props/cpp_reassign_validation.phpt new file mode 100644 index 0000000000000..6539c3ef3316e --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_validation.phpt @@ -0,0 +1,30 @@ +--TEST-- +Promoted readonly property reassignment in constructor - validation +--FILE-- +email = strtolower($email); // Normalize + } +} + +$user = new User('TEST@Example.COM'); +var_dump($user->email); + +try { + new User('not-an-email'); +} catch (InvalidArgumentException $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +string(16) "test@example.com" +Invalid email diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 37278c5cb9a23..ef4d09c980dc2 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1068,14 +1068,16 @@ ZEND_API bool zend_never_inline zend_verify_property_type(const zend_property_in return i_zend_verify_property_type(info, property, strict); } -static zend_never_inline zval* zend_assign_to_typed_prop(const zend_property_info *info, zval *property_val, zval *value, zend_refcounted **garbage_ptr EXECUTE_DATA_DC) +static zend_never_inline zval* zend_assign_to_typed_prop(const zend_property_info *info, zval *property_val, zval *value, zend_refcounted **garbage_ptr, zend_object *zobj EXECUTE_DATA_DC) { zval tmp; if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { - if ((info->flags & ZEND_ACC_READONLY) && !(Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE)) { - zend_readonly_property_modification_error(info); - return &EG(uninitialized_zval); + if (info->flags & ZEND_ACC_READONLY) { + if (!zend_is_readonly_property_modifiable(property_val, info, zobj)) { + zend_readonly_property_modification_error(info); + return &EG(uninitialized_zval); + } } if (info->flags & ZEND_ACC_PPP_SET_MASK && !zend_asymmetric_property_has_set_access(info)) { zend_asymmetric_visibility_property_modification_error(info, "modify"); @@ -1091,7 +1093,7 @@ static zend_never_inline zval* zend_assign_to_typed_prop(const zend_property_inf return &EG(uninitialized_zval); } - Z_PROP_FLAG_P(property_val) &= ~IS_PROP_REINITABLE; + Z_PROP_FLAG_P(property_val) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); return zend_assign_to_variable_ex(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES(), garbage_ptr); } diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 45eac02949d15..ef562012199da 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -46,6 +46,18 @@ #define IN_ISSET ZEND_GUARD_PROPERTY_ISSET #define IN_HOOK ZEND_GUARD_PROPERTY_HOOK +/* Check if we're in the constructor of the class that declared the property. + * Ensures that a child class cannot reassign a parent's promoted readonly property. */ +ZEND_API bool zend_is_in_property_declaring_class_constructor(const zend_property_info *prop_info, const zend_object *zobj) +{ + zend_execute_data *ex = EG(current_execute_data); + return ex && ex->func + && (ex->func->common.fn_flags & ZEND_ACC_CTOR) + && (ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS) + && Z_OBJ(ex->This) == zobj + && ex->func->common.scope == prop_info->ce; +} + static zend_arg_info zend_call_trampoline_arginfo[1] = {{0}}; static zend_arg_info zend_property_hook_arginfo[1] = {{0}}; @@ -1068,7 +1080,7 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva if (error) { if ((prop_info->flags & ZEND_ACC_READONLY) && Z_TYPE_P(variable_ptr) != IS_UNDEF - && !(Z_PROP_FLAG_P(variable_ptr) & IS_PROP_REINITABLE)) { + && !zend_is_readonly_property_modifiable(variable_ptr, prop_info, zobj)) { zend_readonly_property_modification_error(prop_info); variable_ptr = &EG(error_zval); goto exit; @@ -1102,7 +1114,15 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva variable_ptr = &EG(error_zval); goto exit; } - Z_PROP_FLAG_P(variable_ptr) &= ~(IS_PROP_UNINIT|IS_PROP_REINITABLE); + /* For promoted readonly properties being initialized for the first time, + * set IS_PROP_CPP_REINITABLE to allow one reassignment in the declaring class constructor. */ + if ((prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) + && (Z_PROP_FLAG_P(variable_ptr) & IS_PROP_UNINIT) + && zend_is_in_property_declaring_class_constructor(prop_info, zobj)) { + Z_PROP_FLAG_P(variable_ptr) = IS_PROP_CPP_REINITABLE; + } else { + Z_PROP_FLAG_P(variable_ptr) &= ~(IS_PROP_UNINIT|IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); + } value = &tmp; } diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index 3e922343eb15a..7e3bc13efd747 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -276,6 +276,20 @@ ZEND_API zend_result zend_std_get_closure(zend_object *obj, zend_class_entry **c ZEND_API HashTable *rebuild_object_properties_internal(zend_object *zobj); ZEND_API ZEND_COLD zend_never_inline void zend_bad_method_call(const zend_function *fbc, const zend_string *method_name, const zend_class_entry *scope); ZEND_API ZEND_COLD zend_never_inline void zend_abstract_method_call(const zend_function *fbc); +ZEND_API bool zend_is_in_property_declaring_class_constructor(const zend_property_info *prop_info, const zend_object *zobj); + +/* Check if a readonly property can be modified (has REINITABLE or CPP_REINITABLE flag and is in declaring constructor) */ +static zend_always_inline bool zend_is_readonly_property_modifiable(zval *property_val, const zend_property_info *prop_info, zend_object *zobj) +{ + if (Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE) { + return true; + } + if ((Z_PROP_FLAG_P(property_val) & IS_PROP_CPP_REINITABLE) + && zend_is_in_property_declaring_class_constructor(prop_info, zobj)) { + return true; + } + return false; +} static zend_always_inline HashTable *zend_std_get_properties_ex(zend_object *object) { diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 22dbfa9be879b..6f2b03013c13f 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -1594,6 +1594,7 @@ static zend_always_inline uint32_t zval_delref_p(zval* pz) { #define IS_PROP_UNINIT (1<<0) #define IS_PROP_REINITABLE (1<<1) /* It has impact only on readonly properties */ #define IS_PROP_LAZY (1<<2) +#define IS_PROP_CPP_REINITABLE (1<<3) /* Allows one reassignment of promoted readonly property in constructor */ #define Z_PROP_FLAG_P(z) Z_EXTRA_P(z) #define ZVAL_COPY_VALUE_PROP(z, v) \ do { *(z) = *(v); } while (0) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 86708f8c97a29..9881d983fd778 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2525,7 +2525,7 @@ ZEND_VM_C_LABEL(assign_obj_simple): property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); ZEND_VM_C_GOTO(free_and_exit_assign_obj); } else { ZEND_VM_C_LABEL(fast_assign_obj): @@ -2660,7 +2660,7 @@ ZEND_VM_HANDLER(25, ZEND_ASSIGN_STATIC_PROP, ANY, ANY, CACHE_SLOT, SPEC(OP_DATA= value = GET_OP_DATA_ZVAL_PTR(BP_VAR_R); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage, NULL EXECUTE_DATA_CC); FREE_OP_DATA(); } else { value = zend_assign_to_variable_ex(prop, value, OP_DATA_TYPE, EX_USES_STRICT_TYPES(), &garbage); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index cbfae90802cfa..b3c8e4deadc41 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1001,7 +1001,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_STATIC value = RT_CONSTANT((opline+1), (opline+1)->op1); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage, NULL EXECUTE_DATA_CC); } else { @@ -1039,7 +1039,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_STATIC value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage, NULL EXECUTE_DATA_CC); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); } else { value = zend_assign_to_variable_ex(prop, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); @@ -1077,7 +1077,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_STATIC value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage, NULL EXECUTE_DATA_CC); } else { @@ -24416,7 +24416,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -24573,7 +24573,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -24728,7 +24728,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -27124,7 +27124,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -27280,7 +27280,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -27434,7 +27434,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -30948,7 +30948,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -31105,7 +31105,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -31260,7 +31260,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -33482,7 +33482,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -33640,7 +33640,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -33796,7 +33796,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -35580,7 +35580,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -35737,7 +35737,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -35892,7 +35892,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -38149,7 +38149,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -38307,7 +38307,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -38463,7 +38463,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -42518,7 +42518,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -42676,7 +42676,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -42832,7 +42832,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -46341,7 +46341,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -46498,7 +46498,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -46653,7 +46653,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -51455,7 +51455,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -51613,7 +51613,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -51769,7 +51769,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -53721,7 +53721,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_STATIC_PROP value = RT_CONSTANT((opline+1), (opline+1)->op1); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage, NULL EXECUTE_DATA_CC); } else { @@ -53759,7 +53759,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_STATIC_PROP value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage, NULL EXECUTE_DATA_CC); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); } else { value = zend_assign_to_variable_ex(prop, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); @@ -53797,7 +53797,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_STATIC_PROP value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage, NULL EXECUTE_DATA_CC); } else { @@ -76818,7 +76818,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -76975,7 +76975,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -77130,7 +77130,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -79526,7 +79526,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -79682,7 +79682,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -79836,7 +79836,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -83350,7 +83350,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -83507,7 +83507,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -83662,7 +83662,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -85884,7 +85884,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -86042,7 +86042,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -86198,7 +86198,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -87982,7 +87982,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -88139,7 +88139,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -88294,7 +88294,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -90551,7 +90551,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -90709,7 +90709,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -90865,7 +90865,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -94920,7 +94920,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -95078,7 +95078,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -95234,7 +95234,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -98743,7 +98743,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -98900,7 +98900,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -99055,7 +99055,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -103755,7 +103755,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -103913,7 +103913,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -104069,7 +104069,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 59bb9401d9a98..e9e3348393339 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -2803,12 +2803,15 @@ static void ZEND_FASTCALL zend_jit_assign_obj_helper(zend_object *zobj, zend_str } } -static zend_always_inline bool verify_readonly_and_avis(zval *property_val, zend_property_info *info, bool indirect) +static zend_always_inline bool verify_readonly_and_avis(zval *property_val, zend_property_info *info, bool indirect, zend_object *zobj) { if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { - if ((info->flags & ZEND_ACC_READONLY) && !(Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE)) { - zend_readonly_property_modification_error(info); - return false; + if (info->flags & ZEND_ACC_READONLY) { + ZEND_ASSERT(zobj != NULL); /* CPP_REINITABLE only applies to instance properties */ + if (!zend_is_readonly_property_modifiable(property_val, info, zobj)) { + zend_readonly_property_modification_error(info); + return false; + } } if ((info->flags & ZEND_ACC_PPP_SET_MASK) && !zend_asymmetric_property_has_set_access(info)) { const char *operation = indirect ? "indirectly modify" : "modify"; @@ -2819,7 +2822,7 @@ static zend_always_inline bool verify_readonly_and_avis(zval *property_val, zend return true; } -static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend_property_info *info, zval *value, zval *result) +static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend_property_info *info, zval *value, zval *result, zend_object *zobj) { zend_execute_data *execute_data = EG(current_execute_data); zend_refcounted *garbage = NULL; @@ -2832,7 +2835,7 @@ static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend value = &EG(uninitialized_zval); } - if (UNEXPECTED(!verify_readonly_and_avis(property_val, info, false))) { + if (UNEXPECTED(!verify_readonly_and_avis(property_val, info, false, zobj))) { if (result) { ZVAL_UNDEF(result); } @@ -2850,7 +2853,7 @@ static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend return; } - Z_PROP_FLAG_P(property_val) &= ~IS_PROP_REINITABLE; + Z_PROP_FLAG_P(property_val) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); value = zend_assign_to_variable_ex(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); if (result) { @@ -2888,12 +2891,12 @@ static zend_never_inline void _zend_jit_assign_op_overloaded_property(zend_objec OBJ_RELEASE(object); } -static void ZEND_FASTCALL zend_jit_assign_op_to_typed_prop(zval *zptr, zend_property_info *prop_info, zval *value, binary_op_type binary_op) +static void ZEND_FASTCALL zend_jit_assign_op_to_typed_prop(zval *zptr, zend_property_info *prop_info, zval *value, binary_op_type binary_op, zend_object *zobj) { zend_execute_data *execute_data = EG(current_execute_data); zval z_copy; - if (UNEXPECTED(!verify_readonly_and_avis(zptr, prop_info, true))) { + if (UNEXPECTED(!verify_readonly_and_avis(zptr, prop_info, true, zobj))) { return; } @@ -2907,7 +2910,7 @@ static void ZEND_FASTCALL zend_jit_assign_op_to_typed_prop(zval *zptr, zend_prop binary_op(&z_copy, zptr, value); if (EXPECTED(zend_verify_property_type(prop_info, &z_copy, EX_USES_STRICT_TYPES()))) { - Z_PROP_FLAG_P(zptr) &= ~IS_PROP_REINITABLE; + Z_PROP_FLAG_P(zptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); zval_ptr_dtor(zptr); ZVAL_COPY_VALUE(zptr, &z_copy); } else { @@ -2946,7 +2949,7 @@ static void ZEND_FASTCALL zend_jit_assign_obj_op_helper(zend_object *zobj, zend_ //??? } if (prop_info && ZEND_TYPE_IS_SET(prop_info->type)) { /* special case for typed properties */ - zend_jit_assign_op_to_typed_prop(zptr, prop_info, value, binary_op); + zend_jit_assign_op_to_typed_prop(zptr, prop_info, value, binary_op, zobj); } else { binary_op(zptr, zptr, value); } @@ -2983,11 +2986,11 @@ static ZEND_COLD zend_long _zend_jit_throw_dec_prop_error(zend_property_info *pr return ZEND_LONG_MIN; } -static void ZEND_FASTCALL zend_jit_inc_typed_prop(zval *var_ptr, zend_property_info *prop_info) +static void ZEND_FASTCALL zend_jit_inc_typed_prop(zval *var_ptr, zend_property_info *prop_info, zend_object *zobj) { ZEND_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF); - if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true))) { + if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true, zobj))) { return; } @@ -3004,22 +3007,22 @@ static void ZEND_FASTCALL zend_jit_inc_typed_prop(zval *var_ptr, zend_property_i zend_long val = _zend_jit_throw_inc_prop_error(prop_info); ZVAL_LONG(var_ptr, val); } else { - Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; + Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); } } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) { zval_ptr_dtor(var_ptr); ZVAL_COPY_VALUE(var_ptr, &tmp); } else { - Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; + Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); zval_ptr_dtor(&tmp); } } -static void ZEND_FASTCALL zend_jit_dec_typed_prop(zval *var_ptr, zend_property_info *prop_info) +static void ZEND_FASTCALL zend_jit_dec_typed_prop(zval *var_ptr, zend_property_info *prop_info, zend_object *zobj) { ZEND_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF); - if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true))) { + if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true, zobj))) { return; } @@ -3036,36 +3039,36 @@ static void ZEND_FASTCALL zend_jit_dec_typed_prop(zval *var_ptr, zend_property_i zend_long val = _zend_jit_throw_dec_prop_error(prop_info); ZVAL_LONG(var_ptr, val); } else { - Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; + Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); } } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) { zval_ptr_dtor(var_ptr); ZVAL_COPY_VALUE(var_ptr, &tmp); } else { - Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; + Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); zval_ptr_dtor(&tmp); } } -static void ZEND_FASTCALL zend_jit_pre_inc_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result) +static void ZEND_FASTCALL zend_jit_pre_inc_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result, zend_object *zobj) { ZVAL_DEREF(var_ptr); - zend_jit_inc_typed_prop(var_ptr, prop_info); + zend_jit_inc_typed_prop(var_ptr, prop_info, zobj); ZVAL_COPY(result, var_ptr); } -static void ZEND_FASTCALL zend_jit_pre_dec_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result) +static void ZEND_FASTCALL zend_jit_pre_dec_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result, zend_object *zobj) { ZVAL_DEREF(var_ptr); - zend_jit_dec_typed_prop(var_ptr, prop_info); + zend_jit_dec_typed_prop(var_ptr, prop_info, zobj); ZVAL_COPY(result, var_ptr); } -static void ZEND_FASTCALL zend_jit_post_inc_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result) +static void ZEND_FASTCALL zend_jit_post_inc_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result, zend_object *zobj) { ZEND_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF); - if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true))) { + if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true, zobj))) { if (result) { ZVAL_UNDEF(result); } @@ -3084,22 +3087,22 @@ static void ZEND_FASTCALL zend_jit_post_inc_typed_prop(zval *var_ptr, zend_prope zend_long val = _zend_jit_throw_inc_prop_error(prop_info); ZVAL_LONG(var_ptr, val); } else { - Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; + Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); } } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) { zval_ptr_dtor(var_ptr); ZVAL_COPY_VALUE(var_ptr, result); ZVAL_UNDEF(result); } else { - Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; + Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); } } -static void ZEND_FASTCALL zend_jit_post_dec_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result) +static void ZEND_FASTCALL zend_jit_post_dec_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result, zend_object *zobj) { ZEND_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF); - if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true))) { + if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true, zobj))) { if (result) { ZVAL_UNDEF(result); } @@ -3118,14 +3121,14 @@ static void ZEND_FASTCALL zend_jit_post_dec_typed_prop(zval *var_ptr, zend_prope zend_long val = _zend_jit_throw_dec_prop_error(prop_info); ZVAL_LONG(var_ptr, val); } else { - Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; + Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); } } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) { zval_ptr_dtor(var_ptr); ZVAL_COPY_VALUE(var_ptr, result); ZVAL_UNDEF(result); } else { - Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; + Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); } } @@ -3163,7 +3166,7 @@ static void ZEND_FASTCALL zend_jit_pre_inc_obj_helper(zend_object *zobj, zend_st } if (prop_info) { - zend_jit_inc_typed_prop(prop, prop_info); + zend_jit_inc_typed_prop(prop, prop_info, zobj); } else { increment_function(prop); } @@ -3236,7 +3239,7 @@ static void ZEND_FASTCALL zend_jit_pre_dec_obj_helper(zend_object *zobj, zend_st } if (prop_info) { - zend_jit_dec_typed_prop(prop, prop_info); + zend_jit_dec_typed_prop(prop, prop_info, zobj); } else { decrement_function(prop); } @@ -3307,7 +3310,7 @@ static void ZEND_FASTCALL zend_jit_post_inc_obj_helper(zend_object *zobj, zend_s } if (prop_info) { - zend_jit_post_inc_typed_prop(prop, prop_info, result); + zend_jit_post_inc_typed_prop(prop, prop_info, result, zobj); } else { ZVAL_COPY(result, prop); increment_function(prop); @@ -3371,7 +3374,7 @@ static void ZEND_FASTCALL zend_jit_post_dec_obj_helper(zend_object *zobj, zend_s } if (prop_info) { - zend_jit_post_dec_typed_prop(prop, prop_info, result); + zend_jit_post_dec_typed_prop(prop, prop_info, result, zobj); } else { ZVAL_COPY(result, prop); decrement_function(prop); diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index ace1206682042..067048a94aed9 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -14917,11 +14917,12 @@ static int zend_jit_assign_obj(zend_jit_ctx *jit, } // JIT: value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); jit_SET_EX_OPLINE(jit, opline); - ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_to_typed_prop), + ir_CALL_5(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_to_typed_prop), prop_ref, prop_info_ref, arg3, - arg4); + arg4, + obj_ref); if ((opline+1)->op1_type == IS_CONST) { // TODO: ??? @@ -14989,11 +14990,12 @@ static int zend_jit_assign_obj(zend_jit_ctx *jit, } else { arg4 = jit_ZVAL_ADDR(jit, res_addr); } - ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_to_typed_prop), + ir_CALL_5(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_to_typed_prop), prop_ref, ref, arg3, - arg4); + arg4, + obj_ref); ir_END_list(end_inputs); } @@ -15331,11 +15333,12 @@ static int zend_jit_assign_obj_op(zend_jit_ctx *jit, ref = ir_LOAD_A(ir_ADD_OFFSET(ref, prop_info_offset)); } - ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_op_to_typed_prop), + ir_CALL_5(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_op_to_typed_prop), prop_ref, ref, arg2, - ir_CONST_FC_FUNC(binary_op)); + ir_CONST_FC_FUNC(binary_op), + obj_ref); ir_END_list(end_inputs); } @@ -15735,7 +15738,7 @@ static int zend_jit_incdec_obj(zend_jit_ctx *jit, ZEND_UNREACHABLE(); } - ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(func), prop_ref, ref); + ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(func), prop_ref, ref, obj_ref); } else { switch (opline->opcode) { case ZEND_PRE_INC_OBJ: @@ -15753,10 +15756,11 @@ static int zend_jit_incdec_obj(zend_jit_ctx *jit, default: ZEND_UNREACHABLE(); } - ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(func), + ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(func), prop_ref, ref, - jit_ZVAL_ADDR(jit, res_addr)); + jit_ZVAL_ADDR(jit, res_addr), + obj_ref); } ir_END_list(end_inputs); } From 63fde1339d5fc7256cea2e472d95980f91700343 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 22 Jan 2026 18:13:02 +0100 Subject: [PATCH 2/9] Allow reassign within nested calls --- ...hpt => cpp_reassign_indirect_allowed.phpt} | 43 ++++++++++++------- Zend/zend_object_handlers.c | 21 ++++++--- 2 files changed, 42 insertions(+), 22 deletions(-) rename Zend/tests/readonly_props/{cpp_reassign_indirect_fail.phpt => cpp_reassign_indirect_allowed.phpt} (50%) diff --git a/Zend/tests/readonly_props/cpp_reassign_indirect_fail.phpt b/Zend/tests/readonly_props/cpp_reassign_indirect_allowed.phpt similarity index 50% rename from Zend/tests/readonly_props/cpp_reassign_indirect_fail.phpt rename to Zend/tests/readonly_props/cpp_reassign_indirect_allowed.phpt index 64400c0abb5fd..d126da231030c 100644 --- a/Zend/tests/readonly_props/cpp_reassign_indirect_fail.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_indirect_allowed.phpt @@ -1,18 +1,14 @@ --TEST-- -Promoted readonly property reassignment in constructor - indirect reassignment not allowed +Promoted readonly property reassignment in constructor - indirect reassignment allowed --FILE-- initProp(); - } catch (Error $e) { - echo $e->getMessage(), "\n"; - } + $this->initProp(); } private function initProp(): void { @@ -23,7 +19,7 @@ class CalledMethod { $cm = new CalledMethod(); var_dump($cm->prop); -// Reassignment is NOT allowed in closures called by the constructor +// Reassignment IS allowed in closures called by the constructor class ClosureInConstructor { public function __construct( public readonly string $prop = 'default', @@ -31,20 +27,37 @@ class ClosureInConstructor { $fn = function() { $this->prop = 'from closure'; }; + $fn(); + } +} + +$cc = new ClosureInConstructor(); +var_dump($cc->prop); + +// But second reassignment still fails +class MultipleReassign { + public function __construct( + public readonly string $prop = 'default', + ) { + $this->initProp(); try { - $fn(); + $this->initProp(); // Second call - should fail } catch (Error $e) { echo $e->getMessage(), "\n"; } } + + private function initProp(): void { + $this->prop = 'from method'; + } } -$cc = new ClosureInConstructor(); -var_dump($cc->prop); +$mr = new MultipleReassign(); +var_dump($mr->prop); ?> --EXPECT-- -Cannot modify readonly property CalledMethod::$prop -string(7) "default" -Cannot modify readonly property ClosureInConstructor::$prop -string(7) "default" +string(11) "from method" +string(12) "from closure" +Cannot modify readonly property MultipleReassign::$prop +string(11) "from method" diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index ef562012199da..624841e506d16 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -46,16 +46,23 @@ #define IN_ISSET ZEND_GUARD_PROPERTY_ISSET #define IN_HOOK ZEND_GUARD_PROPERTY_HOOK -/* Check if we're in the constructor of the class that declared the property. - * Ensures that a child class cannot reassign a parent's promoted readonly property. */ +/* Check if we're within a constructor call chain for the class that declared the property. + * Walks up the call stack to find if any frame is a constructor for zobj with the right scope. + * This allows reassignment from methods/closures called by the constructor. */ ZEND_API bool zend_is_in_property_declaring_class_constructor(const zend_property_info *prop_info, const zend_object *zobj) { zend_execute_data *ex = EG(current_execute_data); - return ex && ex->func - && (ex->func->common.fn_flags & ZEND_ACC_CTOR) - && (ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS) - && Z_OBJ(ex->This) == zobj - && ex->func->common.scope == prop_info->ce; + while (ex) { + if (ex->func + && (ex->func->common.fn_flags & ZEND_ACC_CTOR) + && (ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS) + && Z_OBJ(ex->This) == zobj + && ex->func->common.scope == prop_info->ce) { + return true; + } + ex = ex->prev_execute_data; + } + return false; } static zend_arg_info zend_call_trampoline_arginfo[1] = {{0}}; From e21784b198796eb98ac5a088823025ff7fd1f8ff Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 22 Jan 2026 18:37:52 +0100 Subject: [PATCH 3/9] Fix inheritance visibility rules --- .../cpp_reassign_child_class.phpt | 73 ++++++++----- .../cpp_reassign_visibility.phpt | 101 ++++++++++++++++++ Zend/zend_object_handlers.c | 15 ++- Zend/zend_object_handlers.h | 6 +- 4 files changed, 155 insertions(+), 40 deletions(-) create mode 100644 Zend/tests/readonly_props/cpp_reassign_visibility.phpt diff --git a/Zend/tests/readonly_props/cpp_reassign_child_class.phpt b/Zend/tests/readonly_props/cpp_reassign_child_class.phpt index c9f8dc2f867e6..3d1066b870266 100644 --- a/Zend/tests/readonly_props/cpp_reassign_child_class.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_child_class.phpt @@ -1,54 +1,46 @@ --TEST-- -Promoted readonly property reassignment in constructor - child class cannot reassign parent's property +Promoted readonly property reassignment in constructor - child class can reassign parent's property --FILE-- prop = 'parent set'; + // Parent does NOT reassign here - leaves opportunity for child } } -class Child extends Parent_ { +class Child1 extends Parent1 { public function __construct() { parent::__construct(); - // Child cannot reassign parent's promoted property - try { - $this->prop = 'child override'; - } catch (Error $e) { - echo $e->getMessage(), "\n"; - } + // Child CAN reassign since parent didn't use the one reassignment + $this->prop = 'child override'; } } -$parent = new Parent_(); +$parent = new Parent1(); var_dump($parent->prop); -$child = new Child(); +$child = new Child1(); var_dump($child->prop); -// Even when child has its own promoted property +// Case 2: Parent USES reassignment, child cannot class Parent2 { public function __construct( - public readonly string $parentProp = 'parent default', + public readonly string $prop = 'parent default', ) { - $this->parentProp = 'parent set'; + $this->prop = 'parent set'; // Uses the one reassignment } } class Child2 extends Parent2 { - public function __construct( - public readonly string $childProp = 'child default', - ) { + public function __construct() { parent::__construct(); - // Child can reassign its own promoted property - $this->childProp = 'child set'; - // But cannot reassign parent's promoted property + // Child cannot reassign - parent already used the one reassignment try { - $this->parentProp = 'child override'; + $this->prop = 'child override'; } catch (Error $e) { echo $e->getMessage(), "\n"; } @@ -56,13 +48,36 @@ class Child2 extends Parent2 { } $child2 = new Child2(); -var_dump($child2->parentProp, $child2->childProp); +var_dump($child2->prop); + +// Case 3: Child with its own promoted property +class Parent3 { + public function __construct( + public readonly string $parentProp = 'parent default', + ) { + // Parent doesn't reassign + } +} + +class Child3 extends Parent3 { + public function __construct( + public readonly string $childProp = 'child default', + ) { + parent::__construct(); + // Child can reassign both: parent's (not yet used) and its own + $this->parentProp = 'child set parent'; + $this->childProp = 'child set own'; + } +} + +$child3 = new Child3(); +var_dump($child3->parentProp, $child3->childProp); ?> --EXPECT-- +string(14) "parent default" +string(14) "child override" +Cannot modify readonly property Parent2::$prop string(10) "parent set" -Cannot modify readonly property Parent_::$prop -string(10) "parent set" -Cannot modify readonly property Parent2::$parentProp -string(10) "parent set" -string(9) "child set" +string(16) "child set parent" +string(13) "child set own" diff --git a/Zend/tests/readonly_props/cpp_reassign_visibility.phpt b/Zend/tests/readonly_props/cpp_reassign_visibility.phpt new file mode 100644 index 0000000000000..4ce8aa7a31c8d --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_visibility.phpt @@ -0,0 +1,101 @@ +--TEST-- +Promoted readonly property reassignment in constructor - visibility rules apply +--FILE-- +prop = 'child override'; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } +} + +$child1 = new Child1(); +var_dump($child1->prop); + +// Case 2: protected(set) - child CAN reassign +class Parent2 { + public function __construct( + protected(set) public readonly string $prop = 'parent default', + ) { + // Parent doesn't use reassignment + } +} + +class Child2 extends Parent2 { + public function __construct() { + parent::__construct(); + // Child CAN reassign - protected(set) allows child classes + $this->prop = 'child override'; + } +} + +$child2 = new Child2(); +var_dump($child2->prop); + +// Case 3: public (default) - child CAN reassign +class Parent3 { + public function __construct( + public readonly string $prop = 'parent default', + ) { + // Parent doesn't use reassignment + } +} + +class Child3 extends Parent3 { + public function __construct() { + parent::__construct(); + // Child CAN reassign - public allows anyone + $this->prop = 'child override'; + } +} + +$child3 = new Child3(); +var_dump($child3->prop); + +// Case 4: protected(set) with parent using reassignment - child cannot (one reassignment rule) +class Parent4 { + public function __construct( + protected(set) public readonly string $prop = 'parent default', + ) { + $this->prop = 'parent set'; // Uses the one reassignment + } +} + +class Child4 extends Parent4 { + public function __construct() { + parent::__construct(); + // Child cannot reassign - parent already used the one reassignment + try { + $this->prop = 'child override'; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } +} + +$child4 = new Child4(); +var_dump($child4->prop); + +?> +--EXPECT-- +Cannot modify private(set) property Parent1::$prop from scope Child1 +string(14) "parent default" +string(14) "child override" +string(14) "child override" +Cannot modify readonly property Parent4::$prop +string(10) "parent set" diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 624841e506d16..7d7b6eb04ab5a 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -46,18 +46,17 @@ #define IN_ISSET ZEND_GUARD_PROPERTY_ISSET #define IN_HOOK ZEND_GUARD_PROPERTY_HOOK -/* Check if we're within a constructor call chain for the class that declared the property. - * Walks up the call stack to find if any frame is a constructor for zobj with the right scope. - * This allows reassignment from methods/closures called by the constructor. */ -ZEND_API bool zend_is_in_property_declaring_class_constructor(const zend_property_info *prop_info, const zend_object *zobj) +/* Check if we're within a constructor call chain for the given object. + * Walks up the call stack to find if any frame is a constructor for zobj. + * This allows reassignment from the constructor or methods/closures called from it. */ +ZEND_API bool zend_is_in_constructor(const zend_object *zobj) { zend_execute_data *ex = EG(current_execute_data); while (ex) { if (ex->func && (ex->func->common.fn_flags & ZEND_ACC_CTOR) && (ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS) - && Z_OBJ(ex->This) == zobj - && ex->func->common.scope == prop_info->ce) { + && Z_OBJ(ex->This) == zobj) { return true; } ex = ex->prev_execute_data; @@ -1122,10 +1121,10 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva goto exit; } /* For promoted readonly properties being initialized for the first time, - * set IS_PROP_CPP_REINITABLE to allow one reassignment in the declaring class constructor. */ + * set IS_PROP_CPP_REINITABLE to allow one reassignment in the constructor. */ if ((prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) && (Z_PROP_FLAG_P(variable_ptr) & IS_PROP_UNINIT) - && zend_is_in_property_declaring_class_constructor(prop_info, zobj)) { + && zend_is_in_constructor(zobj)) { Z_PROP_FLAG_P(variable_ptr) = IS_PROP_CPP_REINITABLE; } else { Z_PROP_FLAG_P(variable_ptr) &= ~(IS_PROP_UNINIT|IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index 7e3bc13efd747..bb42a8c5948f1 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -276,16 +276,16 @@ ZEND_API zend_result zend_std_get_closure(zend_object *obj, zend_class_entry **c ZEND_API HashTable *rebuild_object_properties_internal(zend_object *zobj); ZEND_API ZEND_COLD zend_never_inline void zend_bad_method_call(const zend_function *fbc, const zend_string *method_name, const zend_class_entry *scope); ZEND_API ZEND_COLD zend_never_inline void zend_abstract_method_call(const zend_function *fbc); -ZEND_API bool zend_is_in_property_declaring_class_constructor(const zend_property_info *prop_info, const zend_object *zobj); +ZEND_API bool zend_is_in_constructor(const zend_object *zobj); -/* Check if a readonly property can be modified (has REINITABLE or CPP_REINITABLE flag and is in declaring constructor) */ +/* Check if a readonly property can be modified (has REINITABLE or CPP_REINITABLE flag and is in constructor) */ static zend_always_inline bool zend_is_readonly_property_modifiable(zval *property_val, const zend_property_info *prop_info, zend_object *zobj) { if (Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE) { return true; } if ((Z_PROP_FLAG_P(property_val) & IS_PROP_CPP_REINITABLE) - && zend_is_in_property_declaring_class_constructor(prop_info, zobj)) { + && zend_is_in_constructor(zobj)) { return true; } return false; From 637b3bd7d4d28d081ea06d57e7d8a86ece631b6c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 2 Feb 2026 21:58:30 +0100 Subject: [PATCH 4/9] Fix reassigning when calling the constructor twice --- .../cpp_reassign_direct_ctor_call.phpt | 51 ++++++++++++ .../cpp_reassign_reflection.phpt | 81 +++++++++++++++++++ Zend/zend_object_handlers.c | 16 +++- Zend/zend_object_handlers.h | 4 +- Zend/zend_types.h | 1 + Zend/zend_vm_def.h | 14 +++- Zend/zend_vm_execute.h | 42 ++++++++-- 7 files changed, 198 insertions(+), 11 deletions(-) create mode 100644 Zend/tests/readonly_props/cpp_reassign_direct_ctor_call.phpt create mode 100644 Zend/tests/readonly_props/cpp_reassign_reflection.phpt diff --git a/Zend/tests/readonly_props/cpp_reassign_direct_ctor_call.phpt b/Zend/tests/readonly_props/cpp_reassign_direct_ctor_call.phpt new file mode 100644 index 0000000000000..dd936ff41ad16 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_direct_ctor_call.phpt @@ -0,0 +1,51 @@ +--TEST-- +Promoted readonly properties cannot be reassigned when __construct() is called directly +--FILE-- +value . "\n"; + +// Direct call to __construct() should NOT allow reassignment +try { + $obj->__construct('modified'); + echo "After direct __construct: " . $obj->value . "\n"; +} catch (Error $e) { + echo "Error: " . $e->getMessage() . "\n"; +} + +// Also test with a class that uses reassignment +class Bar { + public function __construct( + public readonly string $value = 'default', + ) { + $this->value = strtoupper($this->value); + } +} + +$bar = new Bar('hello'); +echo "Bar initial value: " . $bar->value . "\n"; + +// Direct call should fail during the CPP assignment (property not UNINIT) +// Note: The error happens inside the constructor because CPP assignment happens first +try { + $bar->__construct('world'); + echo "After direct __construct: " . $bar->value . "\n"; +} catch (Error $e) { + echo "Error: " . $e->getMessage() . "\n"; +} + +?> +--EXPECT-- +Initial value: initial +Error: Cannot modify readonly property Foo::$value +Bar initial value: HELLO +Error: Cannot modify readonly property Bar::$value diff --git a/Zend/tests/readonly_props/cpp_reassign_reflection.phpt b/Zend/tests/readonly_props/cpp_reassign_reflection.phpt new file mode 100644 index 0000000000000..e94e429a9aded --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_reflection.phpt @@ -0,0 +1,81 @@ +--TEST-- +Promoted readonly property reassignment works when object created via reflection +--FILE-- +bar = 'overwritten in constructor'; + } +} + +// Test 1: Object created via reflection without constructor, then __construct() called +echo "Test 1: Reflection newInstanceWithoutConstructor + explicit __construct()\n"; +$ref = new ReflectionClass(Foo::class); +$obj = $ref->newInstanceWithoutConstructor(); + +// Property should be uninitialized at this point +try { + echo $obj->bar; + echo "ERROR: Should have thrown for uninitialized property\n"; +} catch (Error $e) { + echo "OK: " . $e->getMessage() . "\n"; +} + +// Now call constructor - reassignment should work +$obj->__construct('explicit call'); +echo "After __construct: " . $obj->bar . "\n"; + +// Second __construct() call should fail +echo "\nTest 2: Second __construct() call should fail\n"; +try { + $obj->__construct('second call'); + echo "ERROR: Second __construct() should have failed\n"; +} catch (Error $e) { + echo "OK: " . $e->getMessage() . "\n"; +} + +// Test 3: Normal new still works +echo "\nTest 3: Normal 'new' still works\n"; +$obj2 = new Foo('via new'); +echo "After new: " . $obj2->bar . "\n"; + +// Second call should fail +try { + $obj2->__construct('second call'); + echo "ERROR: Should have failed\n"; +} catch (Error $e) { + echo "OK: " . $e->getMessage() . "\n"; +} + +// Test 4: Reflection newInstanceArgs (calls constructor) +echo "\nTest 4: Reflection newInstanceArgs\n"; +$obj3 = $ref->newInstanceArgs(['via newInstanceArgs']); +echo "After newInstanceArgs: " . $obj3->bar . "\n"; + +// Second call should fail +try { + $obj3->__construct('second call'); + echo "ERROR: Should have failed\n"; +} catch (Error $e) { + echo "OK: " . $e->getMessage() . "\n"; +} + +?> +--EXPECT-- +Test 1: Reflection newInstanceWithoutConstructor + explicit __construct() +OK: Typed property Foo::$bar must not be accessed before initialization +After __construct: overwritten in constructor + +Test 2: Second __construct() call should fail +OK: Cannot modify readonly property Foo::$bar + +Test 3: Normal 'new' still works +After new: overwritten in constructor +OK: Cannot modify readonly property Foo::$bar + +Test 4: Reflection newInstanceArgs +After newInstanceArgs: overwritten in constructor +OK: Cannot modify readonly property Foo::$bar diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 7d7b6eb04ab5a..7b1baab7cdd25 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -49,7 +49,7 @@ /* Check if we're within a constructor call chain for the given object. * Walks up the call stack to find if any frame is a constructor for zobj. * This allows reassignment from the constructor or methods/closures called from it. */ -ZEND_API bool zend_is_in_constructor(const zend_object *zobj) +static bool zend_is_in_constructor(const zend_object *zobj) { zend_execute_data *ex = EG(current_execute_data); while (ex) { @@ -64,6 +64,20 @@ ZEND_API bool zend_is_in_constructor(const zend_object *zobj) return false; } +/* Check if we're in the FIRST construction of an object. + * Uses the IS_OBJ_CTOR_CALLED flag which is set when a constructor completes. + * This allows both 'new' and explicit __construct() calls on reflection-created objects. */ +ZEND_API bool zend_is_in_original_construction(const zend_object *zobj) +{ + /* If a constructor has already completed on this object, this is not original construction */ + if (zobj->extra_flags & IS_OBJ_CTOR_CALLED) { + return false; + } + + /* Verify we're actually in a constructor for this object */ + return zend_is_in_constructor(zobj); +} + static zend_arg_info zend_call_trampoline_arginfo[1] = {{0}}; static zend_arg_info zend_property_hook_arginfo[1] = {{0}}; diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index bb42a8c5948f1..6233c04a4a8ea 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -276,7 +276,7 @@ ZEND_API zend_result zend_std_get_closure(zend_object *obj, zend_class_entry **c ZEND_API HashTable *rebuild_object_properties_internal(zend_object *zobj); ZEND_API ZEND_COLD zend_never_inline void zend_bad_method_call(const zend_function *fbc, const zend_string *method_name, const zend_class_entry *scope); ZEND_API ZEND_COLD zend_never_inline void zend_abstract_method_call(const zend_function *fbc); -ZEND_API bool zend_is_in_constructor(const zend_object *zobj); +ZEND_API bool zend_is_in_original_construction(const zend_object *zobj); /* Check if a readonly property can be modified (has REINITABLE or CPP_REINITABLE flag and is in constructor) */ static zend_always_inline bool zend_is_readonly_property_modifiable(zval *property_val, const zend_property_info *prop_info, zend_object *zobj) @@ -285,7 +285,7 @@ static zend_always_inline bool zend_is_readonly_property_modifiable(zval *proper return true; } if ((Z_PROP_FLAG_P(property_val) & IS_PROP_CPP_REINITABLE) - && zend_is_in_constructor(zobj)) { + && zend_is_in_original_construction(zobj)) { return true; } return false; diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 6f2b03013c13f..35bc1234ebdcb 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -858,6 +858,7 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { #define IS_OBJ_LAZY_UNINITIALIZED (1U<<31) /* Virtual proxy or uninitialized Ghost */ #define IS_OBJ_LAZY_PROXY (1U<<30) /* Virtual proxy (may be initialized) */ +#define IS_OBJ_CTOR_CALLED (1U<<29) /* A constructor has completed on this object */ #define OBJ_EXTRA_FLAGS(obj) ((obj)->extra_flags) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 9881d983fd778..218a3ae90f95c 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3001,7 +3001,12 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) call_info = EX_CALL_INFO(); #endif if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { - OBJ_RELEASE(Z_OBJ(execute_data->This)); + zend_object *obj = Z_OBJ(execute_data->This); + /* Mark that a constructor has completed on this object */ + if (EX(func)->common.fn_flags & ZEND_ACC_CTOR) { + obj->extra_flags |= IS_OBJ_CTOR_CALLED; + } + OBJ_RELEASE(obj); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } @@ -3035,7 +3040,12 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) zend_vm_stack_free_extra_args_ex(call_info, execute_data); if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { - OBJ_RELEASE(Z_OBJ(execute_data->This)); + zend_object *obj = Z_OBJ(execute_data->This); + /* Mark that a constructor has completed on this object */ + if (EX(func)->common.fn_flags & ZEND_ACC_CTOR) { + obj->extra_flags |= IS_OBJ_CTOR_CALLED; + } + OBJ_RELEASE(obj); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index b3c8e4deadc41..7c6cca089945c 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1161,7 +1161,12 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV call_info = EX_CALL_INFO(); #endif if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { - OBJ_RELEASE(Z_OBJ(execute_data->This)); + zend_object *obj = Z_OBJ(execute_data->This); + /* Mark that a constructor has completed on this object */ + if (EX(func)->common.fn_flags & ZEND_ACC_CTOR) { + obj->extra_flags |= IS_OBJ_CTOR_CALLED; + } + OBJ_RELEASE(obj); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } @@ -1195,7 +1200,12 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_vm_stack_free_extra_args_ex(call_info, execute_data); if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { - OBJ_RELEASE(Z_OBJ(execute_data->This)); + zend_object *obj = Z_OBJ(execute_data->This); + /* Mark that a constructor has completed on this object */ + if (EX(func)->common.fn_flags & ZEND_ACC_CTOR) { + obj->extra_flags |= IS_OBJ_CTOR_CALLED; + } + OBJ_RELEASE(obj); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } @@ -53881,7 +53891,12 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend call_info = EX_CALL_INFO(); #endif if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { - OBJ_RELEASE(Z_OBJ(execute_data->This)); + zend_object *obj = Z_OBJ(execute_data->This); + /* Mark that a constructor has completed on this object */ + if (EX(func)->common.fn_flags & ZEND_ACC_CTOR) { + obj->extra_flags |= IS_OBJ_CTOR_CALLED; + } + OBJ_RELEASE(obj); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } @@ -53915,7 +53930,12 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend zend_vm_stack_free_extra_args_ex(call_info, execute_data); if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { - OBJ_RELEASE(Z_OBJ(execute_data->This)); + zend_object *obj = Z_OBJ(execute_data->This); + /* Mark that a constructor has completed on this object */ + if (EX(func)->common.fn_flags & ZEND_ACC_CTOR) { + obj->extra_flags |= IS_OBJ_CTOR_CALLED; + } + OBJ_RELEASE(obj); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } @@ -110149,7 +110169,12 @@ ZEND_API void execute_ex(zend_execute_data *ex) call_info = EX_CALL_INFO(); #endif if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { - OBJ_RELEASE(Z_OBJ(execute_data->This)); + zend_object *obj = Z_OBJ(execute_data->This); + /* Mark that a constructor has completed on this object */ + if (EX(func)->common.fn_flags & ZEND_ACC_CTOR) { + obj->extra_flags |= IS_OBJ_CTOR_CALLED; + } + OBJ_RELEASE(obj); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } @@ -110183,7 +110208,12 @@ ZEND_API void execute_ex(zend_execute_data *ex) zend_vm_stack_free_extra_args_ex(call_info, execute_data); if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { - OBJ_RELEASE(Z_OBJ(execute_data->This)); + zend_object *obj = Z_OBJ(execute_data->This); + /* Mark that a constructor has completed on this object */ + if (EX(func)->common.fn_flags & ZEND_ACC_CTOR) { + obj->extra_flags |= IS_OBJ_CTOR_CALLED; + } + OBJ_RELEASE(obj); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } From a0ae9dce4e2dc65b8d2603452e95ecf6758ca843 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 13 Feb 2026 15:44:16 +0100 Subject: [PATCH 5/9] Align CPP readonly reassignment with declaring ctor scope --- .../cpp_reassign_child_class.phpt | 26 +++++--- .../cpp_reassign_child_preempt_parent.phpt | 60 +++++++++++++++++++ .../cpp_reassign_visibility.phpt | 26 +++++--- Zend/zend_execute.c | 2 +- Zend/zend_object_handlers.c | 25 ++++---- Zend/zend_object_handlers.h | 6 +- ext/opcache/jit/zend_jit_helpers.c | 2 +- 7 files changed, 111 insertions(+), 36 deletions(-) create mode 100644 Zend/tests/readonly_props/cpp_reassign_child_preempt_parent.phpt diff --git a/Zend/tests/readonly_props/cpp_reassign_child_class.phpt b/Zend/tests/readonly_props/cpp_reassign_child_class.phpt index 3d1066b870266..b7215c05d0788 100644 --- a/Zend/tests/readonly_props/cpp_reassign_child_class.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_child_class.phpt @@ -1,9 +1,9 @@ --TEST-- -Promoted readonly property reassignment in constructor - child class can reassign parent's property +Promoted readonly property reassignment in constructor - child cannot reassign parent's property --FILE-- prop = 'child override'; + // Child cannot reassign parent-owned promoted property + try { + $this->prop = 'child override'; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } } } @@ -64,8 +68,12 @@ class Child3 extends Parent3 { public readonly string $childProp = 'child default', ) { parent::__construct(); - // Child can reassign both: parent's (not yet used) and its own - $this->parentProp = 'child set parent'; + // Child cannot reassign parent's property, but can reassign its own + try { + $this->parentProp = 'child set parent'; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } $this->childProp = 'child set own'; } } @@ -76,8 +84,10 @@ var_dump($child3->parentProp, $child3->childProp); ?> --EXPECT-- string(14) "parent default" -string(14) "child override" +Cannot modify readonly property Parent1::$prop +string(14) "parent default" Cannot modify readonly property Parent2::$prop string(10) "parent set" -string(16) "child set parent" +Cannot modify readonly property Parent3::$parentProp +string(14) "parent default" string(13) "child set own" diff --git a/Zend/tests/readonly_props/cpp_reassign_child_preempt_parent.phpt b/Zend/tests/readonly_props/cpp_reassign_child_preempt_parent.phpt new file mode 100644 index 0000000000000..0eb0f4ff95bcc --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_child_preempt_parent.phpt @@ -0,0 +1,60 @@ +--TEST-- +Promoted readonly property reassignment in constructor - child preempt then parent ctor throws +--FILE-- +prop = 'parent set'; + } catch (Error) { + // readonly property set by child class + } + } +} + +class ChildNoCPP extends ParentNoCPP { + public function __construct() { + $this->prop = 'child set'; + parent::__construct(); + } +} + +class ParentCPP { + public function __construct( + public readonly string $prop = 'parent default', + ) { + try { + $this->prop = 'parent set'; + } catch (Error) { + // readonly property set by child class + } + } +} + +class ChildCPP extends ParentCPP { + public function __construct() { + $this->prop = 'child set'; + try { + parent::__construct(); + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } + } +} + +$c = new ChildNoCPP(); +var_dump($c->prop); + +$c = new ChildCPP(); +var_dump($c->prop); + +?> +--EXPECT-- +string(9) "child set" +Cannot modify readonly property ParentCPP::$prop +string(9) "child set" diff --git a/Zend/tests/readonly_props/cpp_reassign_visibility.phpt b/Zend/tests/readonly_props/cpp_reassign_visibility.phpt index 4ce8aa7a31c8d..9a7d552a73543 100644 --- a/Zend/tests/readonly_props/cpp_reassign_visibility.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_visibility.phpt @@ -27,7 +27,7 @@ class Child1 extends Parent1 { $child1 = new Child1(); var_dump($child1->prop); -// Case 2: protected(set) - child CAN reassign +// Case 2: protected(set) - child still cannot reassign parent-owned promoted property class Parent2 { public function __construct( protected(set) public readonly string $prop = 'parent default', @@ -39,15 +39,18 @@ class Parent2 { class Child2 extends Parent2 { public function __construct() { parent::__construct(); - // Child CAN reassign - protected(set) allows child classes - $this->prop = 'child override'; + try { + $this->prop = 'child override'; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } } } $child2 = new Child2(); var_dump($child2->prop); -// Case 3: public (default) - child CAN reassign +// Case 3: public (default) - child still cannot reassign parent-owned promoted property class Parent3 { public function __construct( public readonly string $prop = 'parent default', @@ -59,8 +62,11 @@ class Parent3 { class Child3 extends Parent3 { public function __construct() { parent::__construct(); - // Child CAN reassign - public allows anyone - $this->prop = 'child override'; + try { + $this->prop = 'child override'; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } } } @@ -93,9 +99,11 @@ var_dump($child4->prop); ?> --EXPECT-- -Cannot modify private(set) property Parent1::$prop from scope Child1 +Cannot modify readonly property Parent1::$prop +string(14) "parent default" +Cannot modify readonly property Parent2::$prop +string(14) "parent default" +Cannot modify readonly property Parent3::$prop string(14) "parent default" -string(14) "child override" -string(14) "child override" Cannot modify readonly property Parent4::$prop string(10) "parent set" diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index ef4d09c980dc2..ce56833dd7341 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1074,7 +1074,7 @@ static zend_never_inline zval* zend_assign_to_typed_prop(const zend_property_inf if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { if (info->flags & ZEND_ACC_READONLY) { - if (!zend_is_readonly_property_modifiable(property_val, info, zobj)) { + if (!zend_is_readonly_property_modifiable(property_val, info->ce, zobj)) { zend_readonly_property_modification_error(info); return &EG(uninitialized_zval); } diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 7b1baab7cdd25..9baa7e37458de 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -47,16 +47,17 @@ #define IN_HOOK ZEND_GUARD_PROPERTY_HOOK /* Check if we're within a constructor call chain for the given object. - * Walks up the call stack to find if any frame is a constructor for zobj. - * This allows reassignment from the constructor or methods/closures called from it. */ -static bool zend_is_in_constructor(const zend_object *zobj) + * If ctor_scope is non-NULL, restrict to constructor frames declared by ctor_scope. + * Methods/closures called from that constructor are allowed through the stack walk. */ +static bool zend_is_in_constructor(const zend_object *zobj, const zend_class_entry *ctor_scope) { zend_execute_data *ex = EG(current_execute_data); while (ex) { if (ex->func && (ex->func->common.fn_flags & ZEND_ACC_CTOR) && (ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS) - && Z_OBJ(ex->This) == zobj) { + && Z_OBJ(ex->This) == zobj + && (!ctor_scope || ex->func->common.scope == ctor_scope)) { return true; } ex = ex->prev_execute_data; @@ -64,18 +65,14 @@ static bool zend_is_in_constructor(const zend_object *zobj) return false; } -/* Check if we're in the FIRST construction of an object. - * Uses the IS_OBJ_CTOR_CALLED flag which is set when a constructor completes. - * This allows both 'new' and explicit __construct() calls on reflection-created objects. */ -ZEND_API bool zend_is_in_original_construction(const zend_object *zobj) +/* Check if we're in the first construction of an object and executing the + * constructor chain declared by ctor_scope. */ +ZEND_API bool zend_is_in_declaring_constructor(const zend_object *zobj, const zend_class_entry *ctor_scope) { - /* If a constructor has already completed on this object, this is not original construction */ if (zobj->extra_flags & IS_OBJ_CTOR_CALLED) { return false; } - - /* Verify we're actually in a constructor for this object */ - return zend_is_in_constructor(zobj); + return zend_is_in_constructor(zobj, ctor_scope); } static zend_arg_info zend_call_trampoline_arginfo[1] = {{0}}; @@ -1100,7 +1097,7 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva if (error) { if ((prop_info->flags & ZEND_ACC_READONLY) && Z_TYPE_P(variable_ptr) != IS_UNDEF - && !zend_is_readonly_property_modifiable(variable_ptr, prop_info, zobj)) { + && !zend_is_readonly_property_modifiable(variable_ptr, prop_info->ce, zobj)) { zend_readonly_property_modification_error(prop_info); variable_ptr = &EG(error_zval); goto exit; @@ -1138,7 +1135,7 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva * set IS_PROP_CPP_REINITABLE to allow one reassignment in the constructor. */ if ((prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) && (Z_PROP_FLAG_P(variable_ptr) & IS_PROP_UNINIT) - && zend_is_in_constructor(zobj)) { + && zend_is_in_declaring_constructor(zobj, prop_info->ce)) { Z_PROP_FLAG_P(variable_ptr) = IS_PROP_CPP_REINITABLE; } else { Z_PROP_FLAG_P(variable_ptr) &= ~(IS_PROP_UNINIT|IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index 6233c04a4a8ea..650771517a703 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -276,16 +276,16 @@ ZEND_API zend_result zend_std_get_closure(zend_object *obj, zend_class_entry **c ZEND_API HashTable *rebuild_object_properties_internal(zend_object *zobj); ZEND_API ZEND_COLD zend_never_inline void zend_bad_method_call(const zend_function *fbc, const zend_string *method_name, const zend_class_entry *scope); ZEND_API ZEND_COLD zend_never_inline void zend_abstract_method_call(const zend_function *fbc); -ZEND_API bool zend_is_in_original_construction(const zend_object *zobj); +ZEND_API bool zend_is_in_declaring_constructor(const zend_object *zobj, const zend_class_entry *ctor_scope); /* Check if a readonly property can be modified (has REINITABLE or CPP_REINITABLE flag and is in constructor) */ -static zend_always_inline bool zend_is_readonly_property_modifiable(zval *property_val, const zend_property_info *prop_info, zend_object *zobj) +static zend_always_inline bool zend_is_readonly_property_modifiable(zval *property_val, const zend_class_entry *declaring_ce, zend_object *zobj) { if (Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE) { return true; } if ((Z_PROP_FLAG_P(property_val) & IS_PROP_CPP_REINITABLE) - && zend_is_in_original_construction(zobj)) { + && zend_is_in_declaring_constructor(zobj, declaring_ce)) { return true; } return false; diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index e9e3348393339..7ad087b8494e4 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -2808,7 +2808,7 @@ static zend_always_inline bool verify_readonly_and_avis(zval *property_val, zend if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { if (info->flags & ZEND_ACC_READONLY) { ZEND_ASSERT(zobj != NULL); /* CPP_REINITABLE only applies to instance properties */ - if (!zend_is_readonly_property_modifiable(property_val, info, zobj)) { + if (!zend_is_readonly_property_modifiable(property_val, info->ce, zobj)) { zend_readonly_property_modification_error(info); return false; } From 67be986e34687998a14de0f23db0d6466d8b2e95 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 19 Feb 2026 09:23:02 +0100 Subject: [PATCH 6/9] Simplify CPP readonly reassignment to reuse IS_PROP_REINITABLE Replace the IS_PROP_CPP_REINITABLE property flag and IS_OBJ_CTOR_CALLED object flag with the existing IS_PROP_REINITABLE flag, following the same try/finally pattern already used by __clone(). How it works: - On CPP implicit initialization, if the current execute frame's scope matches prop_info->ce (no stack walk needed), set IS_PROP_REINITABLE instead of clearing all property flags. This opens a one-shot reassignment window. - zend_leave_helper clears IS_PROP_REINITABLE from all PROMOTED|READONLY properties of the constructor's scope on exit, gated on ZEND_CALL_HAS_THIS|ZEND_ACC_CTOR (covers both `new Foo()` and `parent::__construct()` calls, not just ZEND_CALL_RELEASE_THIS). - zend_is_readonly_property_modifiable() simplified to a single IS_PROP_REINITABLE check; zend_is_in_declaring_constructor() and the call-stack walk helper removed entirely. A repeated __construct() call cannot bypass readonly because the property is already past IS_PROP_UNINIT, so IS_PROP_REINITABLE is never re-set; no object-level tracking flag is needed. This saves two flag bits (property and object extra_flags) and removes ~30 lines of stack-walk code. --- .../readonly_props/cpp_reassign_basic.phpt | 14 +- .../cpp_reassign_child_class.phpt | 28 ++-- .../cpp_reassign_child_preempt_parent.phpt | 37 +----- .../cpp_reassign_child_redefine.phpt | 89 +++++++++++++ .../cpp_reassign_conditional.phpt | 6 +- .../cpp_reassign_different_object.phpt | 66 +++------- .../cpp_reassign_direct_ctor_call.phpt | 40 ++---- .../cpp_reassign_indirect_allowed.phpt | 22 +--- .../cpp_reassign_indirect_ops.phpt | 32 +---- .../cpp_reassign_multiple_fail.phpt | 6 +- .../cpp_reassign_nonpromoted.phpt | 12 +- .../cpp_reassign_outside_ctor.phpt | 26 ++-- .../cpp_reassign_reflection.phpt | 66 ++-------- .../cpp_reassign_validation.phpt | 8 +- .../cpp_reassign_visibility.phpt | 71 +++++------ Zend/zend_execute.c | 4 +- Zend/zend_object_handlers.c | 53 +++----- Zend/zend_object_handlers.h | 14 +- Zend/zend_types.h | 2 - Zend/zend_vm_def.h | 40 ++++-- Zend/zend_vm_execute.h | 120 ++++++++++++------ ext/opcache/jit/zend_jit_helpers.c | 24 ++-- 22 files changed, 369 insertions(+), 411 deletions(-) create mode 100644 Zend/tests/readonly_props/cpp_reassign_child_redefine.phpt diff --git a/Zend/tests/readonly_props/cpp_reassign_basic.phpt b/Zend/tests/readonly_props/cpp_reassign_basic.phpt index 3725baec13b0e..c1b68761bd911 100644 --- a/Zend/tests/readonly_props/cpp_reassign_basic.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_basic.phpt @@ -5,8 +5,8 @@ Promoted readonly property reassignment in constructor - basic class Point { public function __construct( - public readonly float $x = 0.0, - public readonly float $y = 0.0, + public readonly int $x = 0, + public readonly int $y = 0, ) { // Reassign promoted readonly properties - allowed once $this->x = abs($x); @@ -17,12 +17,12 @@ class Point { $point = new Point(); var_dump($point->x, $point->y); -$point2 = new Point(-5.0, -3.0); +$point2 = new Point(-5, -3); var_dump($point2->x, $point2->y); ?> --EXPECT-- -float(0) -float(0) -float(5) -float(3) +int(0) +int(0) +int(5) +int(3) diff --git a/Zend/tests/readonly_props/cpp_reassign_child_class.phpt b/Zend/tests/readonly_props/cpp_reassign_child_class.phpt index b7215c05d0788..d054585861aa3 100644 --- a/Zend/tests/readonly_props/cpp_reassign_child_class.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_child_class.phpt @@ -8,7 +8,7 @@ class Parent1 { public function __construct( public readonly string $prop = 'parent default', ) { - // Parent does NOT reassign here - leaves opportunity for child + // Parent does NOT reassign here } } @@ -18,15 +18,12 @@ class Child1 extends Parent1 { // Child cannot reassign parent-owned promoted property try { $this->prop = 'child override'; - } catch (Error $e) { - echo $e->getMessage(), "\n"; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } } } -$parent = new Parent1(); -var_dump($parent->prop); - $child = new Child1(); var_dump($child->prop); @@ -42,11 +39,11 @@ class Parent2 { class Child2 extends Parent2 { public function __construct() { parent::__construct(); - // Child cannot reassign - parent already used the one reassignment + // Child cannot reassign parent-owned promoted property try { $this->prop = 'child override'; - } catch (Error $e) { - echo $e->getMessage(), "\n"; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } } } @@ -59,7 +56,7 @@ class Parent3 { public function __construct( public readonly string $parentProp = 'parent default', ) { - // Parent doesn't reassign + // Parent does NOT reassign here } } @@ -71,8 +68,8 @@ class Child3 extends Parent3 { // Child cannot reassign parent's property, but can reassign its own try { $this->parentProp = 'child set parent'; - } catch (Error $e) { - echo $e->getMessage(), "\n"; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } $this->childProp = 'child set own'; } @@ -83,11 +80,10 @@ var_dump($child3->parentProp, $child3->childProp); ?> --EXPECT-- +Error: Cannot modify readonly property Parent1::$prop string(14) "parent default" -Cannot modify readonly property Parent1::$prop -string(14) "parent default" -Cannot modify readonly property Parent2::$prop +Error: Cannot modify readonly property Parent2::$prop string(10) "parent set" -Cannot modify readonly property Parent3::$parentProp +Error: Cannot modify readonly property Parent3::$parentProp string(14) "parent default" string(13) "child set own" diff --git a/Zend/tests/readonly_props/cpp_reassign_child_preempt_parent.phpt b/Zend/tests/readonly_props/cpp_reassign_child_preempt_parent.phpt index 0eb0f4ff95bcc..db2bb81c9eb10 100644 --- a/Zend/tests/readonly_props/cpp_reassign_child_preempt_parent.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_child_preempt_parent.phpt @@ -3,36 +3,11 @@ Promoted readonly property reassignment in constructor - child preempt then pare --FILE-- prop = 'parent set'; - } catch (Error) { - // readonly property set by child class - } - } -} - -class ChildNoCPP extends ParentNoCPP { - public function __construct() { - $this->prop = 'child set'; - parent::__construct(); - } -} - class ParentCPP { public function __construct( public readonly string $prop = 'parent default', ) { - try { - $this->prop = 'parent set'; - } catch (Error) { - // readonly property set by child class - } + $this->prop = 'parent set'; } } @@ -41,20 +16,16 @@ class ChildCPP extends ParentCPP { $this->prop = 'child set'; try { parent::__construct(); - } catch (Error $e) { - echo $e->getMessage(), "\n"; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } } } -$c = new ChildNoCPP(); -var_dump($c->prop); - $c = new ChildCPP(); var_dump($c->prop); ?> --EXPECT-- -string(9) "child set" -Cannot modify readonly property ParentCPP::$prop +Error: Cannot modify readonly property ParentCPP::$prop string(9) "child set" diff --git a/Zend/tests/readonly_props/cpp_reassign_child_redefine.phpt b/Zend/tests/readonly_props/cpp_reassign_child_redefine.phpt new file mode 100644 index 0000000000000..beeef94372856 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_child_redefine.phpt @@ -0,0 +1,89 @@ +--TEST-- +Promoted readonly property reassignment in constructor - child redefines parent property +--FILE-- +x = 'C'; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; + } + } +} + +$c1 = new C1(); +var_dump($c1->x); + +// Case 2: Parent uses CPP and reassigns; child redefines as non-promoted. +// Parent's CPP sets the initial value, but the reassignment fails because the +// child's non-promoted redefinition does not open a reassignment window for the parent. +class P2 { + public function __construct( + public readonly string $x = 'P1', + ) { + $this->x = 'P2'; + } +} + +class C2 extends P2 { + public readonly string $x; + + public function __construct() { + try { + parent::__construct(); + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; + } + } +} + +$c2 = new C2(); +var_dump($c2->x); + +// Case 3: Parent uses CPP, child uses CPP redefinition. +// Child's CPP opens the reassignment window for C3::$x. When parent::__construct() +// runs, P3's CPP tries to initialize C3::$x again, which must fail since C3 +// owns the property and has already initialized it. +class P3 { + public function __construct( + public readonly string $x = 'P', + ) {} +} + +class C3 extends P3 { + public function __construct( + public readonly string $x = 'C1', + ) { + try { + parent::__construct(); + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; + } + } +} + +$c3 = new C3(); +var_dump($c3->x); + +?> +--EXPECT-- +Error: Cannot modify readonly property C1::$x +string(1) "P" +Error: Cannot modify readonly property C2::$x +string(2) "P1" +Error: Cannot modify readonly property C3::$x +string(2) "C1" diff --git a/Zend/tests/readonly_props/cpp_reassign_conditional.phpt b/Zend/tests/readonly_props/cpp_reassign_conditional.phpt index 6c751d72af7d4..e67177e76e3c2 100644 --- a/Zend/tests/readonly_props/cpp_reassign_conditional.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_conditional.phpt @@ -7,17 +7,17 @@ class Config { public function __construct( public readonly ?string $cacheDir = null, ) { - $this->cacheDir ??= sys_get_temp_dir() . '/app_cache'; + $this->cacheDir ??= '/tmp/app_cache'; } } $config1 = new Config(); -var_dump(str_contains($config1->cacheDir, 'app_cache')); +var_dump($config1->cacheDir); $config2 = new Config('/custom/cache'); var_dump($config2->cacheDir); ?> --EXPECT-- -bool(true) +string(14) "/tmp/app_cache" string(13) "/custom/cache" diff --git a/Zend/tests/readonly_props/cpp_reassign_different_object.phpt b/Zend/tests/readonly_props/cpp_reassign_different_object.phpt index da81c17be8ae5..293826f2ee13a 100644 --- a/Zend/tests/readonly_props/cpp_reassign_different_object.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_different_object.phpt @@ -3,64 +3,32 @@ Promoted readonly property reassignment in constructor - different object fails --FILE-- x = abs($x); - } - - public static function createFrom(Point $other): Point { - $new = new self(); - // Cannot modify another object's readonly property - try { - $other->x = 999.0; - } catch (Error $e) { - echo $e->getMessage(), "\n"; - } - return $new; - } -} - -$p1 = new Point(-5.0); -var_dump($p1->x); - -$p2 = Point::createFrom($p1); -var_dump($p1->x); // Unchanged - -// Also test: constructor cannot modify another instance of the same class -class Counter { - private static ?Counter $last = null; - - public function __construct( - public readonly int $value = 0, - ) { - $this->value = $value + 1; // Allowed: own property - - // Cannot modify previous instance - if (self::$last !== null) { + $this->x = $x * 2; + if ($other !== null) { try { - self::$last->value = 999; - } catch (Error $e) { - echo $e->getMessage(), "\n"; + $other->x = 999; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } } - self::$last = $this; } } -$c1 = new Counter(10); -var_dump($c1->value); +$a = new Foo(5); +var_dump($a->x); -$c2 = new Counter(20); -var_dump($c1->value, $c2->value); // $c1 unchanged +$b = new Foo(3, $a); +var_dump($a->x, $b->x); // $a unchanged ?> --EXPECT-- -float(5) -Cannot modify readonly property Point::$x -float(5) -int(11) -Cannot modify readonly property Counter::$value -int(11) -int(21) +int(10) +Error: Cannot modify readonly property Foo::$x +int(10) +int(6) diff --git a/Zend/tests/readonly_props/cpp_reassign_direct_ctor_call.phpt b/Zend/tests/readonly_props/cpp_reassign_direct_ctor_call.phpt index dd936ff41ad16..5ff246eaa46eb 100644 --- a/Zend/tests/readonly_props/cpp_reassign_direct_ctor_call.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_direct_ctor_call.phpt @@ -4,26 +4,6 @@ Promoted readonly properties cannot be reassigned when __construct() is called d value . "\n"; - -// Direct call to __construct() should NOT allow reassignment -try { - $obj->__construct('modified'); - echo "After direct __construct: " . $obj->value . "\n"; -} catch (Error $e) { - echo "Error: " . $e->getMessage() . "\n"; -} - -// Also test with a class that uses reassignment -class Bar { public function __construct( public readonly string $value = 'default', ) { @@ -31,21 +11,19 @@ class Bar { } } -$bar = new Bar('hello'); -echo "Bar initial value: " . $bar->value . "\n"; +$obj = new Foo('hello'); +var_dump($obj->value); -// Direct call should fail during the CPP assignment (property not UNINIT) -// Note: The error happens inside the constructor because CPP assignment happens first +// Direct call fails: CPP assignment cannot reinitialize an already-set property try { - $bar->__construct('world'); - echo "After direct __construct: " . $bar->value . "\n"; -} catch (Error $e) { - echo "Error: " . $e->getMessage() . "\n"; + $obj->__construct('world'); +} catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } +var_dump($obj->value); ?> --EXPECT-- -Initial value: initial +string(5) "HELLO" Error: Cannot modify readonly property Foo::$value -Bar initial value: HELLO -Error: Cannot modify readonly property Bar::$value +string(5) "HELLO" diff --git a/Zend/tests/readonly_props/cpp_reassign_indirect_allowed.phpt b/Zend/tests/readonly_props/cpp_reassign_indirect_allowed.phpt index d126da231030c..8eb209555d43e 100644 --- a/Zend/tests/readonly_props/cpp_reassign_indirect_allowed.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_indirect_allowed.phpt @@ -19,21 +19,6 @@ class CalledMethod { $cm = new CalledMethod(); var_dump($cm->prop); -// Reassignment IS allowed in closures called by the constructor -class ClosureInConstructor { - public function __construct( - public readonly string $prop = 'default', - ) { - $fn = function() { - $this->prop = 'from closure'; - }; - $fn(); - } -} - -$cc = new ClosureInConstructor(); -var_dump($cc->prop); - // But second reassignment still fails class MultipleReassign { public function __construct( @@ -42,8 +27,8 @@ class MultipleReassign { $this->initProp(); try { $this->initProp(); // Second call - should fail - } catch (Error $e) { - echo $e->getMessage(), "\n"; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } } @@ -58,6 +43,5 @@ var_dump($mr->prop); ?> --EXPECT-- string(11) "from method" -string(12) "from closure" -Cannot modify readonly property MultipleReassign::$prop +Error: Cannot modify readonly property MultipleReassign::$prop string(11) "from method" diff --git a/Zend/tests/readonly_props/cpp_reassign_indirect_ops.phpt b/Zend/tests/readonly_props/cpp_reassign_indirect_ops.phpt index da51ad6be4661..e39c3e18d8885 100644 --- a/Zend/tests/readonly_props/cpp_reassign_indirect_ops.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_indirect_ops.phpt @@ -26,8 +26,8 @@ class MultiOp { $this->value += 5; // First modification - allowed try { $this->value++; // Second modification - should fail - } catch (Error $e) { - echo $e->getMessage(), "\n"; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } } } @@ -35,34 +35,8 @@ class MultiOp { $m = new MultiOp(); var_dump($m->value); -// Decrement works too -class Decrement { - public function __construct( - public readonly int $value = 100, - ) { - $this->value--; - } -} - -$d = new Decrement(); -var_dump($d->value); - -// Assignment operators work -class AssignOps { - public function __construct( - public readonly string $text = 'hello', - ) { - $this->text .= ' world'; - } -} - -$a = new AssignOps(); -var_dump($a->text); - ?> --EXPECT-- int(6) -Cannot modify readonly property MultiOp::$value +Error: Cannot modify readonly property MultiOp::$value int(15) -int(99) -string(11) "hello world" diff --git a/Zend/tests/readonly_props/cpp_reassign_multiple_fail.phpt b/Zend/tests/readonly_props/cpp_reassign_multiple_fail.phpt index f66ef39aebb63..fcbce0a372bae 100644 --- a/Zend/tests/readonly_props/cpp_reassign_multiple_fail.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_multiple_fail.phpt @@ -10,8 +10,8 @@ class Example { $this->value = 'first'; // OK - first reassignment try { $this->value = 'second'; // Error - second reassignment - } catch (Error $e) { - echo $e->getMessage(), "\n"; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } } } @@ -21,5 +21,5 @@ var_dump($ex->value); ?> --EXPECT-- -Cannot modify readonly property Example::$value +Error: Cannot modify readonly property Example::$value string(5) "first" diff --git a/Zend/tests/readonly_props/cpp_reassign_nonpromoted.phpt b/Zend/tests/readonly_props/cpp_reassign_nonpromoted.phpt index 0b0c0f311e8c8..092c496c87936 100644 --- a/Zend/tests/readonly_props/cpp_reassign_nonpromoted.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_nonpromoted.phpt @@ -11,8 +11,8 @@ class NonPromoted { $this->prop = 'first'; try { $this->prop = 'second'; // Should fail - not a promoted property - } catch (Error $e) { - echo $e->getMessage(), "\n"; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } } } @@ -31,8 +31,8 @@ class MixedProps { $this->promoted = 'reassigned'; // Allowed (promoted, first reassignment) try { $this->nonPromoted = 'second'; // Should fail (non-promoted) - } catch (Error $e) { - echo $e->getMessage(), "\n"; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } } } @@ -42,8 +42,8 @@ var_dump($m->promoted, $m->nonPromoted); ?> --EXPECT-- -Cannot modify readonly property NonPromoted::$prop +Error: Cannot modify readonly property NonPromoted::$prop string(5) "first" -Cannot modify readonly property MixedProps::$nonPromoted +Error: Cannot modify readonly property MixedProps::$nonPromoted string(10) "reassigned" string(5) "first" diff --git a/Zend/tests/readonly_props/cpp_reassign_outside_ctor.phpt b/Zend/tests/readonly_props/cpp_reassign_outside_ctor.phpt index eb503f1db18e8..f6d0308c49948 100644 --- a/Zend/tests/readonly_props/cpp_reassign_outside_ctor.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_outside_ctor.phpt @@ -5,35 +5,39 @@ Promoted readonly property reassignment in constructor - outside constructor fai class Point { public function __construct( - public readonly float $x = 0.0, + public readonly int $x = 0, ) { $this->x = abs($x); } public function tryModify(): void { - $this->x = 999.0; + $this->x = 999; } } -$point = new Point(-5.0); +$point = new Point(-5); var_dump($point->x); // Cannot reassign from outside constructor try { - $point->x = 100.0; -} catch (Error $e) { - echo $e->getMessage(), "\n"; + $point->x = 100; +} catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } +var_dump($point->x); // Cannot reassign from a method try { $point->tryModify(); -} catch (Error $e) { - echo $e->getMessage(), "\n"; +} catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } +var_dump($point->x); ?> --EXPECT-- -float(5) -Cannot modify readonly property Point::$x -Cannot modify readonly property Point::$x +int(5) +Error: Cannot modify readonly property Point::$x +int(5) +Error: Cannot modify readonly property Point::$x +int(5) diff --git a/Zend/tests/readonly_props/cpp_reassign_reflection.phpt b/Zend/tests/readonly_props/cpp_reassign_reflection.phpt index e94e429a9aded..03c117c2daee3 100644 --- a/Zend/tests/readonly_props/cpp_reassign_reflection.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_reflection.phpt @@ -11,71 +11,29 @@ class Foo { } } -// Test 1: Object created via reflection without constructor, then __construct() called -echo "Test 1: Reflection newInstanceWithoutConstructor + explicit __construct()\n"; $ref = new ReflectionClass(Foo::class); $obj = $ref->newInstanceWithoutConstructor(); -// Property should be uninitialized at this point +// Property is uninitialized at this point try { - echo $obj->bar; - echo "ERROR: Should have thrown for uninitialized property\n"; -} catch (Error $e) { - echo "OK: " . $e->getMessage() . "\n"; + var_dump($obj->bar); +} catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } -// Now call constructor - reassignment should work +// First constructor call: CPP reassignment allowed $obj->__construct('explicit call'); -echo "After __construct: " . $obj->bar . "\n"; +var_dump($obj->bar); -// Second __construct() call should fail -echo "\nTest 2: Second __construct() call should fail\n"; +// Second call fails: property no longer uninitialized try { $obj->__construct('second call'); - echo "ERROR: Second __construct() should have failed\n"; -} catch (Error $e) { - echo "OK: " . $e->getMessage() . "\n"; -} - -// Test 3: Normal new still works -echo "\nTest 3: Normal 'new' still works\n"; -$obj2 = new Foo('via new'); -echo "After new: " . $obj2->bar . "\n"; - -// Second call should fail -try { - $obj2->__construct('second call'); - echo "ERROR: Should have failed\n"; -} catch (Error $e) { - echo "OK: " . $e->getMessage() . "\n"; -} - -// Test 4: Reflection newInstanceArgs (calls constructor) -echo "\nTest 4: Reflection newInstanceArgs\n"; -$obj3 = $ref->newInstanceArgs(['via newInstanceArgs']); -echo "After newInstanceArgs: " . $obj3->bar . "\n"; - -// Second call should fail -try { - $obj3->__construct('second call'); - echo "ERROR: Should have failed\n"; -} catch (Error $e) { - echo "OK: " . $e->getMessage() . "\n"; +} catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } ?> --EXPECT-- -Test 1: Reflection newInstanceWithoutConstructor + explicit __construct() -OK: Typed property Foo::$bar must not be accessed before initialization -After __construct: overwritten in constructor - -Test 2: Second __construct() call should fail -OK: Cannot modify readonly property Foo::$bar - -Test 3: Normal 'new' still works -After new: overwritten in constructor -OK: Cannot modify readonly property Foo::$bar - -Test 4: Reflection newInstanceArgs -After newInstanceArgs: overwritten in constructor -OK: Cannot modify readonly property Foo::$bar +Error: Typed property Foo::$bar must not be accessed before initialization +string(26) "overwritten in constructor" +Error: Cannot modify readonly property Foo::$bar diff --git a/Zend/tests/readonly_props/cpp_reassign_validation.phpt b/Zend/tests/readonly_props/cpp_reassign_validation.phpt index 6539c3ef3316e..f8f9609f5773d 100644 --- a/Zend/tests/readonly_props/cpp_reassign_validation.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_validation.phpt @@ -9,7 +9,7 @@ class User { ) { // Simple validation without filter extension if (!str_contains($email, '@')) { - throw new InvalidArgumentException('Invalid email'); + throw new ValueError('Invalid email'); } $this->email = strtolower($email); // Normalize } @@ -20,11 +20,11 @@ var_dump($user->email); try { new User('not-an-email'); -} catch (InvalidArgumentException $e) { - echo $e->getMessage(), "\n"; +} catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } ?> --EXPECT-- string(16) "test@example.com" -Invalid email +ValueError: Invalid email diff --git a/Zend/tests/readonly_props/cpp_reassign_visibility.phpt b/Zend/tests/readonly_props/cpp_reassign_visibility.phpt index 9a7d552a73543..a16cdbc41799f 100644 --- a/Zend/tests/readonly_props/cpp_reassign_visibility.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_visibility.phpt @@ -3,23 +3,24 @@ Promoted readonly property reassignment in constructor - visibility rules apply --FILE-- init(); + } + + protected function init(): void { } } class Child1 extends Parent1 { - public function __construct() { - parent::__construct(); - // Child cannot reassign - private(set) restricts to declaring class only + protected function init(): void { try { $this->prop = 'child override'; - } catch (Error $e) { - echo $e->getMessage(), "\n"; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } } } @@ -27,69 +28,67 @@ class Child1 extends Parent1 { $child1 = new Child1(); var_dump($child1->prop); -// Case 2: protected(set) - child still cannot reassign parent-owned promoted property +// Case 2: protected(set) - child's init() CAN modify within CPP window class Parent2 { public function __construct( protected(set) public readonly string $prop = 'parent default', ) { - // Parent doesn't use reassignment + $this->init(); + } + + protected function init(): void { } } class Child2 extends Parent2 { - public function __construct() { - parent::__construct(); - try { - $this->prop = 'child override'; - } catch (Error $e) { - echo $e->getMessage(), "\n"; - } + protected function init(): void { + $this->prop = 'child set'; } } $child2 = new Child2(); var_dump($child2->prop); -// Case 3: public (default) - child still cannot reassign parent-owned promoted property +// Case 3: public - child's init() CAN modify within CPP window class Parent3 { public function __construct( public readonly string $prop = 'parent default', ) { - // Parent doesn't use reassignment + $this->init(); + } + + protected function init(): void { } } class Child3 extends Parent3 { - public function __construct() { - parent::__construct(); - try { - $this->prop = 'child override'; - } catch (Error $e) { - echo $e->getMessage(), "\n"; - } + protected function init(): void { + $this->prop = 'child set'; } } $child3 = new Child3(); var_dump($child3->prop); -// Case 4: protected(set) with parent using reassignment - child cannot (one reassignment rule) +// Case 4: protected(set) with parent using reassignment - child cannot (window closed) class Parent4 { public function __construct( protected(set) public readonly string $prop = 'parent default', ) { $this->prop = 'parent set'; // Uses the one reassignment + $this->init(); + } + + protected function init(): void { } } class Child4 extends Parent4 { - public function __construct() { - parent::__construct(); - // Child cannot reassign - parent already used the one reassignment + protected function init(): void { try { $this->prop = 'child override'; - } catch (Error $e) { - echo $e->getMessage(), "\n"; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; } } } @@ -99,11 +98,9 @@ var_dump($child4->prop); ?> --EXPECT-- -Cannot modify readonly property Parent1::$prop -string(14) "parent default" -Cannot modify readonly property Parent2::$prop -string(14) "parent default" -Cannot modify readonly property Parent3::$prop +Error: Cannot modify private(set) property Parent1::$prop from scope Child1 string(14) "parent default" -Cannot modify readonly property Parent4::$prop +string(9) "child set" +string(9) "child set" +Error: Cannot modify readonly property Parent4::$prop string(10) "parent set" diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index ce56833dd7341..2195273718705 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1074,7 +1074,7 @@ static zend_never_inline zval* zend_assign_to_typed_prop(const zend_property_inf if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { if (info->flags & ZEND_ACC_READONLY) { - if (!zend_is_readonly_property_modifiable(property_val, info->ce, zobj)) { + if (!zend_is_readonly_property_modifiable(property_val)) { zend_readonly_property_modification_error(info); return &EG(uninitialized_zval); } @@ -1093,7 +1093,7 @@ static zend_never_inline zval* zend_assign_to_typed_prop(const zend_property_inf return &EG(uninitialized_zval); } - Z_PROP_FLAG_P(property_val) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); + Z_PROP_FLAG_P(property_val) &= ~IS_PROP_REINITABLE; return zend_assign_to_variable_ex(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES(), garbage_ptr); } diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 9baa7e37458de..ccdbd9ba23503 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -46,35 +46,6 @@ #define IN_ISSET ZEND_GUARD_PROPERTY_ISSET #define IN_HOOK ZEND_GUARD_PROPERTY_HOOK -/* Check if we're within a constructor call chain for the given object. - * If ctor_scope is non-NULL, restrict to constructor frames declared by ctor_scope. - * Methods/closures called from that constructor are allowed through the stack walk. */ -static bool zend_is_in_constructor(const zend_object *zobj, const zend_class_entry *ctor_scope) -{ - zend_execute_data *ex = EG(current_execute_data); - while (ex) { - if (ex->func - && (ex->func->common.fn_flags & ZEND_ACC_CTOR) - && (ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS) - && Z_OBJ(ex->This) == zobj - && (!ctor_scope || ex->func->common.scope == ctor_scope)) { - return true; - } - ex = ex->prev_execute_data; - } - return false; -} - -/* Check if we're in the first construction of an object and executing the - * constructor chain declared by ctor_scope. */ -ZEND_API bool zend_is_in_declaring_constructor(const zend_object *zobj, const zend_class_entry *ctor_scope) -{ - if (zobj->extra_flags & IS_OBJ_CTOR_CALLED) { - return false; - } - return zend_is_in_constructor(zobj, ctor_scope); -} - static zend_arg_info zend_call_trampoline_arginfo[1] = {{0}}; static zend_arg_info zend_property_hook_arginfo[1] = {{0}}; @@ -1097,7 +1068,16 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva if (error) { if ((prop_info->flags & ZEND_ACC_READONLY) && Z_TYPE_P(variable_ptr) != IS_UNDEF - && !zend_is_readonly_property_modifiable(variable_ptr, prop_info->ce, zobj)) { + && (!zend_is_readonly_property_modifiable(variable_ptr) + /* Also block if a foreign constructor is performing a CPP initial + * assignment on a property already initialized by the owning class's + * CPP (e.g. child redefines parent's promoted property with its own + * CPP: parent::__construct() must not consume the child's REINITABLE). */ + || ((prop_info->flags & ZEND_ACC_PROMOTED) + && !(Z_PROP_FLAG_P(variable_ptr) & IS_PROP_UNINIT) + && EG(current_execute_data) + && (EG(current_execute_data)->func->common.fn_flags & ZEND_ACC_CTOR) + && EG(current_execute_data)->func->common.scope != prop_info->ce))) { zend_readonly_property_modification_error(prop_info); variable_ptr = &EG(error_zval); goto exit; @@ -1132,13 +1112,18 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva goto exit; } /* For promoted readonly properties being initialized for the first time, - * set IS_PROP_CPP_REINITABLE to allow one reassignment in the constructor. */ + * set IS_PROP_REINITABLE to allow one reassignment in the constructor. + * The flag will be cleared by zend_leave_helper when the constructor exits. + * We check the current execute data directly (no stack walk needed) because + * CPP initialization always runs within the constructor frame itself. */ if ((prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) && (Z_PROP_FLAG_P(variable_ptr) & IS_PROP_UNINIT) - && zend_is_in_declaring_constructor(zobj, prop_info->ce)) { - Z_PROP_FLAG_P(variable_ptr) = IS_PROP_CPP_REINITABLE; + && EG(current_execute_data) + && (EG(current_execute_data)->func->common.fn_flags & ZEND_ACC_CTOR) + && EG(current_execute_data)->func->common.scope == prop_info->ce) { + Z_PROP_FLAG_P(variable_ptr) = IS_PROP_REINITABLE; } else { - Z_PROP_FLAG_P(variable_ptr) &= ~(IS_PROP_UNINIT|IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); + Z_PROP_FLAG_P(variable_ptr) &= ~(IS_PROP_UNINIT|IS_PROP_REINITABLE); } value = &tmp; } diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index 650771517a703..c59ff27958e9d 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -276,19 +276,11 @@ ZEND_API zend_result zend_std_get_closure(zend_object *obj, zend_class_entry **c ZEND_API HashTable *rebuild_object_properties_internal(zend_object *zobj); ZEND_API ZEND_COLD zend_never_inline void zend_bad_method_call(const zend_function *fbc, const zend_string *method_name, const zend_class_entry *scope); ZEND_API ZEND_COLD zend_never_inline void zend_abstract_method_call(const zend_function *fbc); -ZEND_API bool zend_is_in_declaring_constructor(const zend_object *zobj, const zend_class_entry *ctor_scope); -/* Check if a readonly property can be modified (has REINITABLE or CPP_REINITABLE flag and is in constructor) */ -static zend_always_inline bool zend_is_readonly_property_modifiable(zval *property_val, const zend_class_entry *declaring_ce, zend_object *zobj) +/* Check if a readonly property can be modified (has REINITABLE flag set by clone or CPP initialization) */ +static zend_always_inline bool zend_is_readonly_property_modifiable(const zval *property_val) { - if (Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE) { - return true; - } - if ((Z_PROP_FLAG_P(property_val) & IS_PROP_CPP_REINITABLE) - && zend_is_in_declaring_constructor(zobj, declaring_ce)) { - return true; - } - return false; + return (Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE) != 0; } static zend_always_inline HashTable *zend_std_get_properties_ex(zend_object *object) diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 35bc1234ebdcb..22dbfa9be879b 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -858,7 +858,6 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { #define IS_OBJ_LAZY_UNINITIALIZED (1U<<31) /* Virtual proxy or uninitialized Ghost */ #define IS_OBJ_LAZY_PROXY (1U<<30) /* Virtual proxy (may be initialized) */ -#define IS_OBJ_CTOR_CALLED (1U<<29) /* A constructor has completed on this object */ #define OBJ_EXTRA_FLAGS(obj) ((obj)->extra_flags) @@ -1595,7 +1594,6 @@ static zend_always_inline uint32_t zval_delref_p(zval* pz) { #define IS_PROP_UNINIT (1<<0) #define IS_PROP_REINITABLE (1<<1) /* It has impact only on readonly properties */ #define IS_PROP_LAZY (1<<2) -#define IS_PROP_CPP_REINITABLE (1<<3) /* Allows one reassignment of promoted readonly property in constructor */ #define Z_PROP_FLAG_P(z) Z_EXTRA_P(z) #define ZVAL_COPY_VALUE_PROP(z, v) \ do { *(z) = *(v); } while (0) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 218a3ae90f95c..63c0d1b4eee13 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3000,13 +3000,21 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif - if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { + /* When a constructor exits, clear IS_PROP_REINITABLE from all promoted readonly + * properties of the declaring class. Runs for both 'new Foo()' (RELEASE_THIS set) + * and 'parent::__construct()' (only HAS_THIS set, no RELEASE_THIS). */ + if ((call_info & ZEND_CALL_HAS_THIS) && (EX(func)->common.fn_flags & ZEND_ACC_CTOR)) { zend_object *obj = Z_OBJ(execute_data->This); - /* Mark that a constructor has completed on this object */ - if (EX(func)->common.fn_flags & ZEND_ACC_CTOR) { - obj->extra_flags |= IS_OBJ_CTOR_CALLED; - } - OBJ_RELEASE(obj); + zend_property_info *ctor_prop_info; + ZEND_HASH_MAP_FOREACH_PTR(&EX(func)->common.scope->properties_info, ctor_prop_info) { + if ((ctor_prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) + && IS_VALID_PROPERTY_OFFSET(ctor_prop_info->offset)) { + Z_PROP_FLAG_P(OBJ_PROP(obj, ctor_prop_info->offset)) &= ~IS_PROP_REINITABLE; + } + } ZEND_HASH_FOREACH_END(); + } + if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { + OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } @@ -3039,13 +3047,21 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) * as that may free the op_array. */ zend_vm_stack_free_extra_args_ex(call_info, execute_data); - if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { + /* When a constructor exits, clear IS_PROP_REINITABLE from all promoted readonly + * properties of the declaring class. Runs for both 'new Foo()' (RELEASE_THIS set) + * and 'parent::__construct()' (only HAS_THIS set, no RELEASE_THIS). */ + if ((call_info & ZEND_CALL_HAS_THIS) && (EX(func)->common.fn_flags & ZEND_ACC_CTOR)) { zend_object *obj = Z_OBJ(execute_data->This); - /* Mark that a constructor has completed on this object */ - if (EX(func)->common.fn_flags & ZEND_ACC_CTOR) { - obj->extra_flags |= IS_OBJ_CTOR_CALLED; - } - OBJ_RELEASE(obj); + zend_property_info *ctor_prop_info; + ZEND_HASH_MAP_FOREACH_PTR(&EX(func)->common.scope->properties_info, ctor_prop_info) { + if ((ctor_prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) + && IS_VALID_PROPERTY_OFFSET(ctor_prop_info->offset)) { + Z_PROP_FLAG_P(OBJ_PROP(obj, ctor_prop_info->offset)) &= ~IS_PROP_REINITABLE; + } + } ZEND_HASH_FOREACH_END(); + } + if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { + OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 7c6cca089945c..53e3cc8d656a8 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1160,13 +1160,21 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif - if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { + /* When a constructor exits, clear IS_PROP_REINITABLE from all promoted readonly + * properties of the declaring class. Runs for both 'new Foo()' (RELEASE_THIS set) + * and 'parent::__construct()' (only HAS_THIS set, no RELEASE_THIS). */ + if ((call_info & ZEND_CALL_HAS_THIS) && (EX(func)->common.fn_flags & ZEND_ACC_CTOR)) { zend_object *obj = Z_OBJ(execute_data->This); - /* Mark that a constructor has completed on this object */ - if (EX(func)->common.fn_flags & ZEND_ACC_CTOR) { - obj->extra_flags |= IS_OBJ_CTOR_CALLED; - } - OBJ_RELEASE(obj); + zend_property_info *ctor_prop_info; + ZEND_HASH_MAP_FOREACH_PTR(&EX(func)->common.scope->properties_info, ctor_prop_info) { + if ((ctor_prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) + && IS_VALID_PROPERTY_OFFSET(ctor_prop_info->offset)) { + Z_PROP_FLAG_P(OBJ_PROP(obj, ctor_prop_info->offset)) &= ~IS_PROP_REINITABLE; + } + } ZEND_HASH_FOREACH_END(); + } + if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { + OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } @@ -1199,13 +1207,21 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV * as that may free the op_array. */ zend_vm_stack_free_extra_args_ex(call_info, execute_data); - if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { + /* When a constructor exits, clear IS_PROP_REINITABLE from all promoted readonly + * properties of the declaring class. Runs for both 'new Foo()' (RELEASE_THIS set) + * and 'parent::__construct()' (only HAS_THIS set, no RELEASE_THIS). */ + if ((call_info & ZEND_CALL_HAS_THIS) && (EX(func)->common.fn_flags & ZEND_ACC_CTOR)) { zend_object *obj = Z_OBJ(execute_data->This); - /* Mark that a constructor has completed on this object */ - if (EX(func)->common.fn_flags & ZEND_ACC_CTOR) { - obj->extra_flags |= IS_OBJ_CTOR_CALLED; - } - OBJ_RELEASE(obj); + zend_property_info *ctor_prop_info; + ZEND_HASH_MAP_FOREACH_PTR(&EX(func)->common.scope->properties_info, ctor_prop_info) { + if ((ctor_prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) + && IS_VALID_PROPERTY_OFFSET(ctor_prop_info->offset)) { + Z_PROP_FLAG_P(OBJ_PROP(obj, ctor_prop_info->offset)) &= ~IS_PROP_REINITABLE; + } + } ZEND_HASH_FOREACH_END(); + } + if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { + OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } @@ -53890,13 +53906,21 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif - if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { + /* When a constructor exits, clear IS_PROP_REINITABLE from all promoted readonly + * properties of the declaring class. Runs for both 'new Foo()' (RELEASE_THIS set) + * and 'parent::__construct()' (only HAS_THIS set, no RELEASE_THIS). */ + if ((call_info & ZEND_CALL_HAS_THIS) && (EX(func)->common.fn_flags & ZEND_ACC_CTOR)) { zend_object *obj = Z_OBJ(execute_data->This); - /* Mark that a constructor has completed on this object */ - if (EX(func)->common.fn_flags & ZEND_ACC_CTOR) { - obj->extra_flags |= IS_OBJ_CTOR_CALLED; - } - OBJ_RELEASE(obj); + zend_property_info *ctor_prop_info; + ZEND_HASH_MAP_FOREACH_PTR(&EX(func)->common.scope->properties_info, ctor_prop_info) { + if ((ctor_prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) + && IS_VALID_PROPERTY_OFFSET(ctor_prop_info->offset)) { + Z_PROP_FLAG_P(OBJ_PROP(obj, ctor_prop_info->offset)) &= ~IS_PROP_REINITABLE; + } + } ZEND_HASH_FOREACH_END(); + } + if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { + OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } @@ -53929,13 +53953,21 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend * as that may free the op_array. */ zend_vm_stack_free_extra_args_ex(call_info, execute_data); - if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { + /* When a constructor exits, clear IS_PROP_REINITABLE from all promoted readonly + * properties of the declaring class. Runs for both 'new Foo()' (RELEASE_THIS set) + * and 'parent::__construct()' (only HAS_THIS set, no RELEASE_THIS). */ + if ((call_info & ZEND_CALL_HAS_THIS) && (EX(func)->common.fn_flags & ZEND_ACC_CTOR)) { zend_object *obj = Z_OBJ(execute_data->This); - /* Mark that a constructor has completed on this object */ - if (EX(func)->common.fn_flags & ZEND_ACC_CTOR) { - obj->extra_flags |= IS_OBJ_CTOR_CALLED; - } - OBJ_RELEASE(obj); + zend_property_info *ctor_prop_info; + ZEND_HASH_MAP_FOREACH_PTR(&EX(func)->common.scope->properties_info, ctor_prop_info) { + if ((ctor_prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) + && IS_VALID_PROPERTY_OFFSET(ctor_prop_info->offset)) { + Z_PROP_FLAG_P(OBJ_PROP(obj, ctor_prop_info->offset)) &= ~IS_PROP_REINITABLE; + } + } ZEND_HASH_FOREACH_END(); + } + if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { + OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } @@ -110168,13 +110200,21 @@ ZEND_API void execute_ex(zend_execute_data *ex) #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif - if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { + /* When a constructor exits, clear IS_PROP_REINITABLE from all promoted readonly + * properties of the declaring class. Runs for both 'new Foo()' (RELEASE_THIS set) + * and 'parent::__construct()' (only HAS_THIS set, no RELEASE_THIS). */ + if ((call_info & ZEND_CALL_HAS_THIS) && (EX(func)->common.fn_flags & ZEND_ACC_CTOR)) { zend_object *obj = Z_OBJ(execute_data->This); - /* Mark that a constructor has completed on this object */ - if (EX(func)->common.fn_flags & ZEND_ACC_CTOR) { - obj->extra_flags |= IS_OBJ_CTOR_CALLED; - } - OBJ_RELEASE(obj); + zend_property_info *ctor_prop_info; + ZEND_HASH_MAP_FOREACH_PTR(&EX(func)->common.scope->properties_info, ctor_prop_info) { + if ((ctor_prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) + && IS_VALID_PROPERTY_OFFSET(ctor_prop_info->offset)) { + Z_PROP_FLAG_P(OBJ_PROP(obj, ctor_prop_info->offset)) &= ~IS_PROP_REINITABLE; + } + } ZEND_HASH_FOREACH_END(); + } + if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { + OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } @@ -110207,13 +110247,21 @@ ZEND_API void execute_ex(zend_execute_data *ex) * as that may free the op_array. */ zend_vm_stack_free_extra_args_ex(call_info, execute_data); - if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { + /* When a constructor exits, clear IS_PROP_REINITABLE from all promoted readonly + * properties of the declaring class. Runs for both 'new Foo()' (RELEASE_THIS set) + * and 'parent::__construct()' (only HAS_THIS set, no RELEASE_THIS). */ + if ((call_info & ZEND_CALL_HAS_THIS) && (EX(func)->common.fn_flags & ZEND_ACC_CTOR)) { zend_object *obj = Z_OBJ(execute_data->This); - /* Mark that a constructor has completed on this object */ - if (EX(func)->common.fn_flags & ZEND_ACC_CTOR) { - obj->extra_flags |= IS_OBJ_CTOR_CALLED; - } - OBJ_RELEASE(obj); + zend_property_info *ctor_prop_info; + ZEND_HASH_MAP_FOREACH_PTR(&EX(func)->common.scope->properties_info, ctor_prop_info) { + if ((ctor_prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) + && IS_VALID_PROPERTY_OFFSET(ctor_prop_info->offset)) { + Z_PROP_FLAG_P(OBJ_PROP(obj, ctor_prop_info->offset)) &= ~IS_PROP_REINITABLE; + } + } ZEND_HASH_FOREACH_END(); + } + if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { + OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 7ad087b8494e4..1a89ed8d7b830 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -2807,8 +2807,8 @@ static zend_always_inline bool verify_readonly_and_avis(zval *property_val, zend { if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { if (info->flags & ZEND_ACC_READONLY) { - ZEND_ASSERT(zobj != NULL); /* CPP_REINITABLE only applies to instance properties */ - if (!zend_is_readonly_property_modifiable(property_val, info->ce, zobj)) { + ZEND_ASSERT(zobj != NULL); /* REINITABLE only applies to instance properties */ + if (!zend_is_readonly_property_modifiable(property_val)) { zend_readonly_property_modification_error(info); return false; } @@ -2853,7 +2853,7 @@ static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend return; } - Z_PROP_FLAG_P(property_val) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); + Z_PROP_FLAG_P(property_val) &= ~IS_PROP_REINITABLE; value = zend_assign_to_variable_ex(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); if (result) { @@ -2910,7 +2910,7 @@ static void ZEND_FASTCALL zend_jit_assign_op_to_typed_prop(zval *zptr, zend_prop binary_op(&z_copy, zptr, value); if (EXPECTED(zend_verify_property_type(prop_info, &z_copy, EX_USES_STRICT_TYPES()))) { - Z_PROP_FLAG_P(zptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); + Z_PROP_FLAG_P(zptr) &= ~IS_PROP_REINITABLE; zval_ptr_dtor(zptr); ZVAL_COPY_VALUE(zptr, &z_copy); } else { @@ -3007,13 +3007,13 @@ static void ZEND_FASTCALL zend_jit_inc_typed_prop(zval *var_ptr, zend_property_i zend_long val = _zend_jit_throw_inc_prop_error(prop_info); ZVAL_LONG(var_ptr, val); } else { - Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); + Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; } } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) { zval_ptr_dtor(var_ptr); ZVAL_COPY_VALUE(var_ptr, &tmp); } else { - Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); + Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; zval_ptr_dtor(&tmp); } } @@ -3039,13 +3039,13 @@ static void ZEND_FASTCALL zend_jit_dec_typed_prop(zval *var_ptr, zend_property_i zend_long val = _zend_jit_throw_dec_prop_error(prop_info); ZVAL_LONG(var_ptr, val); } else { - Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); + Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; } } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) { zval_ptr_dtor(var_ptr); ZVAL_COPY_VALUE(var_ptr, &tmp); } else { - Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); + Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; zval_ptr_dtor(&tmp); } } @@ -3087,14 +3087,14 @@ static void ZEND_FASTCALL zend_jit_post_inc_typed_prop(zval *var_ptr, zend_prope zend_long val = _zend_jit_throw_inc_prop_error(prop_info); ZVAL_LONG(var_ptr, val); } else { - Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); + Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; } } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) { zval_ptr_dtor(var_ptr); ZVAL_COPY_VALUE(var_ptr, result); ZVAL_UNDEF(result); } else { - Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); + Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; } } @@ -3121,14 +3121,14 @@ static void ZEND_FASTCALL zend_jit_post_dec_typed_prop(zval *var_ptr, zend_prope zend_long val = _zend_jit_throw_dec_prop_error(prop_info); ZVAL_LONG(var_ptr, val); } else { - Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); + Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; } } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) { zval_ptr_dtor(var_ptr); ZVAL_COPY_VALUE(var_ptr, result); ZVAL_UNDEF(result); } else { - Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE|IS_PROP_CPP_REINITABLE); + Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; } } From b712b5b0323c43b9136fcc2265728de6d94c357c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 19 Feb 2026 13:28:00 +0100 Subject: [PATCH 7/9] CPP-owning model --- .../cpp_reassign_child_redefine.phpt | 13 ++----- .../cpp_reassign_indirect_allowed.phpt | 10 ++--- .../cpp_reassign_reflection.phpt | 6 ++- .../cpp_reassign_visibility.phpt | 4 +- Zend/zend_execute.c | 8 ++-- Zend/zend_object_handlers.c | 38 +++++++++++++++---- 6 files changed, 48 insertions(+), 31 deletions(-) diff --git a/Zend/tests/readonly_props/cpp_reassign_child_redefine.phpt b/Zend/tests/readonly_props/cpp_reassign_child_redefine.phpt index beeef94372856..4b5cb96e77486 100644 --- a/Zend/tests/readonly_props/cpp_reassign_child_redefine.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_child_redefine.phpt @@ -29,8 +29,8 @@ $c1 = new C1(); var_dump($c1->x); // Case 2: Parent uses CPP and reassigns; child redefines as non-promoted. -// Parent's CPP sets the initial value, but the reassignment fails because the -// child's non-promoted redefinition does not open a reassignment window for the parent. +// The child does not use CPP, so it does not claim CPP ownership of the property. +// P2's CPP "owns" the reassignment window: P2's body write succeeds. class P2 { public function __construct( public readonly string $x = 'P1', @@ -43,11 +43,7 @@ class C2 extends P2 { public readonly string $x; public function __construct() { - try { - parent::__construct(); - } catch (Throwable $e) { - echo get_class($e), ": ", $e->getMessage(), "\n"; - } + parent::__construct(); } } @@ -83,7 +79,6 @@ var_dump($c3->x); --EXPECT-- Error: Cannot modify readonly property C1::$x string(1) "P" -Error: Cannot modify readonly property C2::$x -string(2) "P1" +string(2) "P2" Error: Cannot modify readonly property C3::$x string(2) "C1" diff --git a/Zend/tests/readonly_props/cpp_reassign_indirect_allowed.phpt b/Zend/tests/readonly_props/cpp_reassign_indirect_allowed.phpt index 8eb209555d43e..3437d54dffbc5 100644 --- a/Zend/tests/readonly_props/cpp_reassign_indirect_allowed.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_indirect_allowed.phpt @@ -24,16 +24,16 @@ class MultipleReassign { public function __construct( public readonly string $prop = 'default', ) { - $this->initProp(); + $this->initProp("first from method"); try { - $this->initProp(); // Second call - should fail + $this->initProp("second from method"); // Second call - should fail } catch (Throwable $e) { echo get_class($e), ": ", $e->getMessage(), "\n"; } } - private function initProp(): void { - $this->prop = 'from method'; + private function initProp(string $v): void { + $this->prop = $v; } } @@ -44,4 +44,4 @@ var_dump($mr->prop); --EXPECT-- string(11) "from method" Error: Cannot modify readonly property MultipleReassign::$prop -string(11) "from method" +string(17) "first from method" diff --git a/Zend/tests/readonly_props/cpp_reassign_reflection.phpt b/Zend/tests/readonly_props/cpp_reassign_reflection.phpt index 03c117c2daee3..8f3033e0f13a5 100644 --- a/Zend/tests/readonly_props/cpp_reassign_reflection.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_reflection.phpt @@ -7,7 +7,7 @@ class Foo { public function __construct( public readonly string $bar = 'default', ) { - $this->bar = 'overwritten in constructor'; + $this->bar = strtoupper($bar); } } @@ -31,9 +31,11 @@ try { } catch (Throwable $e) { echo get_class($e), ": ", $e->getMessage(), "\n"; } +var_dump($obj->bar); ?> --EXPECT-- Error: Typed property Foo::$bar must not be accessed before initialization -string(26) "overwritten in constructor" +string(13) "EXPLICIT CALL" Error: Cannot modify readonly property Foo::$bar +string(13) "EXPLICIT CALL" diff --git a/Zend/tests/readonly_props/cpp_reassign_visibility.phpt b/Zend/tests/readonly_props/cpp_reassign_visibility.phpt index a16cdbc41799f..340e14478407f 100644 --- a/Zend/tests/readonly_props/cpp_reassign_visibility.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_visibility.phpt @@ -49,10 +49,10 @@ class Child2 extends Parent2 { $child2 = new Child2(); var_dump($child2->prop); -// Case 3: public - child's init() CAN modify within CPP window +// Case 3: public(set) - child's init() CAN modify within CPP window class Parent3 { public function __construct( - public readonly string $prop = 'parent default', + public public(set) readonly string $prop = 'parent default', ) { $this->init(); } diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 2195273718705..d80095f311ef8 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1073,11 +1073,9 @@ static zend_never_inline zval* zend_assign_to_typed_prop(const zend_property_inf zval tmp; if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { - if (info->flags & ZEND_ACC_READONLY) { - if (!zend_is_readonly_property_modifiable(property_val)) { - zend_readonly_property_modification_error(info); - return &EG(uninitialized_zval); - } + if (info->flags & ZEND_ACC_READONLY && !zend_is_readonly_property_modifiable(property_val)) { + zend_readonly_property_modification_error(info); + return &EG(uninitialized_zval); } if (info->flags & ZEND_ACC_PPP_SET_MASK && !zend_asymmetric_property_has_set_access(info)) { zend_asymmetric_visibility_property_modification_error(info, "modify"); diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index ccdbd9ba23503..6b62e08810841 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1111,16 +1111,38 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva variable_ptr = &EG(error_zval); goto exit; } - /* For promoted readonly properties being initialized for the first time, - * set IS_PROP_REINITABLE to allow one reassignment in the constructor. - * The flag will be cleared by zend_leave_helper when the constructor exits. - * We check the current execute data directly (no stack walk needed) because - * CPP initialization always runs within the constructor frame itself. */ - if ((prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) + /* For readonly properties initialized for the first time via CPP, set + * IS_PROP_REINITABLE to allow one reassignment in the constructor body. + * The flag is cleared by zend_leave_helper when the constructor exits. + * + * Classical case: the property is promoted in the declaring class and the + * executing constructor belongs to that class (scope == prop_info->ce). + * + * Extended case: a child class redeclared the property without CPP, so + * prop_info->ce is the child but the property isn't promoted there. CPP + * "ownership" still belongs to the ancestor whose constructor has CPP for + * this property name, so its body is allowed to reassign once. The clearing + * loop in zend_leave_helper iterates the exiting ctor's own promoted props, + * which share the same object slot, so cleanup happens automatically. */ + bool reinitable = false; + if ((prop_info->flags & ZEND_ACC_READONLY) && (Z_PROP_FLAG_P(variable_ptr) & IS_PROP_UNINIT) && EG(current_execute_data) - && (EG(current_execute_data)->func->common.fn_flags & ZEND_ACC_CTOR) - && EG(current_execute_data)->func->common.scope == prop_info->ce) { + && (EG(current_execute_data)->func->common.fn_flags & ZEND_ACC_CTOR)) { + zend_class_entry *ctor_scope = EG(current_execute_data)->func->common.scope; + if (prop_info->flags & ZEND_ACC_PROMOTED) { + reinitable = (ctor_scope == prop_info->ce); + } else if (ctor_scope != prop_info->ce) { + /* Child redeclared without CPP: check if the executing ctor's class + * has a CPP declaration for this property name. */ + zend_property_info *scope_prop = zend_hash_find_ptr( + &ctor_scope->properties_info, prop_info->name); + reinitable = scope_prop != NULL + && (scope_prop->flags & (ZEND_ACC_READONLY|ZEND_ACC_PROMOTED)) + == (ZEND_ACC_READONLY|ZEND_ACC_PROMOTED); + } + } + if (reinitable) { Z_PROP_FLAG_P(variable_ptr) = IS_PROP_REINITABLE; } else { Z_PROP_FLAG_P(variable_ptr) &= ~(IS_PROP_UNINIT|IS_PROP_REINITABLE); From 6cdbaf74735a1e9e6417e3c9b8d1343269ad5ebf Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 19 Feb 2026 13:58:06 +0100 Subject: [PATCH 8/9] cleanup --- .../cpp_reassign_child_redefine.phpt | 4 +- Zend/zend_execute.c | 2 +- Zend/zend_vm_def.h | 4 +- Zend/zend_vm_execute.h | 120 +++++++++--------- 4 files changed, 65 insertions(+), 65 deletions(-) diff --git a/Zend/tests/readonly_props/cpp_reassign_child_redefine.phpt b/Zend/tests/readonly_props/cpp_reassign_child_redefine.phpt index 4b5cb96e77486..1cd82db84f528 100644 --- a/Zend/tests/readonly_props/cpp_reassign_child_redefine.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_child_redefine.phpt @@ -4,8 +4,8 @@ Promoted readonly property reassignment in constructor - child redefines parent type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage, NULL EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); FREE_OP_DATA(); } else { value = zend_assign_to_variable_ex(prop, value, OP_DATA_TYPE, EX_USES_STRICT_TYPES(), &garbage); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 53e3cc8d656a8..5656a7a3bcac1 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1001,7 +1001,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_STATIC value = RT_CONSTANT((opline+1), (opline+1)->op1); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage, NULL EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); } else { @@ -1039,7 +1039,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_STATIC value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage, NULL EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); } else { value = zend_assign_to_variable_ex(prop, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); @@ -1077,7 +1077,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_STATIC value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage, NULL EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); } else { @@ -24442,7 +24442,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -24599,7 +24599,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -24754,7 +24754,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -27150,7 +27150,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -27306,7 +27306,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -27460,7 +27460,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -30974,7 +30974,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -31131,7 +31131,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -31286,7 +31286,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -33508,7 +33508,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -33666,7 +33666,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -33822,7 +33822,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -35606,7 +35606,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -35763,7 +35763,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -35918,7 +35918,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -38175,7 +38175,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -38333,7 +38333,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -38489,7 +38489,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -42544,7 +42544,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -42702,7 +42702,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -42858,7 +42858,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -46367,7 +46367,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -46524,7 +46524,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -46679,7 +46679,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -51481,7 +51481,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -51639,7 +51639,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -51795,7 +51795,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -53747,7 +53747,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_STATIC_PROP value = RT_CONSTANT((opline+1), (opline+1)->op1); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage, NULL EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); } else { @@ -53785,7 +53785,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_STATIC_PROP value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage, NULL EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); } else { value = zend_assign_to_variable_ex(prop, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); @@ -53823,7 +53823,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_STATIC_PROP value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage, NULL EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); } else { @@ -76870,7 +76870,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -77027,7 +77027,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -77182,7 +77182,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -79578,7 +79578,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -79734,7 +79734,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -79888,7 +79888,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -83402,7 +83402,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -83559,7 +83559,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -83714,7 +83714,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -85936,7 +85936,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -86094,7 +86094,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -86250,7 +86250,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -88034,7 +88034,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -88191,7 +88191,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -88346,7 +88346,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -90603,7 +90603,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -90761,7 +90761,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -90917,7 +90917,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -94972,7 +94972,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -95130,7 +95130,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -95286,7 +95286,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -98795,7 +98795,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -98952,7 +98952,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -99107,7 +99107,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -103807,7 +103807,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -103965,7 +103965,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -104121,7 +104121,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage, zobj EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: From 74406a1c2c7ef53ff119a41405bb7946d0d21725 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 19 Feb 2026 14:35:48 +0100 Subject: [PATCH 9/9] dedup --- Zend/zend_execute.c | 18 ++++++- Zend/zend_vm_def.h | 28 +--------- Zend/zend_vm_execute.h | 84 +++--------------------------- ext/opcache/jit/zend_jit_helpers.c | 53 +++++++++---------- ext/opcache/jit/zend_jit_ir.c | 22 ++++---- 5 files changed, 59 insertions(+), 146 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 022bf84034d65..0b18e530d4bbe 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1073,7 +1073,7 @@ static zend_never_inline zval* zend_assign_to_typed_prop(const zend_property_inf zval tmp; if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { - if (info->flags & ZEND_ACC_READONLY && !zend_is_readonly_property_modifiable(property_val)) { + if ((info->flags & ZEND_ACC_READONLY) && !zend_is_readonly_property_modifiable(property_val)) { zend_readonly_property_modification_error(info); return &EG(uninitialized_zval); } @@ -5897,6 +5897,22 @@ static zend_always_inline zend_execute_data *_zend_vm_stack_push_call_frame(uint /* This callback disables optimization of "vm_stack_data" variable in VM */ ZEND_API void (ZEND_FASTCALL *zend_touch_vm_stack_data)(void *vm_stack_data) = NULL; +/* Clear IS_PROP_REINITABLE from all promoted readonly properties of the exiting + * constructor's scope. Called for both 'new Foo()' and 'parent::__construct()'. */ +static zend_always_inline void zend_ctor_clear_promoted_readonly_reinitable(zend_execute_data *ex, uint32_t call_info) +{ + if ((call_info & ZEND_CALL_HAS_THIS) && (ex->func->common.fn_flags & ZEND_ACC_CTOR)) { + zend_object *obj = Z_OBJ(ex->This); + zend_property_info *ctor_prop_info; + ZEND_HASH_MAP_FOREACH_PTR(&ex->func->common.scope->properties_info, ctor_prop_info) { + if ((ctor_prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) + && IS_VALID_PROPERTY_OFFSET(ctor_prop_info->offset)) { + Z_PROP_FLAG_P(OBJ_PROP(obj, ctor_prop_info->offset)) &= ~IS_PROP_REINITABLE; + } + } ZEND_HASH_FOREACH_END(); + } +} + #include "zend_vm_execute.h" ZEND_API zend_result zend_set_user_opcode_handler(zend_uchar opcode, user_opcode_handler_t handler) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index b89a9d6a8b7ec..fe3cddd09c692 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3000,19 +3000,7 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif - /* When a constructor exits, clear IS_PROP_REINITABLE from all promoted readonly - * properties of the declaring class. Runs for both 'new Foo()' (RELEASE_THIS set) - * and 'parent::__construct()' (only HAS_THIS set, no RELEASE_THIS). */ - if ((call_info & ZEND_CALL_HAS_THIS) && (EX(func)->common.fn_flags & ZEND_ACC_CTOR)) { - zend_object *obj = Z_OBJ(execute_data->This); - zend_property_info *ctor_prop_info; - ZEND_HASH_MAP_FOREACH_PTR(&EX(func)->common.scope->properties_info, ctor_prop_info) { - if ((ctor_prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) - && IS_VALID_PROPERTY_OFFSET(ctor_prop_info->offset)) { - Z_PROP_FLAG_P(OBJ_PROP(obj, ctor_prop_info->offset)) &= ~IS_PROP_REINITABLE; - } - } ZEND_HASH_FOREACH_END(); - } + zend_ctor_clear_promoted_readonly_reinitable(execute_data, call_info); if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { @@ -3047,19 +3035,7 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) * as that may free the op_array. */ zend_vm_stack_free_extra_args_ex(call_info, execute_data); - /* When a constructor exits, clear IS_PROP_REINITABLE from all promoted readonly - * properties of the declaring class. Runs for both 'new Foo()' (RELEASE_THIS set) - * and 'parent::__construct()' (only HAS_THIS set, no RELEASE_THIS). */ - if ((call_info & ZEND_CALL_HAS_THIS) && (EX(func)->common.fn_flags & ZEND_ACC_CTOR)) { - zend_object *obj = Z_OBJ(execute_data->This); - zend_property_info *ctor_prop_info; - ZEND_HASH_MAP_FOREACH_PTR(&EX(func)->common.scope->properties_info, ctor_prop_info) { - if ((ctor_prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) - && IS_VALID_PROPERTY_OFFSET(ctor_prop_info->offset)) { - Z_PROP_FLAG_P(OBJ_PROP(obj, ctor_prop_info->offset)) &= ~IS_PROP_REINITABLE; - } - } ZEND_HASH_FOREACH_END(); - } + zend_ctor_clear_promoted_readonly_reinitable(execute_data, call_info); if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 5656a7a3bcac1..abba8fe3f60de 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1160,19 +1160,7 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif - /* When a constructor exits, clear IS_PROP_REINITABLE from all promoted readonly - * properties of the declaring class. Runs for both 'new Foo()' (RELEASE_THIS set) - * and 'parent::__construct()' (only HAS_THIS set, no RELEASE_THIS). */ - if ((call_info & ZEND_CALL_HAS_THIS) && (EX(func)->common.fn_flags & ZEND_ACC_CTOR)) { - zend_object *obj = Z_OBJ(execute_data->This); - zend_property_info *ctor_prop_info; - ZEND_HASH_MAP_FOREACH_PTR(&EX(func)->common.scope->properties_info, ctor_prop_info) { - if ((ctor_prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) - && IS_VALID_PROPERTY_OFFSET(ctor_prop_info->offset)) { - Z_PROP_FLAG_P(OBJ_PROP(obj, ctor_prop_info->offset)) &= ~IS_PROP_REINITABLE; - } - } ZEND_HASH_FOREACH_END(); - } + zend_ctor_clear_promoted_readonly_reinitable(execute_data, call_info); if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { @@ -1207,19 +1195,7 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV * as that may free the op_array. */ zend_vm_stack_free_extra_args_ex(call_info, execute_data); - /* When a constructor exits, clear IS_PROP_REINITABLE from all promoted readonly - * properties of the declaring class. Runs for both 'new Foo()' (RELEASE_THIS set) - * and 'parent::__construct()' (only HAS_THIS set, no RELEASE_THIS). */ - if ((call_info & ZEND_CALL_HAS_THIS) && (EX(func)->common.fn_flags & ZEND_ACC_CTOR)) { - zend_object *obj = Z_OBJ(execute_data->This); - zend_property_info *ctor_prop_info; - ZEND_HASH_MAP_FOREACH_PTR(&EX(func)->common.scope->properties_info, ctor_prop_info) { - if ((ctor_prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) - && IS_VALID_PROPERTY_OFFSET(ctor_prop_info->offset)) { - Z_PROP_FLAG_P(OBJ_PROP(obj, ctor_prop_info->offset)) &= ~IS_PROP_REINITABLE; - } - } ZEND_HASH_FOREACH_END(); - } + zend_ctor_clear_promoted_readonly_reinitable(execute_data, call_info); if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { @@ -53906,19 +53882,7 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif - /* When a constructor exits, clear IS_PROP_REINITABLE from all promoted readonly - * properties of the declaring class. Runs for both 'new Foo()' (RELEASE_THIS set) - * and 'parent::__construct()' (only HAS_THIS set, no RELEASE_THIS). */ - if ((call_info & ZEND_CALL_HAS_THIS) && (EX(func)->common.fn_flags & ZEND_ACC_CTOR)) { - zend_object *obj = Z_OBJ(execute_data->This); - zend_property_info *ctor_prop_info; - ZEND_HASH_MAP_FOREACH_PTR(&EX(func)->common.scope->properties_info, ctor_prop_info) { - if ((ctor_prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) - && IS_VALID_PROPERTY_OFFSET(ctor_prop_info->offset)) { - Z_PROP_FLAG_P(OBJ_PROP(obj, ctor_prop_info->offset)) &= ~IS_PROP_REINITABLE; - } - } ZEND_HASH_FOREACH_END(); - } + zend_ctor_clear_promoted_readonly_reinitable(execute_data, call_info); if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { @@ -53953,19 +53917,7 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend * as that may free the op_array. */ zend_vm_stack_free_extra_args_ex(call_info, execute_data); - /* When a constructor exits, clear IS_PROP_REINITABLE from all promoted readonly - * properties of the declaring class. Runs for both 'new Foo()' (RELEASE_THIS set) - * and 'parent::__construct()' (only HAS_THIS set, no RELEASE_THIS). */ - if ((call_info & ZEND_CALL_HAS_THIS) && (EX(func)->common.fn_flags & ZEND_ACC_CTOR)) { - zend_object *obj = Z_OBJ(execute_data->This); - zend_property_info *ctor_prop_info; - ZEND_HASH_MAP_FOREACH_PTR(&EX(func)->common.scope->properties_info, ctor_prop_info) { - if ((ctor_prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) - && IS_VALID_PROPERTY_OFFSET(ctor_prop_info->offset)) { - Z_PROP_FLAG_P(OBJ_PROP(obj, ctor_prop_info->offset)) &= ~IS_PROP_REINITABLE; - } - } ZEND_HASH_FOREACH_END(); - } + zend_ctor_clear_promoted_readonly_reinitable(execute_data, call_info); if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { @@ -110200,19 +110152,7 @@ ZEND_API void execute_ex(zend_execute_data *ex) #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif - /* When a constructor exits, clear IS_PROP_REINITABLE from all promoted readonly - * properties of the declaring class. Runs for both 'new Foo()' (RELEASE_THIS set) - * and 'parent::__construct()' (only HAS_THIS set, no RELEASE_THIS). */ - if ((call_info & ZEND_CALL_HAS_THIS) && (EX(func)->common.fn_flags & ZEND_ACC_CTOR)) { - zend_object *obj = Z_OBJ(execute_data->This); - zend_property_info *ctor_prop_info; - ZEND_HASH_MAP_FOREACH_PTR(&EX(func)->common.scope->properties_info, ctor_prop_info) { - if ((ctor_prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) - && IS_VALID_PROPERTY_OFFSET(ctor_prop_info->offset)) { - Z_PROP_FLAG_P(OBJ_PROP(obj, ctor_prop_info->offset)) &= ~IS_PROP_REINITABLE; - } - } ZEND_HASH_FOREACH_END(); - } + zend_ctor_clear_promoted_readonly_reinitable(execute_data, call_info); if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { @@ -110247,19 +110187,7 @@ ZEND_API void execute_ex(zend_execute_data *ex) * as that may free the op_array. */ zend_vm_stack_free_extra_args_ex(call_info, execute_data); - /* When a constructor exits, clear IS_PROP_REINITABLE from all promoted readonly - * properties of the declaring class. Runs for both 'new Foo()' (RELEASE_THIS set) - * and 'parent::__construct()' (only HAS_THIS set, no RELEASE_THIS). */ - if ((call_info & ZEND_CALL_HAS_THIS) && (EX(func)->common.fn_flags & ZEND_ACC_CTOR)) { - zend_object *obj = Z_OBJ(execute_data->This); - zend_property_info *ctor_prop_info; - ZEND_HASH_MAP_FOREACH_PTR(&EX(func)->common.scope->properties_info, ctor_prop_info) { - if ((ctor_prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) - && IS_VALID_PROPERTY_OFFSET(ctor_prop_info->offset)) { - Z_PROP_FLAG_P(OBJ_PROP(obj, ctor_prop_info->offset)) &= ~IS_PROP_REINITABLE; - } - } ZEND_HASH_FOREACH_END(); - } + zend_ctor_clear_promoted_readonly_reinitable(execute_data, call_info); if (UNEXPECTED(call_info & ZEND_CALL_RELEASE_THIS)) { OBJ_RELEASE(Z_OBJ(execute_data->This)); } else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) { diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 1a89ed8d7b830..4b35e4c97c605 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -2803,15 +2803,12 @@ static void ZEND_FASTCALL zend_jit_assign_obj_helper(zend_object *zobj, zend_str } } -static zend_always_inline bool verify_readonly_and_avis(zval *property_val, zend_property_info *info, bool indirect, zend_object *zobj) +static zend_always_inline bool verify_readonly_and_avis(zval *property_val, zend_property_info *info, bool indirect) { if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { - if (info->flags & ZEND_ACC_READONLY) { - ZEND_ASSERT(zobj != NULL); /* REINITABLE only applies to instance properties */ - if (!zend_is_readonly_property_modifiable(property_val)) { - zend_readonly_property_modification_error(info); - return false; - } + if ((info->flags & ZEND_ACC_READONLY) && !zend_is_readonly_property_modifiable(property_val)) { + zend_readonly_property_modification_error(info); + return false; } if ((info->flags & ZEND_ACC_PPP_SET_MASK) && !zend_asymmetric_property_has_set_access(info)) { const char *operation = indirect ? "indirectly modify" : "modify"; @@ -2822,7 +2819,7 @@ static zend_always_inline bool verify_readonly_and_avis(zval *property_val, zend return true; } -static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend_property_info *info, zval *value, zval *result, zend_object *zobj) +static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend_property_info *info, zval *value, zval *result) { zend_execute_data *execute_data = EG(current_execute_data); zend_refcounted *garbage = NULL; @@ -2835,7 +2832,7 @@ static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend value = &EG(uninitialized_zval); } - if (UNEXPECTED(!verify_readonly_and_avis(property_val, info, false, zobj))) { + if (UNEXPECTED(!verify_readonly_and_avis(property_val, info, false))) { if (result) { ZVAL_UNDEF(result); } @@ -2891,12 +2888,12 @@ static zend_never_inline void _zend_jit_assign_op_overloaded_property(zend_objec OBJ_RELEASE(object); } -static void ZEND_FASTCALL zend_jit_assign_op_to_typed_prop(zval *zptr, zend_property_info *prop_info, zval *value, binary_op_type binary_op, zend_object *zobj) +static void ZEND_FASTCALL zend_jit_assign_op_to_typed_prop(zval *zptr, zend_property_info *prop_info, zval *value, binary_op_type binary_op) { zend_execute_data *execute_data = EG(current_execute_data); zval z_copy; - if (UNEXPECTED(!verify_readonly_and_avis(zptr, prop_info, true, zobj))) { + if (UNEXPECTED(!verify_readonly_and_avis(zptr, prop_info, true))) { return; } @@ -2949,7 +2946,7 @@ static void ZEND_FASTCALL zend_jit_assign_obj_op_helper(zend_object *zobj, zend_ //??? } if (prop_info && ZEND_TYPE_IS_SET(prop_info->type)) { /* special case for typed properties */ - zend_jit_assign_op_to_typed_prop(zptr, prop_info, value, binary_op, zobj); + zend_jit_assign_op_to_typed_prop(zptr, prop_info, value, binary_op); } else { binary_op(zptr, zptr, value); } @@ -2986,11 +2983,11 @@ static ZEND_COLD zend_long _zend_jit_throw_dec_prop_error(zend_property_info *pr return ZEND_LONG_MIN; } -static void ZEND_FASTCALL zend_jit_inc_typed_prop(zval *var_ptr, zend_property_info *prop_info, zend_object *zobj) +static void ZEND_FASTCALL zend_jit_inc_typed_prop(zval *var_ptr, zend_property_info *prop_info) { ZEND_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF); - if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true, zobj))) { + if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true))) { return; } @@ -3018,11 +3015,11 @@ static void ZEND_FASTCALL zend_jit_inc_typed_prop(zval *var_ptr, zend_property_i } } -static void ZEND_FASTCALL zend_jit_dec_typed_prop(zval *var_ptr, zend_property_info *prop_info, zend_object *zobj) +static void ZEND_FASTCALL zend_jit_dec_typed_prop(zval *var_ptr, zend_property_info *prop_info) { ZEND_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF); - if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true, zobj))) { + if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true))) { return; } @@ -3050,25 +3047,25 @@ static void ZEND_FASTCALL zend_jit_dec_typed_prop(zval *var_ptr, zend_property_i } } -static void ZEND_FASTCALL zend_jit_pre_inc_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result, zend_object *zobj) +static void ZEND_FASTCALL zend_jit_pre_inc_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result) { ZVAL_DEREF(var_ptr); - zend_jit_inc_typed_prop(var_ptr, prop_info, zobj); + zend_jit_inc_typed_prop(var_ptr, prop_info); ZVAL_COPY(result, var_ptr); } -static void ZEND_FASTCALL zend_jit_pre_dec_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result, zend_object *zobj) +static void ZEND_FASTCALL zend_jit_pre_dec_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result) { ZVAL_DEREF(var_ptr); - zend_jit_dec_typed_prop(var_ptr, prop_info, zobj); + zend_jit_dec_typed_prop(var_ptr, prop_info); ZVAL_COPY(result, var_ptr); } -static void ZEND_FASTCALL zend_jit_post_inc_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result, zend_object *zobj) +static void ZEND_FASTCALL zend_jit_post_inc_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result) { ZEND_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF); - if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true, zobj))) { + if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true))) { if (result) { ZVAL_UNDEF(result); } @@ -3098,11 +3095,11 @@ static void ZEND_FASTCALL zend_jit_post_inc_typed_prop(zval *var_ptr, zend_prope } } -static void ZEND_FASTCALL zend_jit_post_dec_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result, zend_object *zobj) +static void ZEND_FASTCALL zend_jit_post_dec_typed_prop(zval *var_ptr, zend_property_info *prop_info, zval *result) { ZEND_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF); - if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true, zobj))) { + if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true))) { if (result) { ZVAL_UNDEF(result); } @@ -3166,7 +3163,7 @@ static void ZEND_FASTCALL zend_jit_pre_inc_obj_helper(zend_object *zobj, zend_st } if (prop_info) { - zend_jit_inc_typed_prop(prop, prop_info, zobj); + zend_jit_inc_typed_prop(prop, prop_info); } else { increment_function(prop); } @@ -3239,7 +3236,7 @@ static void ZEND_FASTCALL zend_jit_pre_dec_obj_helper(zend_object *zobj, zend_st } if (prop_info) { - zend_jit_dec_typed_prop(prop, prop_info, zobj); + zend_jit_dec_typed_prop(prop, prop_info); } else { decrement_function(prop); } @@ -3310,7 +3307,7 @@ static void ZEND_FASTCALL zend_jit_post_inc_obj_helper(zend_object *zobj, zend_s } if (prop_info) { - zend_jit_post_inc_typed_prop(prop, prop_info, result, zobj); + zend_jit_post_inc_typed_prop(prop, prop_info, result); } else { ZVAL_COPY(result, prop); increment_function(prop); @@ -3374,7 +3371,7 @@ static void ZEND_FASTCALL zend_jit_post_dec_obj_helper(zend_object *zobj, zend_s } if (prop_info) { - zend_jit_post_dec_typed_prop(prop, prop_info, result, zobj); + zend_jit_post_dec_typed_prop(prop, prop_info, result); } else { ZVAL_COPY(result, prop); decrement_function(prop); diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 067048a94aed9..ace1206682042 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -14917,12 +14917,11 @@ static int zend_jit_assign_obj(zend_jit_ctx *jit, } // JIT: value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); jit_SET_EX_OPLINE(jit, opline); - ir_CALL_5(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_to_typed_prop), + ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_to_typed_prop), prop_ref, prop_info_ref, arg3, - arg4, - obj_ref); + arg4); if ((opline+1)->op1_type == IS_CONST) { // TODO: ??? @@ -14990,12 +14989,11 @@ static int zend_jit_assign_obj(zend_jit_ctx *jit, } else { arg4 = jit_ZVAL_ADDR(jit, res_addr); } - ir_CALL_5(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_to_typed_prop), + ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_to_typed_prop), prop_ref, ref, arg3, - arg4, - obj_ref); + arg4); ir_END_list(end_inputs); } @@ -15333,12 +15331,11 @@ static int zend_jit_assign_obj_op(zend_jit_ctx *jit, ref = ir_LOAD_A(ir_ADD_OFFSET(ref, prop_info_offset)); } - ir_CALL_5(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_op_to_typed_prop), + ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_op_to_typed_prop), prop_ref, ref, arg2, - ir_CONST_FC_FUNC(binary_op), - obj_ref); + ir_CONST_FC_FUNC(binary_op)); ir_END_list(end_inputs); } @@ -15738,7 +15735,7 @@ static int zend_jit_incdec_obj(zend_jit_ctx *jit, ZEND_UNREACHABLE(); } - ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(func), prop_ref, ref, obj_ref); + ir_CALL_2(IR_VOID, ir_CONST_FC_FUNC(func), prop_ref, ref); } else { switch (opline->opcode) { case ZEND_PRE_INC_OBJ: @@ -15756,11 +15753,10 @@ static int zend_jit_incdec_obj(zend_jit_ctx *jit, default: ZEND_UNREACHABLE(); } - ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(func), + ir_CALL_3(IR_VOID, ir_CONST_FC_FUNC(func), prop_ref, ref, - jit_ZVAL_ADDR(jit, res_addr), - obj_ref); + jit_ZVAL_ADDR(jit, res_addr)); } ir_END_list(end_inputs); }