Commit c191020fdaf for php.net

commit c191020fdaf020e63b9e9afcb225ce3481b48abb
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Fri Jul 17 13:22:39 2026 -0400

    Fix double-free of closure run_time_cache in partial application (#22790)

    A partial over a closure whose op_array is not persistent shallow-copies
    the closure's op_array into the per-request PFA cache as a lifetime
    anchor. The copy inherited ZEND_ACC_HEAP_RT_CACHE and the run_time_cache
    pointer, so destroy_op_array() freed the closure's cache twice: once when
    the closure was released and again at PFA cache teardown. Use
    function_add_ref() for the copy, which resets the per-request
    run_time_cache and static_variables_ptr map pointers, so only the live
    closure owns and frees the cache.

diff --git a/Zend/tests/partial_application/closure_rt_cache_lifetime.phpt b/Zend/tests/partial_application/closure_rt_cache_lifetime.phpt
new file mode 100644
index 00000000000..7eb4810cf00
--- /dev/null
+++ b/Zend/tests/partial_application/closure_rt_cache_lifetime.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Partial application over a Closure::fromCallable() must not double-free the run_time_cache
+--FILE--
+<?php
+class C {
+    public function m($x, $y) { return $x + $y; }
+}
+
+$cl = Closure::fromCallable([new C, 'm']);
+$partial = $cl(10, ?);
+var_dump($partial(5));
+unset($partial);
+unset($cl);
+gc_collect_cycles();
+echo "OK\n";
+?>
+--EXPECT--
+int(15)
+OK
diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c
index 2c467dc1fc8..0a7a62eae9a 100644
--- a/ext/opcache/ZendAccelerator.c
+++ b/ext/opcache/ZendAccelerator.c
@@ -2146,8 +2146,7 @@ zend_op_array *zend_accel_compile_pfa(zend_ast *ast,
 			 * See comment in zend_accel_pfa_key(). */
 			zend_op_array *copy = zend_arena_alloc(&CG(arena), sizeof(*copy));
 			memcpy(copy, called_function, sizeof(*copy));
-			zend_string_addref(copy->function_name);
-			(*copy->refcount)++;
+			function_add_ref((zend_function *) copy);
 			/* Reference the copy in op_array->dynamic_func_defs so that it's
 			 * destroyed when op_array is destroyed. */
 			ZEND_ASSERT(!op_array->dynamic_func_defs && !op_array->num_dynamic_func_defs);