From be4ad129929796ff2a8f0b43b87185f938fd4072 Mon Sep 17 00:00:00 2001 From: ChrisAbner Date: Sat, 7 Nov 2026 11:23:11 -0500 Subject: [PATCH] Fix array_map() optimization not resolving the function name of an FCC --- ...ap_foreach_optimization_function_name.phpt | 127 ++++++++++++++++++ Zend/zend_compile.c | 24 +++- 2 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 Zend/tests/array_map_foreach_optimization_function_name.phpt diff --git a/Zend/tests/array_map_foreach_optimization_function_name.phpt b/Zend/tests/array_map_foreach_optimization_function_name.phpt new file mode 100644 index 000000000000..ae9ea191dacb --- /dev/null +++ b/Zend/tests/array_map_foreach_optimization_function_name.phpt @@ -0,0 +1,127 @@ +--TEST-- +array_map(): foreach optimization resolves the function name of an FCC +--FILE-- + +--EXPECT-- +namespaced function: +array(1) { + [0]=> + string(11) "App\label:1" +} +namespaced function shadowing an internal one: +array(1) { + [0]=> + string(16) "App\strtoupper:a" +} +use function: +array(1) { + [0]=> + string(9) "Lib\fmt:2" +} +use function with alias: +array(1) { + [0]=> + string(11) "App\label:3" +} +namespace-relative name: +array(1) { + [0]=> + string(11) "App\label:4" +} +fully qualified name: +array(1) { + [0]=> + string(8) "global:5" +} +fallback to the global function: +array(1) { + [0]=> + string(13) "only_global:6" +} +fallback to an internal function: +array(1) { + [0]=> + string(1) "B" +} +partial application: +array(1) { + [0]=> + string(10) "App\pad:7!" +} +function declared at runtime: +array(1) { + [0]=> + string(25) "App\declared_at_runtime:8" +} +global namespace, use function: +array(1) { + [0]=> + string(9) "Lib\fmt:9" +} +global namespace, use function with alias: +array(1) { + [0]=> + string(10) "Lib\fmt:10" +} +global namespace, unqualified: +array(1) { + [0]=> + string(9) "global:11" +} diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 4de326b36779..9a0d330f04bf 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5399,9 +5399,19 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg /* Evaluate function name */ znode func_node; - { + zend_ast *func_name_ast = callback->child[callback->kind == ZEND_AST_CALL ? 0 : 1]; + if (callback->kind == ZEND_AST_CALL + && func_name_ast->kind == ZEND_AST_ZVAL + && Z_TYPE_P(zend_ast_get_zval(func_name_ast)) == IS_STRING) { + /* A literal function name has no side effects and must not be memoized: + * compiling it as an expression would yield the unresolved string, which + * ignores the current namespace, "use function" imports and the runtime + * fallback to the global function. Compile the call in the loop body with + * the original name instead, like a regular call. */ + func_node.op_type = IS_UNUSED; + } else { znode result; - zend_compile_expr(&result, callback->child[callback->kind == ZEND_AST_CALL ? 0 : 1]); + zend_compile_expr(&result, func_name_ast); if (result.op_type == IS_CONST || result.op_type == IS_TMP_VAR) { func_node = result; } else { @@ -5459,7 +5469,9 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg /* loop body */ znode call_result; zend_ast *func_ast; - if (func_node.op_type == IS_CONST) { + if (func_node.op_type == IS_UNUSED) { + func_ast = func_name_ast; + } else if (func_node.op_type == IS_CONST) { func_ast = zend_ast_create_znode(&func_node); } else { znode copy_node; @@ -5486,7 +5498,9 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg break; } } - zend_ast_destroy(func_ast); + if (func_ast != func_name_ast) { + zend_ast_destroy(func_ast); + } opline = zend_emit_op(NULL, ZEND_ADD_ARRAY_ELEMENT, &call_result, &key); SET_NODE(opline->result, result); /* end loop body */ @@ -5501,7 +5515,7 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg zend_end_loop(opnum_fetch, &reset_node); zend_emit_op(NULL, ZEND_FE_FREE, &reset_node, NULL); - if (func_node.op_type != IS_CONST) { + if (func_node.op_type != IS_UNUSED && func_node.op_type != IS_CONST) { zend_emit_op(NULL, ZEND_FREE, &func_node, NULL); } if (class_node.op_type != IS_UNUSED && class_node.op_type != IS_CONST) {