Skip to content

Commit 7f2d73a

Browse files
committed
Unify arg info representation for internal and user functions
The arg_info member of zend_function is now always a zend_arg_info*. Before, it was a zend_internal_arg_info* on internal functions, unless the ZEND_ACC_USER_ARG_INFO flag was set. Closes GH-19022
1 parent bc79897 commit 7f2d73a

24 files changed

+385
-239
lines changed

UPGRADING.INTERNALS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ PHP 8.6 INTERNALS UPGRADE NOTES
4949
removed. Call zend_wrong_param_count(); followed by RETURN_THROWS();
5050
instead.
5151
. PHP_HAVE_STREAMS macro removed from <php.h>.
52+
. zend_function.arg_info is now always a zend_arg_info*. Before, the arg_info
53+
member was a zend_internal_arg_info on internal functions, unless the
54+
ZEND_ACC_USER_ARG_INFO flag was set.
5255

5356
========================
5457
2. Build system changes

Zend/tests/closures/gh19653_3.phpt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
GH-19653 (Closure named argument unpacking between temporary closures can cause a crash) - temporary method variation
3+
--EXTENSIONS--
4+
zend_test
5+
--FILE--
6+
<?php
7+
8+
function usage1($f) {
9+
$f(tmpMethodParamName: null);
10+
}
11+
12+
usage1([new _ZendTestClass(), 'testTmpMethodWithArgInfo']);
13+
usage1(eval('return function (string $a, string $b): string { return $a.$b; };'));
14+
15+
?>
16+
--EXPECTF--
17+
Fatal error: Uncaught Error: Unknown named parameter $tmpMethodParamName in %s:%d
18+
Stack trace:
19+
#0 %s(%d): usage1(Object(Closure))
20+
#1 {main}
21+
thrown in %s on line %d
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Declaring non persistent method with arg info
3+
--EXTENSIONS--
4+
zend_test
5+
--FILE--
6+
<?php
7+
8+
$o = new _ZendTestClass();
9+
$o->testTmpMethodWithArgInfo(null);
10+
11+
echo new ReflectionFunction($o->testTmpMethodWithArgInfo(...));
12+
13+
?>
14+
--EXPECT--
15+
Closure [ <internal> public method testTmpMethodWithArgInfo ] {
16+
17+
- Parameters [2] {
18+
Parameter #0 [ <optional> Foo|Bar|null $tmpMethodParamName = null ]
19+
Parameter #1 [ <optional> string $tmpMethodParamWithStringDefaultValue = "tmpMethodParamWithStringDefaultValue" ]
20+
}
21+
}

Zend/zend.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
#include "zend_call_stack.h"
3939
#include "zend_max_execution_timer.h"
4040
#include "zend_hrtime.h"
41+
#include "zend_enum.h"
42+
#include "zend_closures.h"
4143
#include "Optimizer/zend_optimizer.h"
4244
#include "php.h"
4345
#include "php_globals.h"
@@ -1054,6 +1056,7 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */
10541056
ZVAL_UNDEF(&EG(last_fatal_error_backtrace));
10551057

10561058
zend_interned_strings_init();
1059+
zend_object_handlers_startup();
10571060
zend_startup_builtin_functions();
10581061
zend_register_standard_constants();
10591062
zend_register_auto_global(zend_string_init_interned("GLOBALS", sizeof("GLOBALS") - 1, 1), 1, php_auto_globals_create_globals);
@@ -1077,6 +1080,9 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */
10771080
tsrm_set_new_thread_end_handler(zend_new_thread_end_handler);
10781081
tsrm_set_shutdown_handler(zend_interned_strings_dtor);
10791082
#endif
1083+
1084+
zend_enum_startup();
1085+
zend_closure_startup();
10801086
}
10811087
/* }}} */
10821088

Zend/zend_API.c

Lines changed: 104 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
*/
2121

2222
#include "zend.h"
23+
#include "zend_compile.h"
2324
#include "zend_execute.h"
2425
#include "zend_API.h"
2526
#include "zend_hash.h"
@@ -2927,6 +2928,80 @@ static zend_always_inline void zend_normalize_internal_type(zend_type *type) {
29272928
} ZEND_TYPE_FOREACH_END();
29282929
}
29292930

