[php-src] master: Fix double-free of closure run_time_cache in partial application (#22790)

Ilia Alshanetsky via GitHub <[email protected]> Fri, 17 Jul 2026 17:22:42 +0000
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Committer: GitHub (web-flow)
Pusher: iliaal
Date: 2026-07-17T13:22:39-04:00

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

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.

Changed paths:
  A  Zend/tests/partial_application/closure_rt_cache_lifetime.phpt
  M  ext/opcache/ZendAccelerator.c


Diff:

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 000000000000..7eb4810cf00a
--- /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 2c467dc1fc8a..0a7a62eae9a9 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);