Commit e3ff9f10a24 for php
commit e3ff9f10a246cc8ed12964991ce3fc89a3cb2df0
Author: Alexander Danilov <10532067+adapik@users.noreply.github.com>
Date: Thu Oct 15 16:20:00 2026 +0300
Fix memory leak in array_map optimization (#24138)
diff --git a/NEWS b/NEWS
index 3e42a4c1d09..cd3a34557f9 100644
--- a/NEWS
+++ b/NEWS
@@ -134,6 +134,8 @@ PHP NEWS
CLOCK_MONOTONIC_RAW. (nicolas-grekas)
. Fixed array_map optimization with non-literal function or non-literal args.
(Arnaud)
+ . Fixed memory leak in array_map() foreach optimization when a constant
+ non-array string argument is passed. (Alexander Danilov)
. Fixed use-after-free when a dl()-loaded extension declares a frameless
function and a later request calls it. (Ilia Alshanetsky)
. Fixed System V shared memory emulation for Windows attaching segments past
diff --git a/Zend/tests/array_map_foreach_optimization_const_non_array.phpt b/Zend/tests/array_map_foreach_optimization_const_non_array.phpt
new file mode 100644
index 00000000000..5ccfbdd7b64
--- /dev/null
+++ b/Zend/tests/array_map_foreach_optimization_const_non_array.phpt
@@ -0,0 +1,32 @@
+--TEST--
+array_map(): foreach optimization - constant non-array argument does not leak
+--FILE--
+<?php
+
+$tests = [
+ 'string' => fn() => array_map(strtoupper(...), "str"),
+ 'int' => fn() => array_map(strtoupper(...), 1),
+ 'float' => fn() => array_map(strtoupper(...), 1.5),
+ 'true' => fn() => array_map(strtoupper(...), true),
+ 'null' => fn() => array_map(strtoupper(...), null),
+ 'folded string' => fn() => array_map(strtoupper(...), "a" . "b"),
+ 'static call' => fn() => array_map(Foo::bar(...), "str"),
+];
+
+foreach ($tests as $name => $test) {
+ try {
+ $test();
+ } catch (Throwable $e) {
+ echo $name, ': ', get_class($e), ': ', $e->getMessage(), "\n";
+ }
+}
+
+?>
+--EXPECT--
+string: TypeError: array_map(): Argument #2 ($array) must be of type array, string given
+int: TypeError: array_map(): Argument #2 ($array) must be of type array, int given
+float: TypeError: array_map(): Argument #2 ($array) must be of type array, float given
+true: TypeError: array_map(): Argument #2 ($array) must be of type array, true given
+null: TypeError: array_map(): Argument #2 ($array) must be of type array, null given
+folded string: TypeError: array_map(): Argument #2 ($array) must be of type array, string given
+static call: Error: Class "Foo" not found
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 40f90e38475..2fbcf1ad2c0 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -5415,15 +5415,7 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg
zend_ast *call_args = zend_partial_apply(callback,
zend_ast_create_znode(&value));
if (!call_args) {
- CG(active_op_array)->T--;
- if (func_node.op_type == IS_CONST) {
- zval_ptr_dtor_nogc(&func_node.u.constant);
- }
- if (class_node.op_type == IS_CONST) {
- zval_ptr_dtor_nogc(&class_node.u.constant);
- }
- /* The callback is not a FCC/PFA, or is not optimizable */
- return FAILURE;
+ goto fail;
}
zend_op *opline;
@@ -5432,6 +5424,10 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg
zend_compile_expr(&array, args->child[1]);
/* array is an argument to both ZEND_TYPE_ASSERT and to ZEND_FE_RESET_R. */
if (array.op_type == IS_CONST) {
+ if (Z_TYPE(array.u.constant) != IS_ARRAY) {
+ zval_ptr_dtor_nogc(&array.u.constant);
+ goto fail;
+ }
Z_TRY_ADDREF(array.u.constant);
}
@@ -5512,6 +5508,17 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg
}
return SUCCESS;
+
+fail:
+ CG(active_op_array)->T--;
+ if (func_node.op_type == IS_CONST) {
+ zval_ptr_dtor_nogc(&func_node.u.constant);
+ }
+ if (class_node.op_type == IS_CONST) {
+ zval_ptr_dtor_nogc(&class_node.u.constant);
+ }
+
+ return FAILURE;
}
static zend_result zend_try_compile_special_func_ex(znode *result, zend_string *lcname, zend_ast_list *args, uint32_t type, uint32_t lineno) /* {{{ */