From 6f8fcf1024adfc7d2e6805a196c1823f07b2a673 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 21 Jan 2026 20:26:47 +0100 Subject: [PATCH 1/7] Allow promoted readonly property to be reassigned once in constructor --- .../cpp_reassign_after_parent.phpt | 43 +++++++ .../readonly_props/cpp_reassign_basic.phpt | 28 +++++ .../cpp_reassign_child_class.phpt | 89 +++++++++++++++ .../cpp_reassign_child_preempt_parent.phpt | 31 +++++ .../cpp_reassign_child_redefine.phpt | 84 ++++++++++++++ .../cpp_reassign_conditional.phpt | 23 ++++ .../cpp_reassign_different_object.phpt | 34 ++++++ .../cpp_reassign_direct_ctor_call.phpt | 29 +++++ .../cpp_reassign_grandchild_redefine.phpt | 39 +++++++ .../cpp_reassign_indirect_allowed.phpt | 47 ++++++++ .../cpp_reassign_indirect_ops.phpt | 42 +++++++ .../cpp_reassign_multi_instance.phpt | 46 ++++++++ .../cpp_reassign_multiple_fail.phpt | 25 ++++ .../cpp_reassign_nonpromoted.phpt | 49 ++++++++ .../cpp_reassign_outside_ctor.phpt | 43 +++++++ .../cpp_reassign_reflection.phpt | 41 +++++++ .../cpp_reassign_validation.phpt | 30 +++++ .../cpp_reassign_visibility.phpt | 106 +++++++++++++++++ Zend/zend_API.c | 5 +- Zend/zend_compile.h | 4 +- Zend/zend_execute.c | 53 ++++++++- Zend/zend_execute.h | 107 ++++++++++++++++++ Zend/zend_object_handlers.c | 42 ++++++- Zend/zend_object_handlers.h | 6 + Zend/zend_objects.c | 11 +- Zend/zend_types.h | 1 + Zend/zend_vm_def.h | 2 + Zend/zend_vm_execute.h | 6 + ext/opcache/jit/zend_jit_helpers.c | 24 ++-- ext/opcache/jit/zend_jit_vm_helpers.c | 3 + 30 files changed, 1071 insertions(+), 22 deletions(-) create mode 100644 Zend/tests/readonly_props/cpp_reassign_after_parent.phpt 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_child_preempt_parent.phpt create mode 100644 Zend/tests/readonly_props/cpp_reassign_child_redefine.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_direct_ctor_call.phpt create mode 100644 Zend/tests/readonly_props/cpp_reassign_grandchild_redefine.phpt create mode 100644 Zend/tests/readonly_props/cpp_reassign_indirect_allowed.phpt create mode 100644 Zend/tests/readonly_props/cpp_reassign_indirect_ops.phpt create mode 100644 Zend/tests/readonly_props/cpp_reassign_multi_instance.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_reflection.phpt create mode 100644 Zend/tests/readonly_props/cpp_reassign_validation.phpt create mode 100644 Zend/tests/readonly_props/cpp_reassign_visibility.phpt diff --git a/Zend/tests/readonly_props/cpp_reassign_after_parent.phpt b/Zend/tests/readonly_props/cpp_reassign_after_parent.phpt new file mode 100644 index 0000000000000..ad17d9b1af6be --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_after_parent.phpt @@ -0,0 +1,43 @@ +--TEST-- +Promoted readonly property reassignment - child can reassign after parent::__construct() +--FILE-- +getMessage(), "\n"; + } + // Child should still be able to reassign its own CPP property + $this->x = 'C_reassigned'; + } +} + +$c = new C(); +var_dump($c->x); + +// Also test with multiple instances +$c2 = new C(); +var_dump($c2->x); + +?> +--EXPECT-- +Error: Cannot modify readonly property C::$x +string(12) "C_reassigned" +Error: Cannot modify readonly property C::$x +string(12) "C_reassigned" 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..c1b68761bd911 --- /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, -3); +var_dump($point2->x, $point2->y); + +?> +--EXPECT-- +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 new file mode 100644 index 0000000000000..d054585861aa3 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_child_class.phpt @@ -0,0 +1,89 @@ +--TEST-- +Promoted readonly property reassignment in constructor - child cannot reassign parent's property +--FILE-- +prop = 'child override'; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; + } + } +} + +$child = new Child1(); +var_dump($child->prop); + +// Case 2: Parent USES reassignment, child cannot +class Parent2 { + public function __construct( + public readonly string $prop = 'parent default', + ) { + $this->prop = 'parent set'; // Uses the one reassignment + } +} + +class Child2 extends Parent2 { + public function __construct() { + parent::__construct(); + // Child cannot reassign parent-owned promoted property + try { + $this->prop = 'child override'; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; + } + } +} + +$child2 = new Child2(); +var_dump($child2->prop); + +// Case 3: Child with its own promoted property +class Parent3 { + public function __construct( + public readonly string $parentProp = 'parent default', + ) { + // Parent does NOT reassign here + } +} + +class Child3 extends Parent3 { + public function __construct( + public readonly string $childProp = 'child default', + ) { + parent::__construct(); + // Child cannot reassign parent's property, but can reassign its own + try { + $this->parentProp = 'child set parent'; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; + } + $this->childProp = 'child set own'; + } +} + +$child3 = new Child3(); +var_dump($child3->parentProp, $child3->childProp); + +?> +--EXPECT-- +Error: Cannot modify readonly property Parent1::$prop +string(14) "parent default" +Error: Cannot modify readonly property Parent2::$prop +string(10) "parent set" +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 new file mode 100644 index 0000000000000..db2bb81c9eb10 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_child_preempt_parent.phpt @@ -0,0 +1,31 @@ +--TEST-- +Promoted readonly property reassignment in constructor - child preempt then parent ctor throws +--FILE-- +prop = 'parent set'; + } +} + +class ChildCPP extends ParentCPP { + public function __construct() { + $this->prop = 'child set'; + try { + parent::__construct(); + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; + } + } +} + +$c = new ChildCPP(); +var_dump($c->prop); + +?> +--EXPECT-- +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..1cd82db84f528 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_child_redefine.phpt @@ -0,0 +1,84 @@ +--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. +// 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', + ) { + $this->x = 'P2'; + } +} + +class C2 extends P2 { + public readonly string $x; + + public function __construct() { + parent::__construct(); + } +} + +$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" +string(2) "P2" +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 new file mode 100644 index 0000000000000..e67177e76e3c2 --- /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 ??= '/tmp/app_cache'; + } +} + +$config1 = new Config(); +var_dump($config1->cacheDir); + +$config2 = new Config('/custom/cache'); +var_dump($config2->cacheDir); + +?> +--EXPECT-- +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 new file mode 100644 index 0000000000000..293826f2ee13a --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_different_object.phpt @@ -0,0 +1,34 @@ +--TEST-- +Promoted readonly property reassignment in constructor - different object fails +--FILE-- +x = $x * 2; + if ($other !== null) { + try { + $other->x = 999; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; + } + } + } +} + +$a = new Foo(5); +var_dump($a->x); + +$b = new Foo(3, $a); +var_dump($a->x, $b->x); // $a unchanged + +?> +--EXPECT-- +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 new file mode 100644 index 0000000000000..5ff246eaa46eb --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_direct_ctor_call.phpt @@ -0,0 +1,29 @@ +--TEST-- +Promoted readonly properties cannot be reassigned when __construct() is called directly +--FILE-- +value = strtoupper($this->value); + } +} + +$obj = new Foo('hello'); +var_dump($obj->value); + +// Direct call fails: CPP assignment cannot reinitialize an already-set property +try { + $obj->__construct('world'); +} catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; +} +var_dump($obj->value); + +?> +--EXPECT-- +string(5) "HELLO" +Error: Cannot modify readonly property Foo::$value +string(5) "HELLO" diff --git a/Zend/tests/readonly_props/cpp_reassign_grandchild_redefine.phpt b/Zend/tests/readonly_props/cpp_reassign_grandchild_redefine.phpt new file mode 100644 index 0000000000000..3b0ad38eae40b --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_grandchild_redefine.phpt @@ -0,0 +1,39 @@ +--TEST-- +Promoted readonly property reassignment - grandchild non-CPP redeclare preserves child CPP window +--FILE-- +getMessage(), "\n"; + } + $this->x = 'C2'; + } +} + +class D extends C { + public readonly string $x; + + public function __construct() { + parent::__construct(); + var_dump($this->x); + } +} + +new D(); + +?> +--EXPECT-- +Error: Cannot modify readonly property D::$x +string(2) "C2" \ No newline at end of file diff --git a/Zend/tests/readonly_props/cpp_reassign_indirect_allowed.phpt b/Zend/tests/readonly_props/cpp_reassign_indirect_allowed.phpt new file mode 100644 index 0000000000000..3437d54dffbc5 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_indirect_allowed.phpt @@ -0,0 +1,47 @@ +--TEST-- +Promoted readonly property reassignment in constructor - indirect reassignment allowed +--FILE-- +initProp(); + } + + private function initProp(): void { + $this->prop = 'from method'; + } +} + +$cm = new CalledMethod(); +var_dump($cm->prop); + +// But second reassignment still fails +class MultipleReassign { + public function __construct( + public readonly string $prop = 'default', + ) { + $this->initProp("first from method"); + try { + $this->initProp("second from method"); // Second call - should fail + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; + } + } + + private function initProp(string $v): void { + $this->prop = $v; + } +} + +$mr = new MultipleReassign(); +var_dump($mr->prop); + +?> +--EXPECT-- +string(11) "from method" +Error: Cannot modify readonly property MultipleReassign::$prop +string(17) "first from method" 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..e39c3e18d8885 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_indirect_ops.phpt @@ -0,0 +1,42 @@ +--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 (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; + } + } +} + +$m = new MultiOp(); +var_dump($m->value); + +?> +--EXPECT-- +int(6) +Error: Cannot modify readonly property MultiOp::$value +int(15) diff --git a/Zend/tests/readonly_props/cpp_reassign_multi_instance.phpt b/Zend/tests/readonly_props/cpp_reassign_multi_instance.phpt new file mode 100644 index 0000000000000..325b114e40195 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_multi_instance.phpt @@ -0,0 +1,46 @@ +--TEST-- +Promoted readonly property reassignment - multiple instantiations (VM cache warm) +--FILE-- +getMessage(), "\n"; + } + } +} + +// First instantiation (slow path) +$c1 = new C(); +var_dump($c1->x); + +// Second instantiation (fast path - cache warm) +$c2 = new C(); +var_dump($c2->x); + +// Third for good measure +$c3 = new C(); +var_dump($c3->x); + +?> +--EXPECT-- +Error: Cannot modify readonly property C::$x +string(1) "C" +Error: Cannot modify readonly property C::$x +string(1) "C" +Error: Cannot modify readonly property C::$x +string(1) "C" 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..fcbce0a372bae --- /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 (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; + } + } +} + +$ex = new Example(); +var_dump($ex->value); + +?> +--EXPECT-- +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 new file mode 100644 index 0000000000000..092c496c87936 --- /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 (Throwable $e) { + echo get_class($e), ": ", $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 (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; + } + } +} + +$m = new MixedProps(); +var_dump($m->promoted, $m->nonPromoted); + +?> +--EXPECT-- +Error: Cannot modify readonly property NonPromoted::$prop +string(5) "first" +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 new file mode 100644 index 0000000000000..f6d0308c49948 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_outside_ctor.phpt @@ -0,0 +1,43 @@ +--TEST-- +Promoted readonly property reassignment in constructor - outside constructor fails +--FILE-- +x = abs($x); + } + + public function tryModify(): void { + $this->x = 999; + } +} + +$point = new Point(-5); +var_dump($point->x); + +// Cannot reassign from outside constructor +try { + $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 (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; +} +var_dump($point->x); + +?> +--EXPECT-- +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 new file mode 100644 index 0000000000000..8f3033e0f13a5 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_reflection.phpt @@ -0,0 +1,41 @@ +--TEST-- +Promoted readonly property reassignment works when object created via reflection +--FILE-- +bar = strtoupper($bar); + } +} + +$ref = new ReflectionClass(Foo::class); +$obj = $ref->newInstanceWithoutConstructor(); + +// Property is uninitialized at this point +try { + var_dump($obj->bar); +} catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; +} + +// First constructor call: CPP reassignment allowed +$obj->__construct('explicit call'); +var_dump($obj->bar); + +// Second call fails: property no longer uninitialized +try { + $obj->__construct('second call'); +} 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(13) "EXPLICIT CALL" +Error: Cannot modify readonly property Foo::$bar +string(13) "EXPLICIT CALL" 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..f8f9609f5773d --- /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 (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +string(16) "test@example.com" +ValueError: Invalid email 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..340e14478407f --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_visibility.phpt @@ -0,0 +1,106 @@ +--TEST-- +Promoted readonly property reassignment in constructor - visibility rules apply +--FILE-- +init(); + } + + protected function init(): void { + } +} + +class Child1 extends Parent1 { + protected function init(): void { + try { + $this->prop = 'child override'; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; + } + } +} + +$child1 = new Child1(); +var_dump($child1->prop); + +// 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', + ) { + $this->init(); + } + + protected function init(): void { + } +} + +class Child2 extends Parent2 { + protected function init(): void { + $this->prop = 'child set'; + } +} + +$child2 = new Child2(); +var_dump($child2->prop); + +// Case 3: public(set) - child's init() CAN modify within CPP window +class Parent3 { + public function __construct( + public public(set) readonly string $prop = 'parent default', + ) { + $this->init(); + } + + protected function init(): void { + } +} + +class Child3 extends Parent3 { + 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 (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 { + protected function init(): void { + try { + $this->prop = 'child override'; + } catch (Throwable $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; + } + } +} + +$child4 = new Child4(); +var_dump($child4->prop); + +?> +--EXPECT-- +Error: Cannot modify private(set) property Parent1::$prop from scope Child1 +string(14) "parent default" +string(9) "child set" +string(9) "child set" +Error: Cannot modify readonly property Parent4::$prop +string(10) "parent set" diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 89333d89af9df..5bbc7c9e63527 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1744,7 +1744,7 @@ ZEND_API void object_properties_load(zend_object *object, const HashTable *prope zval *slot = OBJ_PROP(object, property_info->offset); if (UNEXPECTED((property_info->flags & ZEND_ACC_READONLY) && !Z_ISUNDEF_P(slot))) { if (Z_PROP_FLAG_P(slot) & IS_PROP_REINITABLE) { - Z_PROP_FLAG_P(slot) &= ~IS_PROP_REINITABLE; + Z_PROP_FLAG_P(slot) &= ~(IS_PROP_REINITABLE | IS_PROP_CTOR_REINITABLE); } else { zend_readonly_property_modification_error(property_info); return; @@ -4422,6 +4422,9 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z if (access_type & ZEND_ACC_READONLY) { ce->ce_flags |= ZEND_ACC_HAS_READONLY_PROPS; + if (access_type & ZEND_ACC_PROMOTED) { + ce->ce_flags |= ZEND_ACC_HAS_PROMOTED_READONLY_PROPS; + } } } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 5414467f3f874..986de6a597234 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -275,7 +275,7 @@ typedef struct _zend_oparray_context { #define ZEND_ACC_PROTECTED_SET (1 << 11) /* | | X | */ #define ZEND_ACC_PRIVATE_SET (1 << 12) /* | | X | */ /* | | | */ -/* Class Flags (unused: 31) | | | */ +/* Class Flags | | | */ /* =========== | | | */ /* | | | */ /* Special class types | | | */ @@ -338,6 +338,8 @@ typedef struct _zend_oparray_context { /* loaded from file cache to process memory | | | */ #define ZEND_ACC_FILE_CACHED (1 << 27) /* X | | | */ /* | | | */ +#define ZEND_ACC_HAS_PROMOTED_READONLY_PROPS (1u << 31) /* X | | | */ + /* Class cannot be serialized or unserialized | | | */ #define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */ /* | | | */ diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 37278c5cb9a23..5ec483a394880 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1073,7 +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) && !(Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE)) { + if ((info->flags & ZEND_ACC_READONLY) + && (!zend_readonly_property_is_reinitable_for_context(property_val, info) + || zend_is_foreign_cpp_overwrite(property_val, info))) { zend_readonly_property_modification_error(info); return &EG(uninitialized_zval); } @@ -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_CTOR_REINITABLE); return zend_assign_to_variable_ex(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES(), garbage_ptr); } @@ -5897,6 +5899,53 @@ 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; +static zend_always_inline bool zend_has_more_derived_promoted_override( + const zend_class_entry *runtime_ce, const zend_class_entry *ctor_scope, zend_string *property_name) +{ + for (const zend_class_entry *ce = runtime_ce; ce != NULL && ce != ctor_scope; ce = ce->parent) { + zend_property_info *prop_info = zend_hash_find_ptr(&ce->properties_info, property_name); + if (prop_info != NULL && (prop_info->flags & ZEND_ACC_PROMOTED)) { + return true; + } + } + return false; +} + +/* Outlined slow path: clear IS_PROP_REINITABLE from promoted readonly properties + * of the exiting constructor's scope. Skips properties that a child class has + * redefined with its own CPP (the child owns that reassignment window). */ +static zend_never_inline void zend_ctor_clear_promoted_readonly_reinitable_slow(zend_execute_data *ex) +{ + zend_object *obj = Z_OBJ(ex->This); + zend_class_entry *ctor_scope = ex->func->common.scope; + zend_property_info *prop_info; + + ZEND_HASH_MAP_FOREACH_PTR(&ctor_scope->properties_info, prop_info) { + if ((prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) + && IS_VALID_PROPERTY_OFFSET(prop_info->offset)) { + /* When the object is an instance of a descendant class, do not clear the + * window if a more-derived class owns the property through its own CPP, + * even if the runtime class itself redeclared it without CPP. */ + if (obj->ce != ctor_scope + && zend_has_more_derived_promoted_override(obj->ce, ctor_scope, prop_info->name)) { + continue; + } + Z_PROP_FLAG_P(OBJ_PROP(obj, prop_info->offset)) &= ~(IS_PROP_REINITABLE | IS_PROP_CTOR_REINITABLE); + } + } ZEND_HASH_FOREACH_END(); +} + +/* Clear IS_PROP_REINITABLE from all promoted readonly properties of the exiting + * constructor's scope. Called for both 'new Foo()' and 'parent::__construct()'. */ +ZEND_API void ZEND_FASTCALL 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_CLASS_HAS_PROMOTED_READONLY_PROPS(ex->func->common.scope)) { + zend_ctor_clear_promoted_readonly_reinitable_slow(ex); + } +} + #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_execute.h b/Zend/zend_execute.h index 89a9e79143a82..61e350e182d28 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -583,7 +583,113 @@ ZEND_API zend_result ZEND_FASTCALL zend_handle_undef_args(zend_execute_data *cal #define ZEND_CLASS_HAS_TYPE_HINTS(ce) ((bool)(ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS)) #define ZEND_CLASS_HAS_READONLY_PROPS(ce) ((bool)(ce->ce_flags & ZEND_ACC_HAS_READONLY_PROPS)) +#define ZEND_CLASS_HAS_PROMOTED_READONLY_PROPS(ce) ((bool)(ce->ce_flags & ZEND_ACC_HAS_PROMOTED_READONLY_PROPS)) +static zend_always_inline bool zend_scope_is_derived_from( + const zend_class_entry *scope, const zend_class_entry *ancestor) +{ + for (const zend_class_entry *ce = scope; ce != NULL; ce = ce->parent) { + if (ce == ancestor) { + return true; + } + } + return false; +} + +static zend_always_inline bool zend_has_active_derived_ctor_with_promoted_property( + const zend_execute_data *ex, zend_object *obj, const zend_property_info *prop_info) +{ + for (const zend_execute_data *prev = ex->prev_execute_data; prev != NULL; prev = prev->prev_execute_data) { + if (!(ZEND_CALL_INFO(prev) & ZEND_CALL_HAS_THIS) + || !(prev->func->common.fn_flags & ZEND_ACC_CTOR) + || Z_OBJ(prev->This) != obj) { + continue; + } + + zend_class_entry *scope = prev->func->common.scope; + if (scope == NULL || !zend_scope_is_derived_from(scope, ex->func->common.scope)) { + continue; + } + + zend_property_info *scope_prop = (zend_property_info *) zend_hash_find_ptr( + &scope->properties_info, prop_info->name); + if (scope_prop != NULL + && (scope_prop->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) + == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) { + return true; + } + } + + return false; +} + +static zend_always_inline bool zend_has_active_ctor_with_promoted_property( + const zend_execute_data *ex, zend_object *obj, zend_string *property_name) +{ + for (const zend_execute_data *frame = ex; frame != NULL; frame = frame->prev_execute_data) { + if (!(ZEND_CALL_INFO(frame) & ZEND_CALL_HAS_THIS) + || !(frame->func->common.fn_flags & ZEND_ACC_CTOR) + || Z_OBJ(frame->This) != obj) { + continue; + } + + zend_class_entry *scope = frame->func->common.scope; + if (scope == NULL) { + continue; + } + + zend_property_info *scope_prop = (zend_property_info *) zend_hash_find_ptr( + &scope->properties_info, property_name); + if (scope_prop != NULL + && (scope_prop->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) + == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) { + return true; + } + } + + return false; +} + +static zend_always_inline bool zend_readonly_property_is_reinitable_for_context( + const zval *property_val, const zend_property_info *prop_info) +{ + if (!(Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE)) { + return false; + } + if (!(Z_PROP_FLAG_P(property_val) & IS_PROP_CTOR_REINITABLE)) { + return true; + } + zend_execute_data *ex = EG(current_execute_data); + return ex + && (ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS) + && zend_has_active_ctor_with_promoted_property(ex, Z_OBJ(ex->This), prop_info->name); +} + +/* Check if a foreign constructor is attempting a CPP initial assignment on an + * already-initialized property owned by a different class (e.g., child has CPP + * for $x, parent's CPP also tries to set $x on the child's object). */ +static zend_always_inline bool zend_is_foreign_cpp_overwrite( + const zval *property_val, const zend_property_info *prop_info) +{ + if (Z_PROP_FLAG_P(property_val) & IS_PROP_UNINIT) { + return false; + } + zend_execute_data *ex = EG(current_execute_data); + if (!ex + || !(ex->func->common.fn_flags & ZEND_ACC_CTOR) + || !(ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS)) { + return false; + } + if ((prop_info->flags & ZEND_ACC_PROMOTED) && ex->func->common.scope != prop_info->ce) { + return true; + } + if (!(prop_info->flags & ZEND_ACC_PROMOTED) + && (Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE) + && zend_has_active_derived_ctor_with_promoted_property(ex, Z_OBJ(ex->This), prop_info)) { + return true; + } + return false; +} ZEND_API bool zend_verify_class_constant_type(const zend_class_constant *c, const zend_string *name, zval *constant); ZEND_COLD void zend_verify_class_constant_type_error(const zend_class_constant *c, const zend_string *name, const zval *constant); @@ -591,6 +697,7 @@ ZEND_COLD void zend_verify_class_constant_type_error(const zend_class_constant * ZEND_API bool zend_verify_property_type(const zend_property_info *info, zval *property, bool strict); ZEND_COLD void zend_verify_property_type_error(const zend_property_info *info, const zval *property); ZEND_COLD void zend_magic_get_property_type_inconsistency_error(const zend_property_info *info, const zval *property); +ZEND_API void ZEND_FASTCALL zend_ctor_clear_promoted_readonly_reinitable(zend_execute_data *ex, uint32_t call_info); #define ZEND_REF_ADD_TYPE_SOURCE(ref, source) \ zend_ref_add_type_source(&ZEND_REF_TYPE_SOURCES(ref), source) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 45eac02949d15..4f8c80fc33bab 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1068,7 +1068,8 @@ 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_readonly_property_is_reinitable_for_context(variable_ptr, prop_info) + || zend_is_foreign_cpp_overwrite(variable_ptr, prop_info))) { zend_readonly_property_modification_error(prop_info); variable_ptr = &EG(error_zval); goto exit; @@ -1102,7 +1103,42 @@ 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 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)) { + 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 | IS_PROP_CTOR_REINITABLE; + } else { + Z_PROP_FLAG_P(variable_ptr) &= ~(IS_PROP_UNINIT|IS_PROP_REINITABLE|IS_PROP_CTOR_REINITABLE); + } value = &tmp; } @@ -1520,7 +1556,7 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void if (error) { if ((prop_info->flags & ZEND_ACC_READONLY) && Z_TYPE_P(slot) != IS_UNDEF - && !(Z_PROP_FLAG_P(slot) & IS_PROP_REINITABLE)) { + && !zend_readonly_property_is_reinitable_for_context(slot, prop_info)) { zend_readonly_property_unset_error(prop_info->ce, name); return; } diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index 3e922343eb15a..b1cb7dcd1f214 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -277,6 +277,12 @@ 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); +/* Check if a readonly property has the REINITABLE flag (set by clone or CPP initialization) */ +static zend_always_inline bool zend_readonly_property_is_reinitable(const zval *property_val) +{ + return (Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE) != 0; +} + static zend_always_inline HashTable *zend_std_get_properties_ex(zend_object *object) { if (UNEXPECTED(zend_lazy_object_must_init(object))) { diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c index 6f6a826389442..a40beea4d8a13 100644 --- a/Zend/zend_objects.c +++ b/Zend/zend_objects.c @@ -208,7 +208,8 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, ZVAL_COPY_VALUE_PROP(dst, src); zval_add_ref(dst); if (has_clone_method) { - /* Unconditionally add the IS_PROP_REINITABLE flag to avoid a potential cache miss of property_info */ + /* Clone opens its own reinitability window; stale ctor-only state must not leak into it. */ + Z_PROP_FLAG_P(dst) &= ~IS_PROP_CTOR_REINITABLE; Z_PROP_FLAG_P(dst) |= IS_PROP_REINITABLE; } @@ -257,7 +258,8 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, zval_add_ref(&new_prop); } if (has_clone_method) { - /* Unconditionally add the IS_PROP_REINITABLE flag to avoid a potential cache miss of property_info */ + /* Clone opens its own reinitability window; stale ctor-only state must not leak into it. */ + Z_PROP_FLAG_P(&new_prop) &= ~IS_PROP_CTOR_REINITABLE; Z_PROP_FLAG_P(&new_prop) |= IS_PROP_REINITABLE; } if (EXPECTED(key)) { @@ -274,8 +276,8 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, if (ZEND_CLASS_HAS_READONLY_PROPS(new_object->ce)) { for (uint32_t i = 0; i < new_object->ce->default_properties_count; i++) { zval* prop = OBJ_PROP_NUM(new_object, i); - /* Unconditionally remove the IS_PROP_REINITABLE flag to avoid a potential cache miss of property_info */ - Z_PROP_FLAG_P(prop) &= ~IS_PROP_REINITABLE; + /* Close both clone-opened and any stale ctor-opened reinitability state. */ + Z_PROP_FLAG_P(prop) &= ~(IS_PROP_REINITABLE | IS_PROP_CTOR_REINITABLE); } } } @@ -290,6 +292,7 @@ ZEND_API zend_object *zend_objects_clone_obj_with(zend_object *old_object, const if (ZEND_CLASS_HAS_READONLY_PROPS(new_object->ce)) { for (uint32_t i = 0; i < new_object->ce->default_properties_count; i++) { zval* prop = OBJ_PROP_NUM(new_object, i); + Z_PROP_FLAG_P(prop) &= ~IS_PROP_CTOR_REINITABLE; Z_PROP_FLAG_P(prop) |= IS_PROP_REINITABLE; } } diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 22dbfa9be879b..a30fb0fbf3c15 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_CTOR_REINITABLE (1<<3) #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..fe3cddd09c692 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3000,6 +3000,7 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif + 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)) { @@ -3034,6 +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); + 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 cbfae90802cfa..abba8fe3f60de 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1160,6 +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 + 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)) { @@ -1194,6 +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); + 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)) { @@ -53880,6 +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 + 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)) { @@ -53914,6 +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); + 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)) { @@ -110148,6 +110152,7 @@ ZEND_API void execute_ex(zend_execute_data *ex) #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif + 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)) { @@ -110182,6 +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); + 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 59bb9401d9a98..1f83a82c62c24 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -2806,7 +2806,9 @@ 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) { 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)) { + if ((info->flags & ZEND_ACC_READONLY) + && (!zend_readonly_property_is_reinitable_for_context(property_val, info) + || zend_is_foreign_cpp_overwrite(property_val, info))) { zend_readonly_property_modification_error(info); return false; } @@ -2850,7 +2852,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_CTOR_REINITABLE); value = zend_assign_to_variable_ex(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); if (result) { @@ -2907,7 +2909,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_CTOR_REINITABLE); zval_ptr_dtor(zptr); ZVAL_COPY_VALUE(zptr, &z_copy); } else { @@ -3004,13 +3006,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; + Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE | IS_PROP_CTOR_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_CTOR_REINITABLE); zval_ptr_dtor(&tmp); } } @@ -3036,13 +3038,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; + Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE | IS_PROP_CTOR_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_CTOR_REINITABLE); zval_ptr_dtor(&tmp); } } @@ -3084,14 +3086,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; + Z_PROP_FLAG_P(var_ptr) &= ~(IS_PROP_REINITABLE | IS_PROP_CTOR_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_CTOR_REINITABLE); } } @@ -3118,14 +3120,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_CTOR_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_CTOR_REINITABLE); } } diff --git a/ext/opcache/jit/zend_jit_vm_helpers.c b/ext/opcache/jit/zend_jit_vm_helpers.c index bed5ab59992ed..6e769300793d8 100644 --- a/ext/opcache/jit/zend_jit_vm_helpers.c +++ b/ext/opcache/jit/zend_jit_vm_helpers.c @@ -58,6 +58,7 @@ ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend_jit_leave_func_helper_tai } zend_vm_stack_free_extra_args_ex(call_info, execute_data); + 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)) { @@ -109,6 +110,7 @@ ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_nested_func_helper(ZEND_OPC } zend_vm_stack_free_extra_args_ex(call_info, execute_data); + 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)) { @@ -151,6 +153,7 @@ ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_top_func_helper(ZEND_OPCODE } zend_vm_stack_free_extra_args_ex(call_info, execute_data); } + zend_ctor_clear_promoted_readonly_reinitable(execute_data, call_info); if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(EX(extra_named_params)); } From 19193f2208320dfdb4ec7318189409b192460ccd Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 12 Mar 2026 08:18:10 +0100 Subject: [PATCH 2/7] Handle promoted readonly reassignment in readonly failure paths --- Zend/Optimizer/compact_literals.c | 9 +- .../cpp_reassign_grandchild_redefine.phpt | 2 +- Zend/zend_API.c | 5 +- Zend/zend_compile.c | 3 + Zend/zend_compile.h | 5 +- Zend/zend_execute.c | 61 +--- Zend/zend_execute.h | 53 ++- Zend/zend_object_handlers.c | 52 +-- Zend/zend_object_handlers.h | 6 - Zend/zend_objects.c | 11 +- Zend/zend_types.h | 2 +- Zend/zend_vm_def.h | 8 +- Zend/zend_vm_execute.h | 330 +++++++++--------- ext/opcache/jit/zend_jit_helpers.c | 109 ++++-- ext/opcache/jit/zend_jit_ir.c | 24 +- ext/opcache/jit/zend_jit_vm_helpers.c | 3 - 16 files changed, 351 insertions(+), 332 deletions(-) diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c index 447a034530e1b..20c4c7d3cf321 100644 --- a/Zend/Optimizer/compact_literals.c +++ b/Zend/Optimizer/compact_literals.c @@ -514,15 +514,18 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx case ZEND_POST_INC_OBJ: case ZEND_POST_DEC_OBJ: if (opline->op2_type == IS_CONST) { + uint32_t obj_flags = (opline->opcode == ZEND_ASSIGN_OBJ || opline->opcode == ZEND_ASSIGN_OBJ_REF) + ? ZEND_ASSIGN_OBJ_FLAGS + : ZEND_FETCH_OBJ_FLAGS; // op2 property if (opline->op1_type == IS_UNUSED && property_slot[opline->op2.constant] >= 0) { - opline->extended_value = property_slot[opline->op2.constant] | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS); + opline->extended_value = property_slot[opline->op2.constant] | (opline->extended_value & obj_flags); } else { - opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS); + opline->extended_value = cache_size | (opline->extended_value & obj_flags); cache_size += 3 * sizeof(void *); if (opline->op1_type == IS_UNUSED) { - property_slot[opline->op2.constant] = opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS; + property_slot[opline->op2.constant] = opline->extended_value & ~obj_flags; } } } diff --git a/Zend/tests/readonly_props/cpp_reassign_grandchild_redefine.phpt b/Zend/tests/readonly_props/cpp_reassign_grandchild_redefine.phpt index 3b0ad38eae40b..b068846b7a034 100644 --- a/Zend/tests/readonly_props/cpp_reassign_grandchild_redefine.phpt +++ b/Zend/tests/readonly_props/cpp_reassign_grandchild_redefine.phpt @@ -36,4 +36,4 @@ new D(); ?> --EXPECT-- Error: Cannot modify readonly property D::$x -string(2) "C2" \ No newline at end of file +string(2) "C2" diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 5bbc7c9e63527..89333d89af9df 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1744,7 +1744,7 @@ ZEND_API void object_properties_load(zend_object *object, const HashTable *prope zval *slot = OBJ_PROP(object, property_info->offset); if (UNEXPECTED((property_info->flags & ZEND_ACC_READONLY) && !Z_ISUNDEF_P(slot))) { if (Z_PROP_FLAG_P(slot) & IS_PROP_REINITABLE) { - Z_PROP_FLAG_P(slot) &= ~(IS_PROP_REINITABLE | IS_PROP_CTOR_REINITABLE); + Z_PROP_FLAG_P(slot) &= ~IS_PROP_REINITABLE; } else { zend_readonly_property_modification_error(property_info); return; @@ -4422,9 +4422,6 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z if (access_type & ZEND_ACC_READONLY) { ce->ce_flags |= ZEND_ACC_HAS_READONLY_PROPS; - if (access_type & ZEND_ACC_PROMOTED) { - ce->ce_flags |= ZEND_ACC_HAS_PROMOTED_READONLY_PROPS; - } } } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 3147fda23e9fd..66b0254c4e94c 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8310,6 +8310,9 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 zend_op *opline = zend_emit_op(NULL, is_ref ? ZEND_ASSIGN_OBJ_REF : ZEND_ASSIGN_OBJ, NULL, &name_node); opline->extended_value = zend_alloc_cache_slots(3); + if ((flags & ZEND_ACC_READONLY) != 0) { + opline->extended_value |= ZEND_ASSIGN_OBJ_PROMOTED_READONLY_INIT; + } zend_emit_op_data(&value_node); } } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 986de6a597234..3b3dd5deb4c65 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -338,8 +338,6 @@ typedef struct _zend_oparray_context { /* loaded from file cache to process memory | | | */ #define ZEND_ACC_FILE_CACHED (1 << 27) /* X | | | */ /* | | | */ -#define ZEND_ACC_HAS_PROMOTED_READONLY_PROPS (1u << 31) /* X | | | */ - /* Class cannot be serialized or unserialized | | | */ #define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */ /* | | | */ @@ -1099,6 +1097,9 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); #define ZEND_FETCH_DIM_WRITE 2 #define ZEND_FETCH_OBJ_FLAGS 3 +#define ZEND_ASSIGN_OBJ_PROMOTED_READONLY_INIT (1u << 31) +#define ZEND_ASSIGN_OBJ_FLAGS (ZEND_FETCH_OBJ_FLAGS | ZEND_ASSIGN_OBJ_PROMOTED_READONLY_INIT) + /* Used to mark what kind of operation a writing FETCH_DIM is used in, * to produce a more precise error on incorrect string offset use. */ #define ZEND_FETCH_DIM_REF 1 diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 5ec483a394880..6cad1667cb16c 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1071,10 +1071,14 @@ ZEND_API bool zend_never_inline zend_verify_property_type(const zend_property_in 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) { zval tmp; + zend_readonly_write_kind readonly_write_kind = ZEND_READONLY_WRITE_FORBIDDEN; if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { + if (info->flags & ZEND_ACC_READONLY) { + readonly_write_kind = zend_get_readonly_write_kind(property_val, info); + } if ((info->flags & ZEND_ACC_READONLY) - && (!zend_readonly_property_is_reinitable_for_context(property_val, info) + && (readonly_write_kind == ZEND_READONLY_WRITE_FORBIDDEN || zend_is_foreign_cpp_overwrite(property_val, info))) { zend_readonly_property_modification_error(info); return &EG(uninitialized_zval); @@ -1093,7 +1097,11 @@ 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_CTOR_REINITABLE); + if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { + Z_PROP_FLAG_P(property_val) &= ~IS_PROP_REINITABLE; + } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { + Z_PROP_FLAG_P(property_val) |= IS_PROP_CTOR_REASSIGNED; + } return zend_assign_to_variable_ex(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES(), garbage_ptr); } @@ -3656,7 +3664,7 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c static zend_always_inline void zend_assign_to_property_reference(zval *container, uint32_t container_op_type, zval *prop_ptr, uint32_t prop_op_type, zval *value_ptr OPLINE_DC EXECUTE_DATA_DC) { zval variable, *variable_ptr = &variable; - void **cache_addr = (prop_op_type == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_RETURNS_FUNCTION) : NULL; + void **cache_addr = (prop_op_type == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~(ZEND_RETURNS_FUNCTION | ZEND_ASSIGN_OBJ_PROMOTED_READONLY_INIT)) : NULL; zend_refcounted *garbage = NULL; zend_property_info *prop_info = NULL; @@ -5899,53 +5907,6 @@ 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; -static zend_always_inline bool zend_has_more_derived_promoted_override( - const zend_class_entry *runtime_ce, const zend_class_entry *ctor_scope, zend_string *property_name) -{ - for (const zend_class_entry *ce = runtime_ce; ce != NULL && ce != ctor_scope; ce = ce->parent) { - zend_property_info *prop_info = zend_hash_find_ptr(&ce->properties_info, property_name); - if (prop_info != NULL && (prop_info->flags & ZEND_ACC_PROMOTED)) { - return true; - } - } - return false; -} - -/* Outlined slow path: clear IS_PROP_REINITABLE from promoted readonly properties - * of the exiting constructor's scope. Skips properties that a child class has - * redefined with its own CPP (the child owns that reassignment window). */ -static zend_never_inline void zend_ctor_clear_promoted_readonly_reinitable_slow(zend_execute_data *ex) -{ - zend_object *obj = Z_OBJ(ex->This); - zend_class_entry *ctor_scope = ex->func->common.scope; - zend_property_info *prop_info; - - ZEND_HASH_MAP_FOREACH_PTR(&ctor_scope->properties_info, prop_info) { - if ((prop_info->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED) - && IS_VALID_PROPERTY_OFFSET(prop_info->offset)) { - /* When the object is an instance of a descendant class, do not clear the - * window if a more-derived class owns the property through its own CPP, - * even if the runtime class itself redeclared it without CPP. */ - if (obj->ce != ctor_scope - && zend_has_more_derived_promoted_override(obj->ce, ctor_scope, prop_info->name)) { - continue; - } - Z_PROP_FLAG_P(OBJ_PROP(obj, prop_info->offset)) &= ~(IS_PROP_REINITABLE | IS_PROP_CTOR_REINITABLE); - } - } ZEND_HASH_FOREACH_END(); -} - -/* Clear IS_PROP_REINITABLE from all promoted readonly properties of the exiting - * constructor's scope. Called for both 'new Foo()' and 'parent::__construct()'. */ -ZEND_API void ZEND_FASTCALL 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_CLASS_HAS_PROMOTED_READONLY_PROPS(ex->func->common.scope)) { - zend_ctor_clear_promoted_readonly_reinitable_slow(ex); - } -} - #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_execute.h b/Zend/zend_execute.h index 61e350e182d28..5e3fb6c51f0fe 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -583,7 +583,6 @@ ZEND_API zend_result ZEND_FASTCALL zend_handle_undef_args(zend_execute_data *cal #define ZEND_CLASS_HAS_TYPE_HINTS(ce) ((bool)(ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS)) #define ZEND_CLASS_HAS_READONLY_PROPS(ce) ((bool)(ce->ce_flags & ZEND_ACC_HAS_READONLY_PROPS)) -#define ZEND_CLASS_HAS_PROMOTED_READONLY_PROPS(ce) ((bool)(ce->ce_flags & ZEND_ACC_HAS_PROMOTED_READONLY_PROPS)) static zend_always_inline bool zend_scope_is_derived_from( const zend_class_entry *scope, const zend_class_entry *ancestor) @@ -650,19 +649,53 @@ static zend_always_inline bool zend_has_active_ctor_with_promoted_property( return false; } -static zend_always_inline bool zend_readonly_property_is_reinitable_for_context( - const zval *property_val, const zend_property_info *prop_info) +static zend_always_inline bool zend_is_promoted_readonly_ctor_init_assign( + const zend_execute_data *ex, const zend_property_info *prop_info) { - if (!(Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE)) { + if (ex == NULL + || ex->opline == NULL + || !((ex->opline->opcode == ZEND_ASSIGN_OBJ || ex->opline->opcode == ZEND_ASSIGN_OBJ_REF) + && (ex->opline->extended_value & ZEND_ASSIGN_OBJ_PROMOTED_READONLY_INIT))) { return false; } - if (!(Z_PROP_FLAG_P(property_val) & IS_PROP_CTOR_REINITABLE)) { - return true; + if (!(ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS)) { + return false; + } + return zend_has_active_ctor_with_promoted_property(ex, Z_OBJ(ex->This), prop_info->name); +} + +static zend_always_inline zend_object *zend_property_owner_from_slot( + const zval *property_val, const zend_property_info *prop_info) +{ + return (zend_object *) ((char *) property_val - prop_info->offset); +} + +typedef enum _zend_readonly_write_kind { + ZEND_READONLY_WRITE_FORBIDDEN = 0, /* Write disallowed, or no special flag update needed */ + ZEND_READONLY_WRITE_REINITABLE, /* Clone reinit window: clear IS_PROP_REINITABLE after write */ + ZEND_READONLY_WRITE_CTOR_REASSIGNED, /* CPP ctor reassignment: set IS_PROP_CTOR_REASSIGNED after write */ +} zend_readonly_write_kind; + +static zend_always_inline zend_readonly_write_kind zend_get_readonly_write_kind( + const zval *property_val, const zend_property_info *prop_info) +{ + if (Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE) { + return ZEND_READONLY_WRITE_REINITABLE; + } + if (Z_PROP_FLAG_P(property_val) & IS_PROP_CTOR_REASSIGNED) { + return ZEND_READONLY_WRITE_FORBIDDEN; } zend_execute_data *ex = EG(current_execute_data); - return ex - && (ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS) - && zend_has_active_ctor_with_promoted_property(ex, Z_OBJ(ex->This), prop_info->name); + if (ex == NULL + || !(ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS) + || !zend_has_active_ctor_with_promoted_property(ex, Z_OBJ(ex->This), prop_info->name) + || zend_property_owner_from_slot(property_val, prop_info) != Z_OBJ(ex->This) + || (ex->opline != NULL + && (ex->opline->opcode == ZEND_ASSIGN_OBJ || ex->opline->opcode == ZEND_ASSIGN_OBJ_REF) + && (ex->opline->extended_value & ZEND_ASSIGN_OBJ_PROMOTED_READONLY_INIT))) { + return ZEND_READONLY_WRITE_FORBIDDEN; + } + return ZEND_READONLY_WRITE_CTOR_REASSIGNED; } /* Check if a foreign constructor is attempting a CPP initial assignment on an @@ -684,7 +717,6 @@ static zend_always_inline bool zend_is_foreign_cpp_overwrite( return true; } if (!(prop_info->flags & ZEND_ACC_PROMOTED) - && (Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE) && zend_has_active_derived_ctor_with_promoted_property(ex, Z_OBJ(ex->This), prop_info)) { return true; } @@ -697,7 +729,6 @@ ZEND_COLD void zend_verify_class_constant_type_error(const zend_class_constant * ZEND_API bool zend_verify_property_type(const zend_property_info *info, zval *property, bool strict); ZEND_COLD void zend_verify_property_type_error(const zend_property_info *info, const zval *property); ZEND_COLD void zend_magic_get_property_type_inconsistency_error(const zend_property_info *info, const zval *property); -ZEND_API void ZEND_FASTCALL zend_ctor_clear_promoted_readonly_reinitable(zend_execute_data *ex, uint32_t call_info); #define ZEND_REF_ADD_TYPE_SOURCE(ref, source) \ zend_ref_add_type_source(&ZEND_REF_TYPE_SOURCES(ref), source) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 4f8c80fc33bab..c70e8cfff5c94 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1049,12 +1049,14 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva uintptr_t property_offset; const zend_property_info *prop_info = NULL; uint32_t *guard = NULL; + zend_readonly_write_kind readonly_write_kind = ZEND_READONLY_WRITE_FORBIDDEN; ZEND_ASSERT(!Z_ISREF_P(value)); property_offset = zend_get_property_offset(zobj->ce, name, (zobj->ce->__set != NULL), cache_slot, &prop_info); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { try_again: + readonly_write_kind = ZEND_READONLY_WRITE_FORBIDDEN; variable_ptr = OBJ_PROP(zobj, property_offset); if (prop_info && UNEXPECTED(prop_info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { @@ -1066,9 +1068,12 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva error = (*guard) & IN_SET; } if (error) { + if ((prop_info->flags & ZEND_ACC_READONLY) && Z_TYPE_P(variable_ptr) != IS_UNDEF) { + readonly_write_kind = zend_get_readonly_write_kind(variable_ptr, prop_info); + } if ((prop_info->flags & ZEND_ACC_READONLY) && Z_TYPE_P(variable_ptr) != IS_UNDEF - && (!zend_readonly_property_is_reinitable_for_context(variable_ptr, prop_info) + && (readonly_write_kind == ZEND_READONLY_WRITE_FORBIDDEN || zend_is_foreign_cpp_overwrite(variable_ptr, prop_info))) { zend_readonly_property_modification_error(prop_info); variable_ptr = &EG(error_zval); @@ -1103,42 +1108,7 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva variable_ptr = &EG(error_zval); goto exit; } - /* 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)) { - 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 | IS_PROP_CTOR_REINITABLE; - } else { - Z_PROP_FLAG_P(variable_ptr) &= ~(IS_PROP_UNINIT|IS_PROP_REINITABLE|IS_PROP_CTOR_REINITABLE); - } + Z_PROP_FLAG_P(variable_ptr) &= ~(IS_PROP_UNINIT|IS_PROP_REINITABLE); value = &tmp; } @@ -1148,6 +1118,12 @@ found:; variable_ptr = zend_assign_to_variable_ex( variable_ptr, value, IS_TMP_VAR, property_uses_strict_types(), &garbage); + if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { + Z_PROP_FLAG_P(variable_ptr) &= ~IS_PROP_REINITABLE; + } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { + Z_PROP_FLAG_P(variable_ptr) |= IS_PROP_CTOR_REASSIGNED; + } + if (garbage) { if (GC_DELREF(garbage) == 0) { zend_execute_data *execute_data = EG(current_execute_data); @@ -1556,7 +1532,7 @@ ZEND_API void zend_std_unset_property(zend_object *zobj, zend_string *name, void if (error) { if ((prop_info->flags & ZEND_ACC_READONLY) && Z_TYPE_P(slot) != IS_UNDEF - && !zend_readonly_property_is_reinitable_for_context(slot, prop_info)) { + && !(Z_PROP_FLAG_P(slot) & IS_PROP_REINITABLE)) { zend_readonly_property_unset_error(prop_info->ce, name); return; } diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index b1cb7dcd1f214..3e922343eb15a 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -277,12 +277,6 @@ 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); -/* Check if a readonly property has the REINITABLE flag (set by clone or CPP initialization) */ -static zend_always_inline bool zend_readonly_property_is_reinitable(const zval *property_val) -{ - return (Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE) != 0; -} - static zend_always_inline HashTable *zend_std_get_properties_ex(zend_object *object) { if (UNEXPECTED(zend_lazy_object_must_init(object))) { diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c index a40beea4d8a13..6f6a826389442 100644 --- a/Zend/zend_objects.c +++ b/Zend/zend_objects.c @@ -208,8 +208,7 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, ZVAL_COPY_VALUE_PROP(dst, src); zval_add_ref(dst); if (has_clone_method) { - /* Clone opens its own reinitability window; stale ctor-only state must not leak into it. */ - Z_PROP_FLAG_P(dst) &= ~IS_PROP_CTOR_REINITABLE; + /* Unconditionally add the IS_PROP_REINITABLE flag to avoid a potential cache miss of property_info */ Z_PROP_FLAG_P(dst) |= IS_PROP_REINITABLE; } @@ -258,8 +257,7 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, zval_add_ref(&new_prop); } if (has_clone_method) { - /* Clone opens its own reinitability window; stale ctor-only state must not leak into it. */ - Z_PROP_FLAG_P(&new_prop) &= ~IS_PROP_CTOR_REINITABLE; + /* Unconditionally add the IS_PROP_REINITABLE flag to avoid a potential cache miss of property_info */ Z_PROP_FLAG_P(&new_prop) |= IS_PROP_REINITABLE; } if (EXPECTED(key)) { @@ -276,8 +274,8 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object, if (ZEND_CLASS_HAS_READONLY_PROPS(new_object->ce)) { for (uint32_t i = 0; i < new_object->ce->default_properties_count; i++) { zval* prop = OBJ_PROP_NUM(new_object, i); - /* Close both clone-opened and any stale ctor-opened reinitability state. */ - Z_PROP_FLAG_P(prop) &= ~(IS_PROP_REINITABLE | IS_PROP_CTOR_REINITABLE); + /* Unconditionally remove the IS_PROP_REINITABLE flag to avoid a potential cache miss of property_info */ + Z_PROP_FLAG_P(prop) &= ~IS_PROP_REINITABLE; } } } @@ -292,7 +290,6 @@ ZEND_API zend_object *zend_objects_clone_obj_with(zend_object *old_object, const if (ZEND_CLASS_HAS_READONLY_PROPS(new_object->ce)) { for (uint32_t i = 0; i < new_object->ce->default_properties_count; i++) { zval* prop = OBJ_PROP_NUM(new_object, i); - Z_PROP_FLAG_P(prop) &= ~IS_PROP_CTOR_REINITABLE; Z_PROP_FLAG_P(prop) |= IS_PROP_REINITABLE; } } diff --git a/Zend/zend_types.h b/Zend/zend_types.h index a30fb0fbf3c15..9a00ea86d91fe 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -1594,7 +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_CTOR_REINITABLE (1<<3) +#define IS_PROP_CTOR_REASSIGNED (1<<3) /* Extra ctor reassignment already consumed */ #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 fe3cddd09c692..832aad6c81947 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2512,8 +2512,8 @@ ZEND_VM_HANDLER(24, ZEND_ASSIGN_OBJ, VAR|UNUSED|THIS|CV, CONST|TMP|CV, CACHE_SLO ZEND_VM_C_LABEL(assign_object): zobj = Z_OBJ_P(object); if (OP2_TYPE == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -2619,7 +2619,7 @@ ZEND_VM_C_LABEL(fast_assign_obj): ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (OP2_TYPE == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (OP2_TYPE == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (OP2_TYPE != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -3000,7 +3000,6 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif - 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)) { @@ -3035,7 +3034,6 @@ 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); - 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 abba8fe3f60de..d9caa1efa8976 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1160,7 +1160,6 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif - 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)) { @@ -1195,7 +1194,6 @@ 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); - 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)) { @@ -24405,8 +24403,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -24513,7 +24511,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -24562,8 +24560,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -24669,7 +24667,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -24717,8 +24715,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -24825,7 +24823,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -27113,8 +27111,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -27221,7 +27219,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -27269,8 +27267,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -27376,7 +27374,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -27423,8 +27421,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -27531,7 +27529,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -30937,8 +30935,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -31045,7 +31043,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -31094,8 +31092,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -31201,7 +31199,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -31249,8 +31247,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -31357,7 +31355,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -33471,8 +33469,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -33579,7 +33577,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -33629,8 +33627,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -33736,7 +33734,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -33785,8 +33783,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -33893,7 +33891,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -35569,8 +35567,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -35677,7 +35675,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -35726,8 +35724,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -35833,7 +35831,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -35881,8 +35879,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -35989,7 +35987,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -38138,8 +38136,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -38246,7 +38244,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -38296,8 +38294,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -38403,7 +38401,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -38452,8 +38450,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -38560,7 +38558,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -42507,8 +42505,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -42615,7 +42613,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -42665,8 +42663,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -42772,7 +42770,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -42821,8 +42819,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -42929,7 +42927,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -46330,8 +46328,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -46438,7 +46436,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -46487,8 +46485,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -46594,7 +46592,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -46642,8 +46640,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -46750,7 +46748,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -51444,8 +51442,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -51552,7 +51550,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -51602,8 +51600,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -51709,7 +51707,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -51758,8 +51756,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -51866,7 +51864,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_ASSIGN_OBJ_SP ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -53882,7 +53880,6 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif - 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)) { @@ -53917,7 +53914,6 @@ 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); - 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)) { @@ -76809,8 +76805,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -76917,7 +76913,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -76966,8 +76962,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -77073,7 +77069,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -77121,8 +77117,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -77229,7 +77225,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -79517,8 +79513,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -79625,7 +79621,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -79673,8 +79669,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -79780,7 +79776,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -79827,8 +79823,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -79935,7 +79931,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -83341,8 +83337,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -83449,7 +83445,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -83498,8 +83494,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -83605,7 +83601,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -83653,8 +83649,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -83761,7 +83757,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_VA ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -85875,8 +85871,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -85983,7 +85979,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -86033,8 +86029,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -86140,7 +86136,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -86189,8 +86185,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -86297,7 +86293,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -87973,8 +87969,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -88081,7 +88077,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -88130,8 +88126,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -88237,7 +88233,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -88285,8 +88281,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -88393,7 +88389,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -90542,8 +90538,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -90650,7 +90646,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -90700,8 +90696,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -90807,7 +90803,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -90856,8 +90852,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -90964,7 +90960,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_UN ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -94911,8 +94907,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -95019,7 +95015,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -95069,8 +95065,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -95176,7 +95172,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -95225,8 +95221,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_object: zobj = Z_OBJ_P(object); if (IS_CONST == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -95333,7 +95329,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CONST != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -98734,8 +98730,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -98842,7 +98838,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -98891,8 +98887,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -98998,7 +98994,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -99046,8 +99042,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_object: zobj = Z_OBJ_P(object); if (IS_TMP_VAR == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -99154,7 +99150,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_TMP_VAR == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_TMP_VAR != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -103746,8 +103742,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -103854,7 +103850,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -103904,8 +103900,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -104011,7 +104007,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -104060,8 +104056,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV assign_object: zobj = Z_OBJ_P(object); if (IS_CV == IS_CONST) { - if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value))) { - void **cache_slot = CACHE_ADDR(opline->extended_value); + if (EXPECTED(zobj->ce == CACHED_PTR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS))) { + void **cache_slot = CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS); uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1); zval *property_val; zend_property_info *prop_info; @@ -104168,7 +104164,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_ASSIGN_OBJ_SPEC_CV ZVAL_DEREF(value); } - value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL); + value = zobj->handlers->write_property(zobj, name, value, (IS_CV == IS_CONST) ? CACHE_ADDR(opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) : NULL); if (IS_CV != IS_CONST) { zend_tmp_string_release(tmp_name); @@ -110152,7 +110148,6 @@ ZEND_API void execute_ex(zend_execute_data *ex) #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); #endif - 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)) { @@ -110187,7 +110182,6 @@ 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); - 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 1f83a82c62c24..636188ce131e0 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -2803,22 +2803,31 @@ 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 zend_readonly_write_kind verify_readonly_and_avis( + zval *property_val, zend_property_info *info, bool indirect, bool *ok) { + *ok = true; if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { + zend_readonly_write_kind readonly_write_kind = ZEND_READONLY_WRITE_FORBIDDEN; + if (info->flags & ZEND_ACC_READONLY) { + readonly_write_kind = zend_get_readonly_write_kind(property_val, info); + } if ((info->flags & ZEND_ACC_READONLY) - && (!zend_readonly_property_is_reinitable_for_context(property_val, info) + && (readonly_write_kind == ZEND_READONLY_WRITE_FORBIDDEN || zend_is_foreign_cpp_overwrite(property_val, info))) { zend_readonly_property_modification_error(info); - return false; + *ok = false; + return ZEND_READONLY_WRITE_FORBIDDEN; } if ((info->flags & ZEND_ACC_PPP_SET_MASK) && !zend_asymmetric_property_has_set_access(info)) { const char *operation = indirect ? "indirectly modify" : "modify"; zend_asymmetric_visibility_property_modification_error(info, operation); - return false; + *ok = false; + return ZEND_READONLY_WRITE_FORBIDDEN; } + return readonly_write_kind; } - return true; + return ZEND_READONLY_WRITE_FORBIDDEN; } static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend_property_info *info, zval *value, zval *result) @@ -2826,6 +2835,8 @@ static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend zend_execute_data *execute_data = EG(current_execute_data); zend_refcounted *garbage = NULL; zval tmp; + bool ok; + zend_readonly_write_kind readonly_write_kind; if (UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { const zend_op *op_data = execute_data->opline + 1; @@ -2834,7 +2845,8 @@ 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))) { + readonly_write_kind = verify_readonly_and_avis(property_val, info, false, &ok); + if (UNEXPECTED(!ok)) { if (result) { ZVAL_UNDEF(result); } @@ -2852,7 +2864,11 @@ 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_CTOR_REINITABLE); + if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { + Z_PROP_FLAG_P(property_val) &= ~IS_PROP_REINITABLE; + } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { + Z_PROP_FLAG_P(property_val) |= IS_PROP_CTOR_REASSIGNED; + } value = zend_assign_to_variable_ex(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); if (result) { @@ -2894,8 +2910,11 @@ static void ZEND_FASTCALL zend_jit_assign_op_to_typed_prop(zval *zptr, zend_prop { zend_execute_data *execute_data = EG(current_execute_data); zval z_copy; + bool ok; + zend_readonly_write_kind readonly_write_kind; - if (UNEXPECTED(!verify_readonly_and_avis(zptr, prop_info, true))) { + readonly_write_kind = verify_readonly_and_avis(zptr, prop_info, true, &ok); + if (UNEXPECTED(!ok)) { return; } @@ -2909,7 +2928,11 @@ 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_CTOR_REINITABLE); + if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { + Z_PROP_FLAG_P(zptr) &= ~IS_PROP_REINITABLE; + } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { + Z_PROP_FLAG_P(zptr) |= IS_PROP_CTOR_REASSIGNED; + } zval_ptr_dtor(zptr); ZVAL_COPY_VALUE(zptr, &z_copy); } else { @@ -2988,8 +3011,11 @@ static ZEND_COLD zend_long _zend_jit_throw_dec_prop_error(zend_property_info *pr 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); + bool ok; + zend_readonly_write_kind readonly_write_kind; - if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true))) { + readonly_write_kind = verify_readonly_and_avis(var_ptr, prop_info, true, &ok); + if (UNEXPECTED(!ok)) { return; } @@ -3006,13 +3032,21 @@ 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_CTOR_REINITABLE); + if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { + Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; + } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { + Z_PROP_FLAG_P(var_ptr) |= IS_PROP_CTOR_REASSIGNED; + } } } 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_CTOR_REINITABLE); + if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { + Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; + } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { + Z_PROP_FLAG_P(var_ptr) |= IS_PROP_CTOR_REASSIGNED; + } zval_ptr_dtor(&tmp); } } @@ -3020,8 +3054,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_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF); + bool ok; + zend_readonly_write_kind readonly_write_kind; - if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true))) { + readonly_write_kind = verify_readonly_and_avis(var_ptr, prop_info, true, &ok); + if (UNEXPECTED(!ok)) { return; } @@ -3038,13 +3075,21 @@ 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_CTOR_REINITABLE); + if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { + Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; + } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { + Z_PROP_FLAG_P(var_ptr) |= IS_PROP_CTOR_REASSIGNED; + } } } 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_CTOR_REINITABLE); + if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { + Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; + } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { + Z_PROP_FLAG_P(var_ptr) |= IS_PROP_CTOR_REASSIGNED; + } zval_ptr_dtor(&tmp); } } @@ -3066,8 +3111,11 @@ static void ZEND_FASTCALL zend_jit_pre_dec_typed_prop(zval *var_ptr, zend_proper 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); + bool ok; + zend_readonly_write_kind readonly_write_kind; - if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true))) { + readonly_write_kind = verify_readonly_and_avis(var_ptr, prop_info, true, &ok); + if (UNEXPECTED(!ok)) { if (result) { ZVAL_UNDEF(result); } @@ -3086,22 +3134,33 @@ 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_CTOR_REINITABLE); + if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { + Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; + } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { + Z_PROP_FLAG_P(var_ptr) |= IS_PROP_CTOR_REASSIGNED; + } } } 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_CTOR_REINITABLE); + if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { + Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; + } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { + Z_PROP_FLAG_P(var_ptr) |= IS_PROP_CTOR_REASSIGNED; + } } } 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); + bool ok; + zend_readonly_write_kind readonly_write_kind; - if (UNEXPECTED(!verify_readonly_and_avis(var_ptr, prop_info, true))) { + readonly_write_kind = verify_readonly_and_avis(var_ptr, prop_info, true, &ok); + if (UNEXPECTED(!ok)) { if (result) { ZVAL_UNDEF(result); } @@ -3120,14 +3179,22 @@ 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_CTOR_REINITABLE); + if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { + Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; + } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { + Z_PROP_FLAG_P(var_ptr) |= IS_PROP_CTOR_REASSIGNED; + } } } 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_CTOR_REINITABLE); + if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { + Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; + } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { + Z_PROP_FLAG_P(var_ptr) |= IS_PROP_CTOR_REASSIGNED; + } } } diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 19b3ce125733a..ca957dce92520 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -14874,7 +14874,7 @@ static int zend_jit_assign_obj(zend_jit_ctx *jit, if (!prop_info) { ir_ref run_time_cache = ir_LOAD_A(jit_EX(run_time_cache)); - ir_ref ref = ir_LOAD_A(ir_ADD_OFFSET(run_time_cache, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS)); + ir_ref ref = ir_LOAD_A(ir_ADD_OFFSET(run_time_cache, opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS)); ir_ref if_same = ir_IF(ir_EQ(ref, ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_object, ce))))); ir_IF_FALSE_cold(if_same); @@ -14882,7 +14882,7 @@ static int zend_jit_assign_obj(zend_jit_ctx *jit, ir_IF_TRUE(if_same); ir_ref offset_ref = ir_LOAD_A( - ir_ADD_OFFSET(run_time_cache, (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*))); + ir_ADD_OFFSET(run_time_cache, (opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) + sizeof(void*))); ir_ref if_dynamic = ir_IF(ir_LT(offset_ref, ir_CONST_ADDR(ZEND_FIRST_PROPERTY_OFFSET))); ir_IF_TRUE_cold(if_dynamic); @@ -14900,7 +14900,7 @@ static int zend_jit_assign_obj(zend_jit_ctx *jit, if (!ce || ce_is_instanceof || (ce->ce_flags & (ZEND_ACC_HAS_TYPE_HINTS|ZEND_ACC_TRAIT))) { ir_ref arg3, arg4; ir_ref prop_info_ref = ir_LOAD_A( - ir_ADD_OFFSET(run_time_cache, (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*) * 2)); + ir_ADD_OFFSET(run_time_cache, (opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) + sizeof(void*) * 2)); ir_ref if_has_prop_info = ir_IF(prop_info_ref); ir_IF_TRUE_cold(if_has_prop_info); @@ -15068,7 +15068,7 @@ static int zend_jit_assign_obj(zend_jit_ctx *jit, obj_ref, ir_CONST_ADDR(name), arg3, - ir_ADD_OFFSET(run_time_cache, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS), + ir_ADD_OFFSET(run_time_cache, opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS), arg5); ir_END_list(end_inputs); @@ -15232,7 +15232,7 @@ static int zend_jit_assign_obj_op(zend_jit_ctx *jit, if (!prop_info) { ir_ref run_time_cache = ir_LOAD_A(jit_EX(run_time_cache)); - ir_ref ref = ir_LOAD_A(ir_ADD_OFFSET(run_time_cache, (opline+1)->extended_value & ~ZEND_FETCH_OBJ_FLAGS)); + ir_ref ref = ir_LOAD_A(ir_ADD_OFFSET(run_time_cache, (opline+1)->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS)); ir_ref if_same = ir_IF(ir_EQ(ref, ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_object, ce))))); ir_IF_FALSE_cold(if_same); @@ -15241,7 +15241,7 @@ static int zend_jit_assign_obj_op(zend_jit_ctx *jit, ir_IF_TRUE(if_same); if (!ce || ce_is_instanceof || (ce->ce_flags & (ZEND_ACC_HAS_TYPE_HINTS|ZEND_ACC_TRAIT))) { ir_ref prop_info_ref = ir_LOAD_A( - ir_ADD_OFFSET(run_time_cache, ((opline+1)->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*) * 2)); + ir_ADD_OFFSET(run_time_cache, ((opline+1)->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) + sizeof(void*) * 2)); ir_ref if_has_prop_info = ir_IF(prop_info_ref); ir_IF_TRUE_cold(if_has_prop_info); ir_END_list(slow_inputs); @@ -15249,7 +15249,7 @@ static int zend_jit_assign_obj_op(zend_jit_ctx *jit, ir_IF_FALSE(if_has_prop_info); } ir_ref offset_ref = ir_LOAD_A( - ir_ADD_OFFSET(run_time_cache, ((opline+1)->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*))); + ir_ADD_OFFSET(run_time_cache, ((opline+1)->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) + sizeof(void*))); ir_ref if_dynamic = ir_IF(ir_LT(offset_ref, ir_CONST_ADDR(ZEND_FIRST_PROPERTY_OFFSET))); ir_IF_TRUE_cold(if_dynamic); @@ -15508,7 +15508,7 @@ static int zend_jit_assign_obj_op(zend_jit_ctx *jit, obj_ref, ir_CONST_ADDR(name), arg3, - ir_ADD_OFFSET(run_time_cache, (opline+1)->extended_value & ~ZEND_FETCH_OBJ_FLAGS), + ir_ADD_OFFSET(run_time_cache, (opline+1)->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS), ir_CONST_FC_FUNC(binary_op)); ir_END_list(end_inputs); @@ -15655,7 +15655,7 @@ static int zend_jit_incdec_obj(zend_jit_ctx *jit, if (!prop_info) { ir_ref run_time_cache = ir_LOAD_A(jit_EX(run_time_cache)); - ir_ref ref = ir_LOAD_A(ir_ADD_OFFSET(run_time_cache, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS)); + ir_ref ref = ir_LOAD_A(ir_ADD_OFFSET(run_time_cache, opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS)); ir_ref if_same = ir_IF(ir_EQ(ref, ir_LOAD_A(ir_ADD_OFFSET(obj_ref, offsetof(zend_object, ce))))); ir_IF_FALSE_cold(if_same); @@ -15664,7 +15664,7 @@ static int zend_jit_incdec_obj(zend_jit_ctx *jit, ir_IF_TRUE(if_same); if (!ce || ce_is_instanceof || (ce->ce_flags & (ZEND_ACC_HAS_TYPE_HINTS|ZEND_ACC_TRAIT))) { ir_ref prop_info_ref = ir_LOAD_A( - ir_ADD_OFFSET(run_time_cache, (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*) * 2)); + ir_ADD_OFFSET(run_time_cache, (opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) + sizeof(void*) * 2)); ir_ref if_has_prop_info = ir_IF(prop_info_ref); ir_IF_TRUE_cold(if_has_prop_info); ir_END_list(slow_inputs); @@ -15672,7 +15672,7 @@ static int zend_jit_incdec_obj(zend_jit_ctx *jit, ir_IF_FALSE(if_has_prop_info); } ir_ref offset_ref = ir_LOAD_A( - ir_ADD_OFFSET(run_time_cache, (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*))); + ir_ADD_OFFSET(run_time_cache, (opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS) + sizeof(void*))); ir_ref if_dynamic = ir_IF(ir_LT(offset_ref, ir_CONST_ADDR(ZEND_FIRST_PROPERTY_OFFSET))); ir_IF_TRUE_cold(if_dynamic); @@ -15993,7 +15993,7 @@ static int zend_jit_incdec_obj(zend_jit_ctx *jit, ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(func), obj_ref, ir_CONST_ADDR(name), - ir_ADD_OFFSET(run_time_cache, opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS), + ir_ADD_OFFSET(run_time_cache, opline->extended_value & ~ZEND_ASSIGN_OBJ_FLAGS), (opline->result_type == IS_UNUSED) ? IR_NULL : jit_ZVAL_ADDR(jit, res_addr)); ir_END_list(end_inputs); diff --git a/ext/opcache/jit/zend_jit_vm_helpers.c b/ext/opcache/jit/zend_jit_vm_helpers.c index 6e769300793d8..bed5ab59992ed 100644 --- a/ext/opcache/jit/zend_jit_vm_helpers.c +++ b/ext/opcache/jit/zend_jit_vm_helpers.c @@ -58,7 +58,6 @@ ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend_jit_leave_func_helper_tai } zend_vm_stack_free_extra_args_ex(call_info, execute_data); - 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)) { @@ -110,7 +109,6 @@ ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_nested_func_helper(ZEND_OPC } zend_vm_stack_free_extra_args_ex(call_info, execute_data); - 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)) { @@ -153,7 +151,6 @@ ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_top_func_helper(ZEND_OPCODE } zend_vm_stack_free_extra_args_ex(call_info, execute_data); } - zend_ctor_clear_promoted_readonly_reinitable(execute_data, call_info); if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { zend_free_extra_named_params(EX(extra_named_params)); } From 153e05703da76d2267b01bb59c19d473d812a038 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 12 Mar 2026 15:20:51 +0100 Subject: [PATCH 3/7] Cleanup --- .../cpp_reassign_through_function.phpt | 30 ++++ Zend/zend_execute.c | 25 +-- Zend/zend_execute.h | 151 ++++++------------ Zend/zend_object_handlers.c | 26 +-- Zend/zend_objects_API.h | 5 + ext/opcache/jit/zend_jit_helpers.c | 123 +++----------- 6 files changed, 119 insertions(+), 241 deletions(-) create mode 100644 Zend/tests/readonly_props/cpp_reassign_through_function.phpt diff --git a/Zend/tests/readonly_props/cpp_reassign_through_function.phpt b/Zend/tests/readonly_props/cpp_reassign_through_function.phpt new file mode 100644 index 0000000000000..994ea71220059 --- /dev/null +++ b/Zend/tests/readonly_props/cpp_reassign_through_function.phpt @@ -0,0 +1,30 @@ +--TEST-- +Promoted readonly property reassignment through plain function should error +--FILE-- +prop++; + } +} + +function set($c) { + try { + $c->set(); + } catch (Error $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; + } +} + +$c = new C(42); +var_dump($c->prop); + +?> +--EXPECT-- +Error: Cannot modify readonly property C::$prop +int(42) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 6cad1667cb16c..8393585d09162 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1071,22 +1071,9 @@ ZEND_API bool zend_never_inline zend_verify_property_type(const zend_property_in 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) { zval tmp; - zend_readonly_write_kind readonly_write_kind = ZEND_READONLY_WRITE_FORBIDDEN; - - if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { - if (info->flags & ZEND_ACC_READONLY) { - readonly_write_kind = zend_get_readonly_write_kind(property_val, info); - } - if ((info->flags & ZEND_ACC_READONLY) - && (readonly_write_kind == ZEND_READONLY_WRITE_FORBIDDEN - || zend_is_foreign_cpp_overwrite(property_val, info))) { - 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"); - return &EG(uninitialized_zval); - } + zend_property_write_kind prop_write_kind = zend_verify_readonly_and_avis(property_val, info, false); + if (prop_write_kind == ZEND_PROPERTY_WRITE_FORBIDDEN) { + return &EG(uninitialized_zval); } ZVAL_DEREF(value); @@ -1097,11 +1084,7 @@ static zend_never_inline zval* zend_assign_to_typed_prop(const zend_property_inf return &EG(uninitialized_zval); } - if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { - Z_PROP_FLAG_P(property_val) &= ~IS_PROP_REINITABLE; - } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { - Z_PROP_FLAG_P(property_val) |= IS_PROP_CTOR_REASSIGNED; - } + zend_property_write_commit(property_val, prop_write_kind); return zend_assign_to_variable_ex(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES(), garbage_ptr); } diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 5e3fb6c51f0fe..b4418577676f4 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -595,132 +595,87 @@ static zend_always_inline bool zend_scope_is_derived_from( return false; } -static zend_always_inline bool zend_has_active_derived_ctor_with_promoted_property( - const zend_execute_data *ex, zend_object *obj, const zend_property_info *prop_info) -{ - for (const zend_execute_data *prev = ex->prev_execute_data; prev != NULL; prev = prev->prev_execute_data) { - if (!(ZEND_CALL_INFO(prev) & ZEND_CALL_HAS_THIS) - || !(prev->func->common.fn_flags & ZEND_ACC_CTOR) - || Z_OBJ(prev->This) != obj) { - continue; - } - - zend_class_entry *scope = prev->func->common.scope; - if (scope == NULL || !zend_scope_is_derived_from(scope, ex->func->common.scope)) { - continue; - } - - zend_property_info *scope_prop = (zend_property_info *) zend_hash_find_ptr( - &scope->properties_info, prop_info->name); - if (scope_prop != NULL - && (scope_prop->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) - == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) { - return true; - } - } - - return false; -} - static zend_always_inline bool zend_has_active_ctor_with_promoted_property( const zend_execute_data *ex, zend_object *obj, zend_string *property_name) { for (const zend_execute_data *frame = ex; frame != NULL; frame = frame->prev_execute_data) { - if (!(ZEND_CALL_INFO(frame) & ZEND_CALL_HAS_THIS) - || !(frame->func->common.fn_flags & ZEND_ACC_CTOR) - || Z_OBJ(frame->This) != obj) { + if (!(ZEND_CALL_INFO(frame) & ZEND_CALL_HAS_THIS) || Z_OBJ(frame->This) != obj) { + return false; + } + if (!(frame->func->common.fn_flags & ZEND_ACC_CTOR)) { continue; } zend_class_entry *scope = frame->func->common.scope; - if (scope == NULL) { - continue; - } + ZEND_ASSERT(scope); - zend_property_info *scope_prop = (zend_property_info *) zend_hash_find_ptr( - &scope->properties_info, property_name); - if (scope_prop != NULL - && (scope_prop->flags & (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) - == (ZEND_ACC_READONLY | ZEND_ACC_PROMOTED)) { - return true; - } + zend_property_info *scope_prop = (zend_property_info *) zend_hash_find_ptr(&scope->properties_info, property_name); + ZEND_ASSERT(scope_prop); + return scope_prop->ce == scope && (scope_prop->flags & (ZEND_ACC_READONLY|ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY|ZEND_ACC_PROMOTED); } return false; } -static zend_always_inline bool zend_is_promoted_readonly_ctor_init_assign( - const zend_execute_data *ex, const zend_property_info *prop_info) -{ - if (ex == NULL - || ex->opline == NULL - || !((ex->opline->opcode == ZEND_ASSIGN_OBJ || ex->opline->opcode == ZEND_ASSIGN_OBJ_REF) - && (ex->opline->extended_value & ZEND_ASSIGN_OBJ_PROMOTED_READONLY_INIT))) { - return false; - } - if (!(ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS)) { - return false; - } - return zend_has_active_ctor_with_promoted_property(ex, Z_OBJ(ex->This), prop_info->name); -} - -static zend_always_inline zend_object *zend_property_owner_from_slot( - const zval *property_val, const zend_property_info *prop_info) -{ - return (zend_object *) ((char *) property_val - prop_info->offset); -} - -typedef enum _zend_readonly_write_kind { - ZEND_READONLY_WRITE_FORBIDDEN = 0, /* Write disallowed, or no special flag update needed */ - ZEND_READONLY_WRITE_REINITABLE, /* Clone reinit window: clear IS_PROP_REINITABLE after write */ - ZEND_READONLY_WRITE_CTOR_REASSIGNED, /* CPP ctor reassignment: set IS_PROP_CTOR_REASSIGNED after write */ -} zend_readonly_write_kind; +typedef enum _zend_property_write_kind { + ZEND_PROPERTY_WRITE_FORBIDDEN = 0, /* Write disallowed */ + ZEND_PROPERTY_WRITE_OK, /* Write allowed */ + ZEND_PROPERTY_WRITE_READONLY_REINITABLE, /* Clone reinit window: clear IS_PROP_REINITABLE after write */ + ZEND_PROPERTY_WRITE_READONLY_CTOR_REASSIGNED, /* CPP ctor reassignment: set IS_PROP_CTOR_REASSIGNED after write */ +} zend_property_write_kind; -static zend_always_inline zend_readonly_write_kind zend_get_readonly_write_kind( +static zend_always_inline zend_property_write_kind zend_get_readonly_write_kind( const zval *property_val, const zend_property_info *prop_info) { if (Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE) { - return ZEND_READONLY_WRITE_REINITABLE; + return ZEND_PROPERTY_WRITE_READONLY_REINITABLE; } if (Z_PROP_FLAG_P(property_val) & IS_PROP_CTOR_REASSIGNED) { - return ZEND_READONLY_WRITE_FORBIDDEN; + return ZEND_PROPERTY_WRITE_FORBIDDEN; } zend_execute_data *ex = EG(current_execute_data); - if (ex == NULL - || !(ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS) - || !zend_has_active_ctor_with_promoted_property(ex, Z_OBJ(ex->This), prop_info->name) - || zend_property_owner_from_slot(property_val, prop_info) != Z_OBJ(ex->This) - || (ex->opline != NULL - && (ex->opline->opcode == ZEND_ASSIGN_OBJ || ex->opline->opcode == ZEND_ASSIGN_OBJ_REF) - && (ex->opline->extended_value & ZEND_ASSIGN_OBJ_PROMOTED_READONLY_INIT))) { - return ZEND_READONLY_WRITE_FORBIDDEN; + if (ex == NULL || !(ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS)) { + return ZEND_PROPERTY_WRITE_FORBIDDEN; } - return ZEND_READONLY_WRITE_CTOR_REASSIGNED; + zend_object *obj = zend_get_object_from_slot(property_val, prop_info); + if (!zend_has_active_ctor_with_promoted_property(ex, obj, prop_info->name) + || (ex->opline + && (ex->opline->opcode == ZEND_ASSIGN_OBJ || ex->opline->opcode == ZEND_ASSIGN_OBJ_REF) + && (ex->opline->extended_value & ZEND_ASSIGN_OBJ_PROMOTED_READONLY_INIT))) { + return ZEND_PROPERTY_WRITE_FORBIDDEN; + } + return ZEND_PROPERTY_WRITE_READONLY_CTOR_REASSIGNED; } -/* Check if a foreign constructor is attempting a CPP initial assignment on an - * already-initialized property owned by a different class (e.g., child has CPP - * for $x, parent's CPP also tries to set $x on the child's object). */ -static zend_always_inline bool zend_is_foreign_cpp_overwrite( - const zval *property_val, const zend_property_info *prop_info) +static zend_always_inline zend_property_write_kind zend_verify_readonly_and_avis( + zval *property_val, const zend_property_info *info, bool indirect) { - if (Z_PROP_FLAG_P(property_val) & IS_PROP_UNINIT) { - return false; - } - zend_execute_data *ex = EG(current_execute_data); - if (!ex - || !(ex->func->common.fn_flags & ZEND_ACC_CTOR) - || !(ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS)) { - return false; - } - if ((prop_info->flags & ZEND_ACC_PROMOTED) && ex->func->common.scope != prop_info->ce) { - return true; + if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { + zend_property_write_kind prop_write_kind = ZEND_PROPERTY_WRITE_OK; + if ((info->flags & ZEND_ACC_READONLY) && !Z_ISUNDEF_P(property_val)) { + prop_write_kind = zend_get_readonly_write_kind(property_val, info); + if (prop_write_kind == ZEND_PROPERTY_WRITE_FORBIDDEN) { + zend_readonly_property_modification_error(info); + return ZEND_PROPERTY_WRITE_FORBIDDEN; + } + } + if ((info->flags & ZEND_ACC_PPP_SET_MASK) && !zend_asymmetric_property_has_set_access(info)) { + const char *operation = indirect ? "indirectly modify" : "modify"; + zend_asymmetric_visibility_property_modification_error(info, operation); + return ZEND_PROPERTY_WRITE_FORBIDDEN; + } + return prop_write_kind; } - if (!(prop_info->flags & ZEND_ACC_PROMOTED) - && zend_has_active_derived_ctor_with_promoted_property(ex, Z_OBJ(ex->This), prop_info)) { - return true; + return ZEND_PROPERTY_WRITE_OK; +} + +static zend_always_inline void zend_property_write_commit(zval *property_val, zend_property_write_kind kind) +{ + if (kind == ZEND_PROPERTY_WRITE_READONLY_REINITABLE) { + Z_PROP_FLAG_P(property_val) &= ~IS_PROP_REINITABLE; + } else if (kind == ZEND_PROPERTY_WRITE_READONLY_CTOR_REASSIGNED) { + Z_PROP_FLAG_P(property_val) |= IS_PROP_CTOR_REASSIGNED; } - return false; } ZEND_API bool zend_verify_class_constant_type(const zend_class_constant *c, const zend_string *name, zval *constant); diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index c70e8cfff5c94..b6e7df41814a2 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1049,14 +1049,13 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva uintptr_t property_offset; const zend_property_info *prop_info = NULL; uint32_t *guard = NULL; - zend_readonly_write_kind readonly_write_kind = ZEND_READONLY_WRITE_FORBIDDEN; ZEND_ASSERT(!Z_ISREF_P(value)); property_offset = zend_get_property_offset(zobj->ce, name, (zobj->ce->__set != NULL), cache_slot, &prop_info); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { -try_again: - readonly_write_kind = ZEND_READONLY_WRITE_FORBIDDEN; +try_again:; + zend_property_write_kind prop_write_kind = ZEND_PROPERTY_WRITE_OK; variable_ptr = OBJ_PROP(zobj, property_offset); if (prop_info && UNEXPECTED(prop_info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { @@ -1068,19 +1067,8 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva error = (*guard) & IN_SET; } if (error) { - if ((prop_info->flags & ZEND_ACC_READONLY) && Z_TYPE_P(variable_ptr) != IS_UNDEF) { - readonly_write_kind = zend_get_readonly_write_kind(variable_ptr, prop_info); - } - if ((prop_info->flags & ZEND_ACC_READONLY) - && Z_TYPE_P(variable_ptr) != IS_UNDEF - && (readonly_write_kind == ZEND_READONLY_WRITE_FORBIDDEN - || zend_is_foreign_cpp_overwrite(variable_ptr, prop_info))) { - zend_readonly_property_modification_error(prop_info); - variable_ptr = &EG(error_zval); - goto exit; - } - if ((prop_info->flags & ZEND_ACC_PPP_SET_MASK) && !zend_asymmetric_property_has_set_access(prop_info)) { - zend_asymmetric_visibility_property_modification_error(prop_info, "modify"); + prop_write_kind = zend_verify_readonly_and_avis(variable_ptr, prop_info, false); + if (prop_write_kind == ZEND_PROPERTY_WRITE_FORBIDDEN) { variable_ptr = &EG(error_zval); goto exit; } @@ -1118,11 +1106,7 @@ found:; variable_ptr = zend_assign_to_variable_ex( variable_ptr, value, IS_TMP_VAR, property_uses_strict_types(), &garbage); - if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { - Z_PROP_FLAG_P(variable_ptr) &= ~IS_PROP_REINITABLE; - } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { - Z_PROP_FLAG_P(variable_ptr) |= IS_PROP_CTOR_REASSIGNED; - } + zend_property_write_commit(variable_ptr, prop_write_kind); if (garbage) { if (GC_DELREF(garbage) == 0) { diff --git a/Zend/zend_objects_API.h b/Zend/zend_objects_API.h index 86c3a49f8c8c5..793caf945efcf 100644 --- a/Zend/zend_objects_API.h +++ b/Zend/zend_objects_API.h @@ -137,6 +137,11 @@ static inline zend_property_info *zend_get_typed_property_info_for_slot(zend_obj return NULL; } +static zend_always_inline zend_object *zend_get_object_from_slot(const zval *slot, const zend_property_info *prop_info) +{ + return (zend_object *)((char *)slot - prop_info->offset); +} + static zend_always_inline bool zend_check_method_accessible(const zend_function *fn, const zend_class_entry *scope) { if (!(fn->common.fn_flags & ZEND_ACC_PUBLIC) diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 636188ce131e0..4c2d77c7f3f22 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -2803,40 +2803,11 @@ static void ZEND_FASTCALL zend_jit_assign_obj_helper(zend_object *zobj, zend_str } } -static zend_always_inline zend_readonly_write_kind verify_readonly_and_avis( - zval *property_val, zend_property_info *info, bool indirect, bool *ok) -{ - *ok = true; - if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { - zend_readonly_write_kind readonly_write_kind = ZEND_READONLY_WRITE_FORBIDDEN; - if (info->flags & ZEND_ACC_READONLY) { - readonly_write_kind = zend_get_readonly_write_kind(property_val, info); - } - if ((info->flags & ZEND_ACC_READONLY) - && (readonly_write_kind == ZEND_READONLY_WRITE_FORBIDDEN - || zend_is_foreign_cpp_overwrite(property_val, info))) { - zend_readonly_property_modification_error(info); - *ok = false; - return ZEND_READONLY_WRITE_FORBIDDEN; - } - if ((info->flags & ZEND_ACC_PPP_SET_MASK) && !zend_asymmetric_property_has_set_access(info)) { - const char *operation = indirect ? "indirectly modify" : "modify"; - zend_asymmetric_visibility_property_modification_error(info, operation); - *ok = false; - return ZEND_READONLY_WRITE_FORBIDDEN; - } - return readonly_write_kind; - } - return ZEND_READONLY_WRITE_FORBIDDEN; -} - 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; zval tmp; - bool ok; - zend_readonly_write_kind readonly_write_kind; if (UNEXPECTED(Z_TYPE_P(value) == IS_UNDEF)) { const zend_op *op_data = execute_data->opline + 1; @@ -2845,8 +2816,8 @@ static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend value = &EG(uninitialized_zval); } - readonly_write_kind = verify_readonly_and_avis(property_val, info, false, &ok); - if (UNEXPECTED(!ok)) { + zend_property_write_kind prop_write_kind = zend_verify_readonly_and_avis(property_val, info, false); + if (UNEXPECTED(prop_write_kind == ZEND_PROPERTY_WRITE_FORBIDDEN)) { if (result) { ZVAL_UNDEF(result); } @@ -2864,11 +2835,7 @@ static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend return; } - if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { - Z_PROP_FLAG_P(property_val) &= ~IS_PROP_REINITABLE; - } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { - Z_PROP_FLAG_P(property_val) |= IS_PROP_CTOR_REASSIGNED; - } + zend_property_write_commit(property_val, prop_write_kind); value = zend_assign_to_variable_ex(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); if (result) { @@ -2910,11 +2877,9 @@ static void ZEND_FASTCALL zend_jit_assign_op_to_typed_prop(zval *zptr, zend_prop { zend_execute_data *execute_data = EG(current_execute_data); zval z_copy; - bool ok; - zend_readonly_write_kind readonly_write_kind; - readonly_write_kind = verify_readonly_and_avis(zptr, prop_info, true, &ok); - if (UNEXPECTED(!ok)) { + zend_property_write_kind prop_write_kind = zend_verify_readonly_and_avis(zptr, prop_info, true); + if (UNEXPECTED(prop_write_kind == ZEND_PROPERTY_WRITE_FORBIDDEN)) { return; } @@ -2928,11 +2893,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()))) { - if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { - Z_PROP_FLAG_P(zptr) &= ~IS_PROP_REINITABLE; - } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { - Z_PROP_FLAG_P(zptr) |= IS_PROP_CTOR_REASSIGNED; - } + zend_property_write_commit(zptr, prop_write_kind); zval_ptr_dtor(zptr); ZVAL_COPY_VALUE(zptr, &z_copy); } else { @@ -3011,11 +2972,9 @@ static ZEND_COLD zend_long _zend_jit_throw_dec_prop_error(zend_property_info *pr 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); - bool ok; - zend_readonly_write_kind readonly_write_kind; - readonly_write_kind = verify_readonly_and_avis(var_ptr, prop_info, true, &ok); - if (UNEXPECTED(!ok)) { + zend_property_write_kind prop_write_kind = zend_verify_readonly_and_avis(var_ptr, prop_info, true); + if (UNEXPECTED(prop_write_kind == ZEND_PROPERTY_WRITE_FORBIDDEN)) { return; } @@ -3032,21 +2991,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 { - if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { - Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; - } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { - Z_PROP_FLAG_P(var_ptr) |= IS_PROP_CTOR_REASSIGNED; - } + zend_property_write_commit(var_ptr, prop_write_kind); } } 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 { - if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { - Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; - } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { - Z_PROP_FLAG_P(var_ptr) |= IS_PROP_CTOR_REASSIGNED; - } + zend_property_write_commit(var_ptr, prop_write_kind); zval_ptr_dtor(&tmp); } } @@ -3054,11 +3005,9 @@ 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_ASSERT(Z_TYPE_P(var_ptr) != IS_UNDEF); - bool ok; - zend_readonly_write_kind readonly_write_kind; - readonly_write_kind = verify_readonly_and_avis(var_ptr, prop_info, true, &ok); - if (UNEXPECTED(!ok)) { + zend_property_write_kind prop_write_kind = zend_verify_readonly_and_avis(var_ptr, prop_info, true); + if (UNEXPECTED(prop_write_kind == ZEND_PROPERTY_WRITE_FORBIDDEN)) { return; } @@ -3075,21 +3024,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 { - if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { - Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; - } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { - Z_PROP_FLAG_P(var_ptr) |= IS_PROP_CTOR_REASSIGNED; - } + zend_property_write_commit(var_ptr, prop_write_kind); } } 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 { - if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { - Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; - } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { - Z_PROP_FLAG_P(var_ptr) |= IS_PROP_CTOR_REASSIGNED; - } + zend_property_write_commit(var_ptr, prop_write_kind); zval_ptr_dtor(&tmp); } } @@ -3111,11 +3052,9 @@ static void ZEND_FASTCALL zend_jit_pre_dec_typed_prop(zval *var_ptr, zend_proper 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); - bool ok; - zend_readonly_write_kind readonly_write_kind; - readonly_write_kind = verify_readonly_and_avis(var_ptr, prop_info, true, &ok); - if (UNEXPECTED(!ok)) { + zend_property_write_kind prop_write_kind = zend_verify_readonly_and_avis(var_ptr, prop_info, true); + if (UNEXPECTED(prop_write_kind == ZEND_PROPERTY_WRITE_FORBIDDEN)) { if (result) { ZVAL_UNDEF(result); } @@ -3134,33 +3073,23 @@ 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 { - if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { - Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; - } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { - Z_PROP_FLAG_P(var_ptr) |= IS_PROP_CTOR_REASSIGNED; - } + zend_property_write_commit(var_ptr, prop_write_kind); } } 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 { - if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { - Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; - } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { - Z_PROP_FLAG_P(var_ptr) |= IS_PROP_CTOR_REASSIGNED; - } + zend_property_write_commit(var_ptr, prop_write_kind); } } 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); - bool ok; - zend_readonly_write_kind readonly_write_kind; - readonly_write_kind = verify_readonly_and_avis(var_ptr, prop_info, true, &ok); - if (UNEXPECTED(!ok)) { + zend_property_write_kind prop_write_kind = zend_verify_readonly_and_avis(var_ptr, prop_info, true); + if (UNEXPECTED(prop_write_kind == ZEND_PROPERTY_WRITE_FORBIDDEN)) { if (result) { ZVAL_UNDEF(result); } @@ -3179,22 +3108,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 { - if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { - Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; - } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { - Z_PROP_FLAG_P(var_ptr) |= IS_PROP_CTOR_REASSIGNED; - } + zend_property_write_commit(var_ptr, prop_write_kind); } } 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 { - if (readonly_write_kind == ZEND_READONLY_WRITE_REINITABLE) { - Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_REINITABLE; - } else if (readonly_write_kind == ZEND_READONLY_WRITE_CTOR_REASSIGNED) { - Z_PROP_FLAG_P(var_ptr) |= IS_PROP_CTOR_REASSIGNED; - } + zend_property_write_commit(var_ptr, prop_write_kind); } } From 2594fa77d8d109d6826a74bd41584ed34fa71cb2 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 12 Mar 2026 16:37:43 +0100 Subject: [PATCH 4/7] Remove another unused function --- Zend/zend_execute.h | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index b4418577676f4..602dcb6e69fc0 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -584,17 +584,6 @@ ZEND_API zend_result ZEND_FASTCALL zend_handle_undef_args(zend_execute_data *cal #define ZEND_CLASS_HAS_TYPE_HINTS(ce) ((bool)(ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS)) #define ZEND_CLASS_HAS_READONLY_PROPS(ce) ((bool)(ce->ce_flags & ZEND_ACC_HAS_READONLY_PROPS)) -static zend_always_inline bool zend_scope_is_derived_from( - const zend_class_entry *scope, const zend_class_entry *ancestor) -{ - for (const zend_class_entry *ce = scope; ce != NULL; ce = ce->parent) { - if (ce == ancestor) { - return true; - } - } - return false; -} - static zend_always_inline bool zend_has_active_ctor_with_promoted_property( const zend_execute_data *ex, zend_object *obj, zend_string *property_name) { From 8e01d6ec985184865c156a88b36a455be9b6e778 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 12 Mar 2026 16:40:46 +0100 Subject: [PATCH 5/7] Revert ZEND_ACC comment change --- Zend/zend_compile.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 3b3dd5deb4c65..61358d29fd62d 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -275,7 +275,7 @@ typedef struct _zend_oparray_context { #define ZEND_ACC_PROTECTED_SET (1 << 11) /* | | X | */ #define ZEND_ACC_PRIVATE_SET (1 << 12) /* | | X | */ /* | | | */ -/* Class Flags | | | */ +/* Class Flags (unused: 31) | | | */ /* =========== | | | */ /* | | | */ /* Special class types | | | */ From 6e8db20850bcdeda9a8dc2875decb86e16af5850 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 12 Mar 2026 16:55:44 +0100 Subject: [PATCH 6/7] Fix potentially uninit var --- Zend/zend_object_handlers.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index b6e7df41814a2..e3dcb9f7a17c7 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1049,13 +1049,13 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva uintptr_t property_offset; const zend_property_info *prop_info = NULL; uint32_t *guard = NULL; + zend_property_write_kind prop_write_kind = ZEND_PROPERTY_WRITE_OK; ZEND_ASSERT(!Z_ISREF_P(value)); property_offset = zend_get_property_offset(zobj->ce, name, (zobj->ce->__set != NULL), cache_slot, &prop_info); if (EXPECTED(IS_VALID_PROPERTY_OFFSET(property_offset))) { -try_again:; - zend_property_write_kind prop_write_kind = ZEND_PROPERTY_WRITE_OK; +try_again: variable_ptr = OBJ_PROP(zobj, property_offset); if (prop_info && UNEXPECTED(prop_info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { From 182e462af1541b3c1ba8f563b7b45488c24f716f Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 12 Mar 2026 17:22:06 +0100 Subject: [PATCH 7/7] Move slow code to C file --- Zend/zend_execute.c | 46 ++++++++++++++++++++++++++++++++++++++++++++ Zend/zend_execute.h | 47 ++------------------------------------------- 2 files changed, 48 insertions(+), 45 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 8393585d09162..8d04d116fcfe9 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -5957,3 +5957,49 @@ ZEND_API void zend_return_unwrap_ref(zend_execute_data *execute_data, zval *retu zend_unwrap_reference(return_value); } + +static zend_always_inline bool zend_has_ctor_for_cpp_reassignment( + const zend_execute_data *ex, zend_object *obj, zend_string *property_name) +{ + for (const zend_execute_data *frame = ex; frame != NULL; frame = frame->prev_execute_data) { + if (!(ZEND_CALL_INFO(frame) & ZEND_CALL_HAS_THIS) || Z_OBJ(frame->This) != obj) { + return false; + } + if (!(frame->func->common.fn_flags & ZEND_ACC_CTOR)) { + continue; + } + + zend_class_entry *scope = frame->func->common.scope; + ZEND_ASSERT(scope); + + zend_property_info *scope_prop = (zend_property_info *) zend_hash_find_ptr(&scope->properties_info, property_name); + ZEND_ASSERT(scope_prop); + return scope_prop->ce == scope && (scope_prop->flags & (ZEND_ACC_READONLY|ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY|ZEND_ACC_PROMOTED); + } + + return false; +} + +ZEND_API ZEND_COLD zend_property_write_kind zend_verify_readonly_slow(zval *property_val, const zend_property_info *info) +{ + ZEND_ASSERT(info->flags & ZEND_ACC_READONLY); + + if (Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE) { + return ZEND_PROPERTY_WRITE_READONLY_REINITABLE; + } + if (Z_PROP_FLAG_P(property_val) & IS_PROP_CTOR_REASSIGNED) { + return ZEND_PROPERTY_WRITE_FORBIDDEN; + } + zend_execute_data *execute_data = EG(current_execute_data); + if (!execute_data || !(EX_CALL_INFO() & ZEND_CALL_HAS_THIS)) { + return ZEND_PROPERTY_WRITE_FORBIDDEN; + } + const zend_op *opline = EX(opline); + zend_object *obj = zend_get_object_from_slot(property_val, info); + if (!zend_has_ctor_for_cpp_reassignment(execute_data, obj, info->name) + || ((opline->opcode == ZEND_ASSIGN_OBJ || opline->opcode == ZEND_ASSIGN_OBJ_REF) + && opline->extended_value & ZEND_ASSIGN_OBJ_PROMOTED_READONLY_INIT)) { + return ZEND_PROPERTY_WRITE_FORBIDDEN; + } + return ZEND_PROPERTY_WRITE_READONLY_CTOR_REASSIGNED; +} diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 602dcb6e69fc0..d4d55231f3b09 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -584,28 +584,6 @@ ZEND_API zend_result ZEND_FASTCALL zend_handle_undef_args(zend_execute_data *cal #define ZEND_CLASS_HAS_TYPE_HINTS(ce) ((bool)(ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS)) #define ZEND_CLASS_HAS_READONLY_PROPS(ce) ((bool)(ce->ce_flags & ZEND_ACC_HAS_READONLY_PROPS)) -static zend_always_inline bool zend_has_active_ctor_with_promoted_property( - const zend_execute_data *ex, zend_object *obj, zend_string *property_name) -{ - for (const zend_execute_data *frame = ex; frame != NULL; frame = frame->prev_execute_data) { - if (!(ZEND_CALL_INFO(frame) & ZEND_CALL_HAS_THIS) || Z_OBJ(frame->This) != obj) { - return false; - } - if (!(frame->func->common.fn_flags & ZEND_ACC_CTOR)) { - continue; - } - - zend_class_entry *scope = frame->func->common.scope; - ZEND_ASSERT(scope); - - zend_property_info *scope_prop = (zend_property_info *) zend_hash_find_ptr(&scope->properties_info, property_name); - ZEND_ASSERT(scope_prop); - return scope_prop->ce == scope && (scope_prop->flags & (ZEND_ACC_READONLY|ZEND_ACC_PROMOTED)) == (ZEND_ACC_READONLY|ZEND_ACC_PROMOTED); - } - - return false; -} - typedef enum _zend_property_write_kind { ZEND_PROPERTY_WRITE_FORBIDDEN = 0, /* Write disallowed */ ZEND_PROPERTY_WRITE_OK, /* Write allowed */ @@ -613,28 +591,7 @@ typedef enum _zend_property_write_kind { ZEND_PROPERTY_WRITE_READONLY_CTOR_REASSIGNED, /* CPP ctor reassignment: set IS_PROP_CTOR_REASSIGNED after write */ } zend_property_write_kind; -static zend_always_inline zend_property_write_kind zend_get_readonly_write_kind( - const zval *property_val, const zend_property_info *prop_info) -{ - if (Z_PROP_FLAG_P(property_val) & IS_PROP_REINITABLE) { - return ZEND_PROPERTY_WRITE_READONLY_REINITABLE; - } - if (Z_PROP_FLAG_P(property_val) & IS_PROP_CTOR_REASSIGNED) { - return ZEND_PROPERTY_WRITE_FORBIDDEN; - } - zend_execute_data *ex = EG(current_execute_data); - if (ex == NULL || !(ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_THIS)) { - return ZEND_PROPERTY_WRITE_FORBIDDEN; - } - zend_object *obj = zend_get_object_from_slot(property_val, prop_info); - if (!zend_has_active_ctor_with_promoted_property(ex, obj, prop_info->name) - || (ex->opline - && (ex->opline->opcode == ZEND_ASSIGN_OBJ || ex->opline->opcode == ZEND_ASSIGN_OBJ_REF) - && (ex->opline->extended_value & ZEND_ASSIGN_OBJ_PROMOTED_READONLY_INIT))) { - return ZEND_PROPERTY_WRITE_FORBIDDEN; - } - return ZEND_PROPERTY_WRITE_READONLY_CTOR_REASSIGNED; -} +ZEND_API ZEND_COLD zend_property_write_kind zend_verify_readonly_slow(zval *property_val, const zend_property_info *info); static zend_always_inline zend_property_write_kind zend_verify_readonly_and_avis( zval *property_val, const zend_property_info *info, bool indirect) @@ -642,7 +599,7 @@ static zend_always_inline zend_property_write_kind zend_verify_readonly_and_avis if (UNEXPECTED(info->flags & (ZEND_ACC_READONLY|ZEND_ACC_PPP_SET_MASK))) { zend_property_write_kind prop_write_kind = ZEND_PROPERTY_WRITE_OK; if ((info->flags & ZEND_ACC_READONLY) && !Z_ISUNDEF_P(property_val)) { - prop_write_kind = zend_get_readonly_write_kind(property_val, info); + prop_write_kind = zend_verify_readonly_slow(property_val, info); if (prop_write_kind == ZEND_PROPERTY_WRITE_FORBIDDEN) { zend_readonly_property_modification_error(info); return ZEND_PROPERTY_WRITE_FORBIDDEN;