Commit d27da095b1c for php.net

commit d27da095b1cbc353887dda31f165c0ff45de5a05
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Fri Apr 17 11:19:26 2026 -0400

    Fix infinite recursion in property hook getter in opcache preloaded trait

    preload_fix_trait_op_array rewrites the clone op_array from its original
    after optimization, preserving function_name, scope, fn_flags, prototype,
    and static_variables. For trait-cloned property hooks, it also needs to
    preserve prop_info: zend_do_traits_property_binding set it to the using
    class's property, but the reset pointed it back at the trait's property.
    That mismatch caused zend_is_in_hook to miss the self-access check inside
    the hook, recursing into the getter/setter instead of reading or writing
    the backing store.

    Fixes GH-21770
    Closes GH-21788

diff --git a/NEWS b/NEWS
index feb9221c2e0..012a28ffe6c 100644
--- a/NEWS
+++ b/NEWS
@@ -231,6 +231,8 @@ PHP                                                                        NEWS
   . Fixed bug GH-21593 (Borked function JIT JMPNZ smart branch). (ilutov)
   . Fixed bug GH-21460 (COND optimization regression). (Dmitry, Arnaud)
   . Fixed faulty returns out of zend_try block in zend_jit_trace(). (ilutov)
+  . Fixed bug GH-21770 (Infinite recursion in property hook getter in opcache
+    preloaded trait). (iliaal)

 - OpenSSL:
   . Fix a bunch of memory leaks and crashes on edge cases. (ndossche)
diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c
index dca5f607ad3..a2d964c1507 100644
--- a/ext/opcache/ZendAccelerator.c
+++ b/ext/opcache/ZendAccelerator.c
@@ -4290,12 +4290,14 @@ static void preload_fix_trait_op_array(zend_op_array *op_array)
 	uint32_t fn_flags = op_array->fn_flags;
 	zend_function *prototype = op_array->prototype;
 	HashTable *ht = op_array->static_variables;
+	const zend_property_info *prop_info = op_array->prop_info;
 	*op_array = *orig_op_array;
 	op_array->function_name = function_name;
 	op_array->scope = scope;
 	op_array->fn_flags = fn_flags;
 	op_array->prototype = prototype;
 	op_array->static_variables = ht;
+	op_array->prop_info = prop_info;
 }

 static void preload_fix_trait_methods(zend_class_entry *ce)
diff --git a/ext/opcache/tests/gh21770.phpt b/ext/opcache/tests/gh21770.phpt
new file mode 100644
index 00000000000..d2fd52a4712
--- /dev/null
+++ b/ext/opcache/tests/gh21770.phpt
@@ -0,0 +1,25 @@
+--TEST--
+GH-21770 (Infinite recursion in property hook getter in opcache preloaded trait)
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.preload={PWD}/preload_gh21770.inc
+--EXTENSIONS--
+opcache
+--SKIPIF--
+<?php
+if (PHP_OS_FAMILY == 'Windows') die('skip Preloading is not supported on Windows');
+?>
+--FILE--
+<?php
+$b = new B();
+echo $b->a, "\n";
+
+$c = new C();
+$c->x = 42;
+var_dump($c->x);
+?>
+--EXPECT--
+a
+int(42)
diff --git a/ext/opcache/tests/preload_gh21770.inc b/ext/opcache/tests/preload_gh21770.inc
new file mode 100644
index 00000000000..7433a15eca9
--- /dev/null
+++ b/ext/opcache/tests/preload_gh21770.inc
@@ -0,0 +1,20 @@
+<?php
+trait A {
+    public ?string $a = 'a' {
+        get => $this->a;
+    }
+}
+
+trait X {
+    public int $x = 0 {
+        set(int $value) => $value;
+    }
+}
+
+class B {
+    use A;
+}
+
+class C {
+    use X;
+}