From f76ae0ea8c82554fd8177a54e74c65a4dd85f815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Wed, 21 May 2025 15:48:20 +0200 Subject: [PATCH 1/4] Make `array()` a function --- Zend/zend_ast.c | 17 ++++-- Zend/zend_builtin_functions.c | 37 +++++++++++++ Zend/zend_builtin_functions.stub.php | 2 + Zend/zend_builtin_functions_arginfo.h | 8 ++- Zend/zend_compile.h | 1 + Zend/zend_language_parser.y | 20 +++++++ build/gen_stub.php | 3 ++ ext/standard/tests/array/array_function.phpt | 56 ++++++++++++++++++++ 8 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 ext/standard/tests/array/array_function.phpt diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 8bdd29c5512cc..4b7effe93e947 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -2173,9 +2173,20 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio zend_ast_export_list(str, (zend_ast_list*)ast, 1, 20, indent); break; case ZEND_AST_ARRAY: - smart_str_appendc(str, '['); - zend_ast_export_list(str, (zend_ast_list*)ast, 1, 20, indent); - smart_str_appendc(str, ']'); + switch (ast->attr) { + case ZEND_ARRAY_SYNTAX_LONG: + case ZEND_ARRAY_SYNTAX_FUNCTION: + smart_str_appends(str, "array("); + zend_ast_export_list(str, (zend_ast_list*)ast, 1, 20, indent); + smart_str_appendc(str, ')'); + break; + case ZEND_ARRAY_SYNTAX_SHORT: + default: + smart_str_appendc(str, '['); + zend_ast_export_list(str, (zend_ast_list*)ast, 1, 20, indent); + smart_str_appendc(str, ']'); + break; + } break; case ZEND_AST_ENCAPS_LIST: smart_str_appendc(str, '"'); diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 7a07ceadce2e2..6b942acd7a3e8 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -69,6 +69,43 @@ zend_result zend_startup_builtin_functions(void) /* {{{ */ } /* }}} */ +ZEND_FUNCTION(array) +{ + zval *args; + uint32_t argc; + HashTable *named_params; + + ZEND_PARSE_PARAMETERS_START(0, -1) + Z_PARAM_VARIADIC_WITH_NAMED(args, argc, named_params); + ZEND_PARSE_PARAMETERS_END(); + + if (EXPECTED(argc == 0)) { + if (EXPECTED(named_params != NULL)) { + GC_ADDREF(named_params); + RETURN_ARR(named_params); + } else { + RETURN_EMPTY_ARRAY(); + } + } else { + HashTable *entries; + + ALLOC_HASHTABLE(entries); + zend_hash_init(entries, argc + (named_params ? zend_hash_num_elements(named_params) : 0), NULL, NULL, false); + for (uint32_t i = 0; i < argc; i++) { + zend_hash_index_add_new(entries, i, &args[i]); + } + if (named_params != NULL) { + zend_string *key; + zval *val; + ZEND_HASH_FOREACH_STR_KEY_VAL(named_params, key, val) { + zend_hash_update(entries, key, val); + } ZEND_HASH_FOREACH_END(); + } + + RETURN_ARR(entries); + } +} + ZEND_FUNCTION(exit) { zend_string *str = NULL; diff --git a/Zend/zend_builtin_functions.stub.php b/Zend/zend_builtin_functions.stub.php index 7f316835aea6b..f2339c7b7257d 100644 --- a/Zend/zend_builtin_functions.stub.php +++ b/Zend/zend_builtin_functions.stub.php @@ -7,6 +7,8 @@ class stdClass { } +function _array(mixed ...$entries): array {} + function exit(string|int $status = 0): never {} /** @alias exit */ diff --git a/Zend/zend_builtin_functions_arginfo.h b/Zend/zend_builtin_functions_arginfo.h index 9498b8292f892..7aef4e979233e 100644 --- a/Zend/zend_builtin_functions_arginfo.h +++ b/Zend/zend_builtin_functions_arginfo.h @@ -1,5 +1,9 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: a24761186f1ddf758e648b0a764826537cbd33b9 */ + * Stub hash: 7c7fee9683cb3b717ee395b6c0203c4b3484268b */ + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_array, 0, 0, IS_ARRAY, 0) + ZEND_ARG_VARIADIC_TYPE_INFO(0, entries, IS_MIXED, 0) +ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_exit, 0, 0, IS_NEVER, 0) ZEND_ARG_TYPE_MASK(0, status, MAY_BE_STRING|MAY_BE_LONG, "0") @@ -243,6 +247,7 @@ static const zend_frameless_function_info frameless_function_infos_class_exists[ { 0 }, }; +ZEND_FUNCTION(array); ZEND_FUNCTION(exit); ZEND_FUNCTION(zend_version); ZEND_FUNCTION(func_num_args); @@ -306,6 +311,7 @@ ZEND_FUNCTION(gc_disable); ZEND_FUNCTION(gc_status); static const zend_function_entry ext_functions[] = { + ZEND_FE(array, arginfo_array) ZEND_FE(exit, arginfo_exit) ZEND_RAW_FENTRY("die", zif_exit, arginfo_die, 0, NULL, NULL) ZEND_FE(zend_version, arginfo_zend_version) diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 62d0fbcded2ee..c7f7f7736541b 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1053,6 +1053,7 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); #define ZEND_ARRAY_SYNTAX_LIST 1 /* list() */ #define ZEND_ARRAY_SYNTAX_LONG 2 /* array() */ #define ZEND_ARRAY_SYNTAX_SHORT 3 /* [] */ +#define ZEND_ARRAY_SYNTAX_FUNCTION 4 /* [] */ /* var status for backpatching */ #define BP_VAR_R 0 diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 08b2ac6b3f39b..4a1d09cf2fa45 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -286,6 +286,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type function_name non_empty_member_modifiers %type property_hook property_hook_list optional_property_hook_list hooked_property property_hook_body %type optional_parameter_list +%type non_empty_array_function_argument_list array_function_argument %type returns_ref function fn is_reference is_variadic property_modifiers property_hook_modifiers %type method_modifiers class_const_modifiers member_modifier optional_cpp_modifiers @@ -1447,11 +1448,30 @@ ctor_arguments: dereferenceable_scalar: T_ARRAY '(' array_pair_list ')' { $$ = $3; $$->attr = ZEND_ARRAY_SYNTAX_LONG; } + | T_ARRAY '(' non_empty_array_function_argument_list possible_comma ')' + { $$ = $3; $$->attr = ZEND_ARRAY_SYNTAX_FUNCTION; } + | T_ARRAY '(' T_ELLIPSIS ')' { + zend_ast *name = zend_ast_create_zval_from_str(ZSTR_KNOWN(ZEND_STR_ARRAY)); + name->attr = ZEND_NAME_FQ; + $$ = zend_ast_create(ZEND_AST_CALL, name, zend_ast_create_fcc()); + } | '[' array_pair_list ']' { $$ = $2; $$->attr = ZEND_ARRAY_SYNTAX_SHORT; } | T_CONSTANT_ENCAPSED_STRING { $$ = $1; } | '"' encaps_list '"' { $$ = $2; } ; +non_empty_array_function_argument_list: + array_function_argument + { $$ = zend_ast_create_list(1, ZEND_AST_ARRAY, $1); } + | non_empty_array_function_argument_list ',' array_function_argument + { $$ = zend_ast_list_add($1, $3); } +; + +array_function_argument: + identifier ':' expr + { $$ = zend_ast_create(ZEND_AST_NAMED_ARG, $1, $3); } +; + scalar: T_LNUMBER { $$ = $1; } | T_DNUMBER { $$ = $1; } diff --git a/build/gen_stub.php b/build/gen_stub.php index 5e06a53172e07..d3e67578c8a42 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1009,6 +1009,9 @@ class FunctionName implements FunctionOrMethodName { private /* readonly */ Name $name; public function __construct(Name $name) { + if ($name->name === '_array') { + $name = new Name('array', $name->getAttributes()); + } $this->name = $name; } diff --git a/ext/standard/tests/array/array_function.phpt b/ext/standard/tests/array/array_function.phpt new file mode 100644 index 0000000000000..dddc4ef8d0626 --- /dev/null +++ b/ext/standard/tests/array/array_function.phpt @@ -0,0 +1,56 @@ +--TEST-- +Test array() function +--FILE-- +getMessage(), PHP_EOL; +} + +var_dump(array_map(array(...), [1, 2, 3])); + +?> +--EXPECT-- +array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) +} +array(2) { + ["foo"]=> + string(3) "bar" + ["baz"]=> + string(4) "quux" +} +array(2) { + ["bar"]=> + string(3) "foo" + ["quux"]=> + string(3) "baz" +} +assert(false && \array(foo: 'bar', baz: 'quux')) +array(3) { + [0]=> + array(1) { + [0]=> + int(1) + } + [1]=> + array(1) { + [0]=> + int(2) + } + [2]=> + array(1) { + [0]=> + int(3) + } +} From 7accee033734363c2fc4d48645db074acdc587fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Wed, 21 May 2025 16:07:26 +0200 Subject: [PATCH 2/4] Fix tests/array/array_map_variation16.phpt --- ext/standard/tests/array/array_map_variation16.phpt | 2 -- 1 file changed, 2 deletions(-) diff --git a/ext/standard/tests/array/array_map_variation16.phpt b/ext/standard/tests/array/array_map_variation16.phpt index 7ee1f04967c5c..5a0c648c7cc14 100644 --- a/ext/standard/tests/array/array_map_variation16.phpt +++ b/ext/standard/tests/array/array_map_variation16.phpt @@ -8,7 +8,6 @@ $arg = [1, 2]; // built-in functions & language constructs $callbacks = [ 'echo', - 'array', 'empty', 'eval', 'isset', @@ -29,7 +28,6 @@ echo "Done"; ?> --EXPECT-- array_map(): Argument #1 ($callback) must be a valid callback or null, function "echo" not found or invalid function name -array_map(): Argument #1 ($callback) must be a valid callback or null, function "array" not found or invalid function name array_map(): Argument #1 ($callback) must be a valid callback or null, function "empty" not found or invalid function name array_map(): Argument #1 ($callback) must be a valid callback or null, function "eval" not found or invalid function name array_map(): Argument #1 ($callback) must be a valid callback or null, function "isset" not found or invalid function name From 978ba6ea50f404d9d62429e88190f1f10f12f47e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Thu, 22 May 2025 09:45:13 +0200 Subject: [PATCH 3/4] Disallow disabling the `array()` function --- Zend/zend_API.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index e0006e7d7275f..875b6eb6c37e0 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3641,6 +3641,7 @@ static void zend_disable_function(const char *function_name, size_t function_nam if (UNEXPECTED( (function_name_length == strlen("exit") && !memcmp(function_name, "exit", strlen("exit"))) || (function_name_length == strlen("die") && !memcmp(function_name, "die", strlen("die"))) + || (function_name_length == strlen("array") && !memcmp(function_name, "array", strlen("array"))) )) { zend_error(E_WARNING, "Cannot disable function %s()", function_name); return; From c77a97dde98e0395a3f8e49f631592a184e88d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Thu, 22 May 2025 09:48:03 +0200 Subject: [PATCH 4/4] Fix comment in zend_compile.h --- Zend/zend_compile.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index c7f7f7736541b..ca5dde972dc34 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1053,7 +1053,7 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); #define ZEND_ARRAY_SYNTAX_LIST 1 /* list() */ #define ZEND_ARRAY_SYNTAX_LONG 2 /* array() */ #define ZEND_ARRAY_SYNTAX_SHORT 3 /* [] */ -#define ZEND_ARRAY_SYNTAX_FUNCTION 4 /* [] */ +#define ZEND_ARRAY_SYNTAX_FUNCTION 4 /* array(key: "val") */ /* var status for backpatching */ #define BP_VAR_R 0