[PHP-CVS] [php-src] master: Free unbound arguments when partial application construction throws (#22788)

[email protected] (Ilia Alshanetsky via GitHub)
Newsgroups php.cvs
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Committer: GitHub (web-flow)
Pusher: iliaal
Date: 2026-07-17T11:38:13-04:00

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

Free unbound arguments when partial application construction throws (#22788)

The ZEND_CALLABLE_CONVERT_PARTIAL handler relies on zp_bind() to move
each pre-bound argument into the partial and never frees positional
arguments itself. When construction throws before an argument is moved,
a NULL op_array from zp_get_op_array() or a bound-argument type error,
the unmoved arguments were owned by nobody and leaked. Free the
arguments that were not moved at each throwing path.

Changed paths:
  A  Zend/tests/partial_application/construct_throw_arg_leak.phpt
  M  Zend/zend_partial.c


Diff:

diff --git a/Zend/tests/partial_application/construct_throw_arg_leak.phpt b/Zend/tests/partial_application/construct_throw_arg_leak.phpt
new file mode 100644
index 000000000000..1291901f0cc8
--- /dev/null
+++ b/Zend/tests/partial_application/construct_throw_arg_leak.phpt
@@ -0,0 +1,34 @@
+--TEST--
+Partial application must not leak bound arguments when construction throws
+--FILE--
+<?php
+class Obj {}
+
+function typed(int $a, $b, $c) {}
+try {
+    typed([1, 2, 3], new Obj(), ?);
+} catch (\TypeError $e) {
+    echo $e::class, "\n";
+}
+
+function typed2($a, int $b, $c, $d) {}
+try {
+    typed2(new Obj(), [1, 2], new Obj(), ?);
+} catch (\TypeError $e) {
+    echo $e::class, "\n";
+}
+
+function one($a) {}
+try {
+    one(new Obj(), new Obj(), ?);
+} catch (\ArgumentCountError $e) {
+    echo $e::class, "\n";
+}
+
+echo "OK\n";
+?>
+--EXPECT--
+TypeError
+TypeError
+ArgumentCountError
+OK
diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c
index 4095daa1f1a3..2b5b5f4d3b44 100644
--- a/Zend/zend_partial.c
+++ b/Zend/zend_partial.c
@@ -1069,6 +1069,13 @@ static const zend_op_array *zp_get_op_array(zval *this_ptr, zend_function *funct
 	return op_array;
 }
 
+static void zp_free_unbound_args(uint32_t start, uint32_t argc, zval *argv)
+{
+	for (uint32_t offset = start; offset < argc; offset++) {
+		zval_ptr_dtor_nogc(&argv[offset]);
+	}
+}
+
 /* Bind pre-bound arguments as lexical vars */
 static void zp_bind(zval *result, zend_function *function, uint32_t argc, zval *argv,
 		zend_array *extra_named_params) {
@@ -1102,6 +1109,7 @@ static void zp_bind(zval *result, zend_function *function, uint32_t argc, zval *
 			zend_verify_arg_error(function, arg_info, offset+1, var);
 			zval_ptr_dtor(result);
 			ZVAL_NULL(result);
+			zp_free_unbound_args(offset, argc, argv);
 			return;
 		}
 		ZEND_ASSERT(zp_arg_must_be_sent_by_ref(function, offset+1) ? Z_ISREF_P(var) : !Z_ISREF_P(var));
@@ -1131,6 +1139,7 @@ void zend_partial_create(zval *result, zval *this_ptr, zend_function *function,
 
 	if (UNEXPECTED(!op_array)) {
 		ZEND_ASSERT(EG(exception));
+		zp_free_unbound_args(0, argc, argv);
 		ZVAL_NULL(result);
 		return;
 	}
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.