From 6a17febe6c92119f079969e6b3ab09d03686e65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 26 Jan 2026 08:59:30 +0100 Subject: [PATCH] zend_compile: Fix array_map() optimization (#21016) * zend_compile: Fix handling of PFA syntax in array_map() optimization PFA is not implemented and the syntax is rejected at compile-time, thus it was assumed the assertion would be unreachable. However the check for PFA syntax happens after compiling special functions, making it reachable. Fix this by gracefully returning FAILURE which will then correctly emit the error during the compilation of the normal call. Fixes php/php-src#20991. * zend_compile: Fix array_map() optimization for dynamic function names Fixes php/php-src#20990. * zend_compile: Adjust array_map() optimization after review feedback --- Zend/zend_compile.c | 24 +++--- .../array_map_foreach_optimization_006.phpt | 84 +++++++++++++++++++ 2 files changed, 95 insertions(+), 13 deletions(-) create mode 100644 ext/opcache/tests/array_map_foreach_optimization_006.phpt diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 916d8eebd8960..1ce921d4fec2f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5054,25 +5054,23 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg /* Bail out if the callback is assert() due to the AST stringification logic * breaking for the generated call. */ - if (callback->kind == ZEND_AST_CALL && zend_string_equals_literal_ci(zend_ast_get_str(callback->child[0]), "assert")) { + if (callback->kind == ZEND_AST_CALL + && callback->child[0]->kind == ZEND_AST_ZVAL + && Z_TYPE_P(zend_ast_get_zval(callback->child[0])) == IS_STRING + && zend_string_equals_literal_ci(zend_ast_get_str(callback->child[0]), "assert")) { + return FAILURE; + } + + zend_ast_list *callback_args = zend_ast_get_list(((zend_ast_fcc*)args_ast)->args); + if (callback_args->children != 1 || callback_args->child[0]->attr != ZEND_PLACEHOLDER_VARIADIC) { + /* Full PFA is not yet implemented, will fail in zend_compile_call_common(). */ return FAILURE; } znode value; value.op_type = IS_TMP_VAR; value.u.op.var = get_temporary_variable(); - - zend_ast_list *callback_args = zend_ast_get_list(((zend_ast_fcc*)args_ast)->args); - zend_ast *call_args = zend_ast_create_list(0, ZEND_AST_ARG_LIST); - for (uint32_t i = 0; i < callback_args->children; i++) { - zend_ast *child = callback_args->child[i]; - if (child->kind == ZEND_AST_PLACEHOLDER_ARG) { - call_args = zend_ast_list_add(call_args, zend_ast_create_znode(&value)); - } else { - ZEND_ASSERT(0 && "not implemented"); - call_args = zend_ast_list_add(call_args, child); - } - } + zend_ast *call_args = zend_ast_create_list(1, ZEND_AST_ARG_LIST, zend_ast_create_znode(&value)); zend_op *opline; diff --git a/ext/opcache/tests/array_map_foreach_optimization_006.phpt b/ext/opcache/tests/array_map_foreach_optimization_006.phpt new file mode 100644 index 0000000000000..a3b1c534e3410 --- /dev/null +++ b/ext/opcache/tests/array_map_foreach_optimization_006.phpt @@ -0,0 +1,84 @@ +--TEST-- +array_map(): foreach optimization - dynamic name +--EXTENSIONS-- +opcache +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.opt_debug_level=0x20000 +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=%d, args=0, vars=%d, tmps=%d) + ; (after optimizer) + ; %s +0000 INIT_FCALL 2 %d string("range") +0001 SEND_VAL int(1) 1 +0002 SEND_VAL int(10) 2 +0003 V3 = DO_ICALL +0004 ASSIGN CV0($array) V3 +0005 ASSIGN CV1($plus1) string("plus1") +0006 TYPE_ASSERT 131079 string("array_map") CV0($array) +0007 T3 = INIT_ARRAY 0 (packed) NEXT +0008 V4 = FE_RESET_R CV0($array) 0015 +0009 T6 = FE_FETCH_R V4 T5 0015 +0010 INIT_DYNAMIC_CALL 1 CV1($plus1) +0011 SEND_VAL_EX T5 1 +0012 V5 = DO_FCALL +0013 T3 = ADD_ARRAY_ELEMENT V5 T6 +0014 JMP 0009 +0015 FE_FREE V4 +0016 ASSIGN CV2($foo) T3 +0017 INIT_FCALL 1 %d string("var_dump") +0018 SEND_VAR CV2($foo) 1 +0019 DO_ICALL +0020 RETURN int(1) +LIVE RANGES: + 3: 0008 - 0016 (tmp/var) + 4: 0009 - 0015 (loop) + 5: 0010 - 0011 (tmp/var) + 6: 0010 - 0013 (tmp/var) + +plus1: + ; (lines=3, args=1, vars=1, tmps=%d) + ; (after optimizer) + ; %s +0000 CV0($x) = RECV 1 +0001 T1 = ADD CV0($x) int(1) +0002 RETURN T1 +array(10) { + [0]=> + int(2) + [1]=> + int(3) + [2]=> + int(4) + [3]=> + int(5) + [4]=> + int(6) + [5]=> + int(7) + [6]=> + int(8) + [7]=> + int(9) + [8]=> + int(10) + [9]=> + int(11) +}