From 8c008c8f374dfa7a9c1a7cfc7ba5aee561b20748 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Fri, 17 Apr 2026 11:19:26 -0400 Subject: [PATCH] Fix GH-21770: 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. Closes GH-21770 --- ext/opcache/ZendAccelerator.c | 2 ++ ext/opcache/tests/gh21770.phpt | 25 +++++++++++++++++++++++++ ext/opcache/tests/preload_gh21770.inc | 20 ++++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 ext/opcache/tests/gh21770.phpt create mode 100644 ext/opcache/tests/preload_gh21770.inc 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-- + +--FILE-- +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..f6db3ee1f871 --- /dev/null +++ b/ext/opcache/tests/preload_gh21770.inc @@ -0,0 +1,20 @@ + $this->a; + } +} + +trait X { + public int $x = 0 { + set(int $value) => $this->x = $value; + } +} + +class B { + use A; +} + +class C { + use X; +}