2931+
static void zend_convert_internal_arg_info_type(zend_type *type, bool persistent)
2932+
{
2933+
if (ZEND_TYPE_HAS_LITERAL_NAME(*type)) {
2934+
// gen_stubs.php does not support codegen for compound types. As a
2935+
// temporary workaround, we support union types by splitting
2936+
// the type name on `|` characters if necessary.
2937+
const char *class_name = ZEND_TYPE_LITERAL_NAME(*type);
2938+
type->type_mask &= ~_ZEND_TYPE_LITERAL_NAME_BIT;
2939+
2940+
size_t num_types = 1;
2941+
const char *p = class_name;
2942+
while ((p = strchr(p, '|'))) {
2943+
num_types++;
2944+
p++;
2945+
}
2946+
2947+
if (num_types == 1) {
2948+
/* Simple class type */
2949+
zend_string *str = zend_string_init_interned(class_name, strlen(class_name), persistent);
2950+
zend_alloc_ce_cache(str);
2951+
ZEND_TYPE_SET_PTR(*type, str);
2952+
type->type_mask |= _ZEND_TYPE_NAME_BIT;
2953+
} else {
2954+
/* Union type */
2955+
zend_type_list *list = pemalloc(ZEND_TYPE_LIST_SIZE(num_types), persistent);
2956+
list->num_types = num_types;
2957+
ZEND_TYPE_SET_LIST(*type, list);
2958+
ZEND_TYPE_FULL_MASK(*type) |= _ZEND_TYPE_UNION_BIT;
2959+
2960+
const char *start = class_name;
2961+
uint32_t j = 0;
2962+
while (true) {
2963+
const char *end = strchr(start, '|');
2964+
zend_string *str = zend_string_init_interned(start, end ? end - start : strlen(start), persistent);
2965+
zend_alloc_ce_cache(str);
2966+
list->types[j] = (zend_type) ZEND_TYPE_INIT_CLASS(str, 0, 0);
2967+
if (!end) {
2968+
break;
2969+
}
2970+
start = end + 1;
2971+
j++;
2972+
}
2973+
}
2974+
}
2975+
if (ZEND_TYPE_IS_ITERABLE_FALLBACK(*type)) {
2976+
/* Warning generated an extension load warning which is emitted for every test
2977+
zend_error(E_CORE_WARNING, "iterable type is now a compile time alias for array|Traversable,"
2978+
" regenerate the argument info via the php-src gen_stub build script");
2979+
*/
2980+
zend_type legacy_iterable = ZEND_TYPE_INIT_CLASS_MASK(
2981+
ZSTR_KNOWN(ZEND_STR_TRAVERSABLE),
2982+
(type->type_mask | MAY_BE_ARRAY)
2983+
);
2984+
*type = legacy_iterable;
2985+
}
2986+
}
2987+
2988+
ZEND_API void zend_convert_internal_arg_info(zend_arg_info *new_arg_info, const zend_internal_arg_info *arg_info, bool is_return_info, bool persistent)
2989+
{
2990+
if (!is_return_info) {
2991+
new_arg_info->name = zend_string_init_interned(arg_info->name, strlen(arg_info->name), persistent);
2992+
if (arg_info->default_value) {
2993+
new_arg_info->default_value = zend_string_init_interned(arg_info->default_value, strlen(arg_info->default_value), persistent);
2994+
} else {
2995+
new_arg_info->default_value = NULL;
2996+
}
2997+
} else {
2998+
new_arg_info->name = NULL;
2999+
new_arg_info->default_value = NULL;
3000+
}
3001+
new_arg_info->type = arg_info->type;
3002+
zend_convert_internal_arg_info_type(&new_arg_info->type, persistent);
3003+
}
3004+
29303005
/* registers all functions in *library_functions in the function hash */
29313006
ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend_function_entry *functions, HashTable *function_table, int type) /* {{{ */
29323007
{
@@ -2938,6 +3013,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
29383013
int error_type;
29393014
zend_string *lowercase_name;
29403015
size_t fname_len;
3016+
const zend_internal_arg_info *internal_arg_info;
29413017

29423018
if (type==MODULE_PERSISTENT) {
29433019
error_type = E_CORE_WARNING;
@@ -2994,7 +3070,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
29943070

29953071
if (ptr->arg_info) {
29963072
zend_internal_function_info *info = (zend_internal_function_info*)ptr->arg_info;
2997-
internal_function->arg_info = (zend_internal_arg_info*)ptr->arg_info+1;
3073+
internal_arg_info = ptr->arg_info+1;
29983074
internal_function->num_args = ptr->num_args;
29993075
/* Currently you cannot denote that the function can accept less arguments than num_args */
30003076
if (info->required_num_args == (uintptr_t)-1) {
@@ -3024,7 +3100,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
30243100
zend_error(E_CORE_WARNING, "Missing arginfo for %s%s%s()",
30253101
scope ? ZSTR_VAL(scope->name) : "", scope ? "::" : "", ptr->fname);
30263102

3027-
internal_function->arg_info = NULL;
3103+
internal_arg_info = NULL;
30283104
internal_function->num_args = 0;
30293105
internal_function->required_num_args = 0;
30303106
}
@@ -3035,13 +3111,11 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
30353111
!(internal_function->fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) {
30363112
zend_error(E_CORE_WARNING, "%s::__toString() implemented without string return type",
30373113
ZSTR_VAL(scope->name));
3038-
internal_function->arg_info = (zend_internal_arg_info *) arg_info_toString + 1;
3114+
internal_arg_info = (zend_internal_arg_info *) arg_info_toString + 1;
30393115
internal_function->fn_flags |= ZEND_ACC_HAS_RETURN_TYPE;
30403116
internal_function->num_args = internal_function->required_num_args = 0;
30413117
}
30423118

3043-
3044-
zend_set_function_arg_flags((zend_function*)internal_function);
30453119
if (ptr->flags & ZEND_ACC_ABSTRACT) {
30463120
if (scope) {
30473121
/* This is a class that must be abstract itself. Here we set the check info. */
@@ -3106,17 +3180,17 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
31063180
}
31073181

31083182
/* If types of arguments have to be checked */
3109-
if (reg_function->arg_info && num_args) {
3183+
if (internal_arg_info && num_args) {
31103184
uint32_t i;
31113185
for (i = 0; i < num_args; i++) {
3112-
zend_internal_arg_info *arg_info = &reg_function->arg_info[i];
3186+
const zend_internal_arg_info *arg_info = &internal_arg_info[i];
31133187
ZEND_ASSERT(arg_info->name && "Parameter must have a name");
31143188
if (ZEND_TYPE_IS_SET(arg_info->type)) {
31153189
reg_function->fn_flags |= ZEND_ACC_HAS_TYPE_HINTS;
31163190
}
31173191
#if ZEND_DEBUG
31183192
for (uint32_t j = 0; j < i; j++) {
3119-
if (!strcmp(arg_info->name, reg_function->arg_info[j].name)) {
3193+
if (!strcmp(arg_info->name, internal_arg_info[j].name)) {
31203194
zend_error_noreturn(E_CORE_ERROR,
31213195
"Duplicate parameter name $%s for function %s%s%s()", arg_info->name,
31223196
scope ? ZSTR_VAL(scope->name) : "", scope ? "::" : "", ptr->fname);
@@ -3126,78 +3200,24 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
31263200
}
31273201
}
31283202

3129-
/* Rebuild arginfos if parameter/property types and/or a return type are used */
3130-
if (reg_function->arg_info &&
3131-
(reg_function->fn_flags & (ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_HAS_TYPE_HINTS))) {
3132-
/* convert "const char*" class type names into "zend_string*" */
3203+
/* Convert zend_internal_arg_info to zend_arg_info */
3204+
if (internal_arg_info) {
31333205
uint32_t i;
3134-
zend_internal_arg_info *arg_info = reg_function->arg_info - 1;
3135-
zend_internal_arg_info *new_arg_info;
3206+
const zend_internal_arg_info *arg_info = internal_arg_info - 1;
3207+
zend_arg_info *new_arg_info;
31363208

31373209
/* Treat return type as an extra argument */
31383210
num_args++;
3139-
new_arg_info = malloc(sizeof(zend_internal_arg_info) * num_args);
3140-
memcpy(new_arg_info, arg_info, sizeof(zend_internal_arg_info) * num_args);
3211+
new_arg_info = malloc(sizeof(zend_arg_info) * num_args);
31413212
reg_function->arg_info = new_arg_info + 1;
31423213
for (i = 0; i < num_args; i++) {
3143-
if (ZEND_TYPE_HAS_LITERAL_NAME(new_arg_info[i].type)) {
3144-
// gen_stubs.php does not support codegen for DNF types in arg infos.
3145-
// As a temporary workaround, we split the type name on `|` characters,
3146-
// converting it to an union type if necessary.
3147-
const char *class_name = ZEND_TYPE_LITERAL_NAME(new_arg_info[i].type);
3148-
new_arg_info[i].type.type_mask &= ~_ZEND_TYPE_LITERAL_NAME_BIT;
3149-
3150-
size_t num_types = 1;
3151-
const char *p = class_name;
3152-
while ((p = strchr(p, '|'))) {
3153-
num_types++;
3154-
p++;
3155-
}
3156-
3157-
if (num_types == 1) {
3158-
/* Simple class type */
3159-
zend_string *str = zend_string_init_interned(class_name, strlen(class_name), 1);
3160-
zend_alloc_ce_cache(str);
3161-
ZEND_TYPE_SET_PTR(new_arg_info[i].type, str);
3162-
new_arg_info[i].type.type_mask |= _ZEND_TYPE_NAME_BIT;
3163-
} else {
3164-
/* Union type */
3165-
zend_type_list *list = malloc(ZEND_TYPE_LIST_SIZE(num_types));
3166-
list->num_types = num_types;
3167-
ZEND_TYPE_SET_LIST(new_arg_info[i].type, list);
3168-
ZEND_TYPE_FULL_MASK(new_arg_info[i].type) |= _ZEND_TYPE_UNION_BIT;
3169-
3170-
const char *start = class_name;
3171-
uint32_t j = 0;
3172-
while (true) {
3173-
const char *end = strchr(start, '|');
3174-
zend_string *str = zend_string_init_interned(start, end ? end - start : strlen(start), 1);
3175-
zend_alloc_ce_cache(str);
3176-
list->types[j] = (zend_type) ZEND_TYPE_INIT_CLASS(str, 0, 0);
3177-
if (!end) {
3178-
break;
3179-
}
3180-
start = end + 1;
3181-
j++;
3182-
}
3183-
}
3184-
}
3185-
if (ZEND_TYPE_IS_ITERABLE_FALLBACK(new_arg_info[i].type)) {
3186-
/* Warning generated an extension load warning which is emitted for every test
3187-
zend_error(E_CORE_WARNING, "iterable type is now a compile time alias for array|Traversable,"
3188-
" regenerate the argument info via the php-src gen_stub build script");
3189-
*/
3190-
zend_type legacy_iterable = ZEND_TYPE_INIT_CLASS_MASK(
3191-
ZSTR_KNOWN(ZEND_STR_TRAVERSABLE),
3192-
(new_arg_info[i].type.type_mask | MAY_BE_ARRAY)
3193-
);
3194-
new_arg_info[i].type = legacy_iterable;
3195-
}
3196-
3197-
zend_normalize_internal_type(&new_arg_info[i].type);
3214+
zend_convert_internal_arg_info(&new_arg_info[i], &arg_info[i],
3215+
i == 0, true);
31983216
}
31993217
}
32003218

3219+
zend_set_function_arg_flags((zend_function*)reg_function);
3220+
32013221
if (scope) {
32023222
zend_check_magic_method_implementation(
32033223
scope, (zend_function *)reg_function, lowercase_name, E_CORE_ERROR);
@@ -5242,48 +5262,43 @@ static zend_string *try_parse_string(const char *str, size_t len, char quote) {
52425262
}
52435263

52445264
ZEND_API zend_result zend_get_default_from_internal_arg_info(
5245-
zval *default_value_zval, const zend_internal_arg_info *arg_info)
5265+
zval *default_value_zval, const zend_arg_info *arg_info)
52465266
{
5247-
const char *default_value = arg_info->default_value;
5267+
const zend_string *default_value = arg_info->default_value;
52485268
if (!default_value) {
52495269
return FAILURE;
52505270
}
52515271

52525272
/* Avoid going through the full AST machinery for some simple and common cases. */
5253-
size_t default_value_len = strlen(default_value);
52545273
zend_ulong lval;
5255-
if (default_value_len == sizeof("null")-1
5256-
&& !memcmp(default_value, "null", sizeof("null")-1)) {
5274+
if (zend_string_equals_literal(default_value, "null")) {
52575275
ZVAL_NULL(default_value_zval);
52585276
return SUCCESS;
5259-
} else if (default_value_len == sizeof("true")-1
5260-
&& !memcmp(default_value, "true", sizeof("true")-1)) {
5277+
} else if (zend_string_equals_literal(default_value, "true")) {
52615278
ZVAL_TRUE(default_value_zval);
52625279
return SUCCESS;
5263-
} else if (default_value_len == sizeof("false")-1
5264-
&& !memcmp(default_value, "false", sizeof("false")-1)) {
5280+
} else if (zend_string_equals_literal(default_value, "false")) {
52655281
ZVAL_FALSE(default_value_zval);
52665282
return SUCCESS;
5267-
} else if (default_value_len >= 2
5268-
&& (default_value[0] == '\'' || default_value[0] == '"')
5269-
&& default_value[default_value_len - 1] == default_value[0]) {
5283+
} else if (ZSTR_LEN(default_value) >= 2
5284+
&& (ZSTR_VAL(default_value)[0] == '\'' || ZSTR_VAL(default_value)[0] == '"')
5285+
&& ZSTR_VAL(default_value)[ZSTR_LEN(default_value) - 1] == ZSTR_VAL(default_value)[0]) {
52705286
zend_string *str = try_parse_string(
5271-
default_value + 1, default_value_len - 2, default_value[0]);
5287+
ZSTR_VAL(default_value) + 1, ZSTR_LEN(default_value) - 2, ZSTR_VAL(default_value)[0]);
52725288
if (str) {
52735289
ZVAL_STR(default_value_zval, str);
52745290
return SUCCESS;
52755291
}
5276-
} else if (default_value_len == sizeof("[]")-1
5277-
&& !memcmp(default_value, "[]", sizeof("[]")-1)) {
5292+
} else if (zend_string_equals_literal(default_value, "[]")) {
52785293
ZVAL_EMPTY_ARRAY(default_value_zval);
52795294
return SUCCESS;
5280-
} else if (ZEND_HANDLE_NUMERIC_STR(default_value, default_value_len, lval)) {
5295+
} else if (ZEND_HANDLE_NUMERIC(default_value, lval)) {
52815296
ZVAL_LONG(default_value_zval, lval);
52825297
return SUCCESS;
52835298
}
52845299

52855300
#if 0
5286-
fprintf(stderr, "Evaluating %s via AST\n", default_value);
5301+
fprintf(stderr, "Evaluating %s via AST\n", ZSTR_VAL(default_value));
52875302
#endif
5288-
return get_default_via_ast(default_value_zval, default_value);
5303+
return get_default_via_ast(default_value_zval, ZSTR_VAL(default_value));
52895304
}

Zend/zend_API.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -922,8 +922,12 @@ ZEND_API bool zend_is_iterable(const zval *iterable);
922922

923923
ZEND_API bool zend_is_countable(const zval *countable);
924924

925+
ZEND_API void zend_convert_internal_arg_info(zend_arg_info *new_arg_info,
926+
const zend_internal_arg_info *arg_info, bool is_return_info,
927+
bool permanent);
928+
925929
ZEND_API zend_result zend_get_default_from_internal_arg_info(
926-
zval *default_value_zval, const zend_internal_arg_info *arg_info);
930+
zval *default_value_zval, const zend_arg_info *arg_info);
927931

928932
END_EXTERN_C()
929933

0 commit comments

Comments
 (0)