[php-src] PHP-8.4: Fix infinite recursion in property hook getter in opcache preloaded trait

Ilia Alshanetsky <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-07-01T07:53:08-04:00

Commit: https://github.com/php/php-src/commit/d27da095b1cbc353887dda31f165c0ff45de5a05
Raw diff: https://github.com/php/php-src/commit/d27da095b1cbc353887dda31f165c0ff45de5a05.diff

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

Changed paths:
  A  ext/opcache/tests/gh21770.phpt
  A  ext/opcache/tests/preload_gh21770.inc
  M  NEWS
  M  ext/opcache/ZendAccelerator.c


Diff:

diff --git a/NEWS b/NEWS
index feb9221c2e0b..012a28ffe6ce 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 dca5f607ad39..a2d964c15070 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 000000000000..d2fd52a4712b
--- /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 000000000000..7433a15eca92
--- /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;
+}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.