Skip to content

Commit 5f5b7bb

Browse files
committed
More dynamic internal call support
1 parent 0003399 commit 5f5b7bb

File tree

10 files changed

+120
-59
lines changed

10 files changed

+120
-59
lines changed
Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,59 @@
11
--TEST--
2-
call_user_func() with named parameters
2+
call_user_func() and friends with named parameters
33
--FILE--
44
<?php
55

6-
function test($a = 'a', $b = 'b') {
7-
var_dump($a, $b);
8-
}
6+
$test = function($a = 'a', $b = 'b', $c = 'c') {
7+
echo "a = $a, b = $b, c = $c\n";
8+
};
9+
$test_variadic = function(...$args) {
10+
var_dump($args);
11+
};
12+
13+
call_user_func($test, 'A', c: 'C');
14+
call_user_func($test, c: 'C', a: 'A');
15+
call_user_func($test, c: 'C');
16+
call_user_func($test_variadic, 'A', c: 'C');
17+
var_dump(call_user_func('call_user_func', $test, c: 'D'));
18+
var_dump(call_user_func('array_slice', [1, 2, 3, 4, 5], length: 2));
19+
echo "\n";
920

10-
call_user_func('test', 'A', b: 'B');
11-
call_user_func('test', b: 'B', a: 'A');
12-
call_user_func('test', b: 'B');
21+
$test->__invoke('A', c: 'C');
22+
$test_variadic->__invoke('A', c: 'C');
23+
$test->call(new class {}, 'A', c: 'C');
24+
$test_variadic->call(new class {}, 'A', c: 'C');
1325

1426
?>
1527
--EXPECT--
16-
string(1) "A"
17-
string(1) "B"
18-
string(1) "A"
19-
string(1) "B"
20-
string(1) "a"
21-
string(1) "B"
28+
a = A, b = b, c = C
29+
a = A, b = b, c = C
30+
a = a, b = b, c = C
31+
array(2) {
32+
[0]=>
33+
string(1) "A"
34+
["c"]=>
35+
string(1) "C"
36+
}
37+
a = a, b = b, c = D
38+
NULL
39+
array(2) {
40+
[0]=>
41+
int(1)
42+
[1]=>
43+
int(2)
44+
}
45+
46+
a = A, b = b, c = C
47+
array(2) {
48+
[0]=>
49+
string(1) "A"
50+
["c"]=>
51+
string(1) "C"
52+
}
53+
a = A, b = b, c = C
54+
array(2) {
55+
[0]=>
56+
string(1) "A"
57+
["c"]=>
58+
string(1) "C"
59+
}

Zend/zend_API.h

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -499,12 +499,15 @@ ZEND_API int add_property_zval_ex(zval *arg, const char *key, size_t key_len, zv
499499
#define add_property_zval(__arg, __key, __value) add_property_zval_ex(__arg, __key, strlen(__key), __value)
500500

501501

502-
ZEND_API int _call_user_function_ex(zval *object, zval *function_name, zval *retval_ptr, uint32_t param_count, zval params[], int no_separation);
502+
ZEND_API int _call_user_function_impl(zval *object, zval *function_name, zval *retval_ptr, uint32_t param_count, zval params[], HashTable *named_params, int no_separation);
503503

504504
#define call_user_function(function_table, object, function_name, retval_ptr, param_count, params) \
505-
_call_user_function_ex(object, function_name, retval_ptr, param_count, params, 1)
505+
_call_user_function_impl(object, function_name, retval_ptr, param_count, params, NULL, 1)
506506
#define call_user_function_ex(function_table, object, function_name, retval_ptr, param_count, params, no_separation, symbol_table) \
507-
_call_user_function_ex(object, function_name, retval_ptr, param_count, params, no_separation)
507+
_call_user_function_impl(object, function_name, retval_ptr, param_count, params, NULL, no_separation)
508+
509+
#define call_user_function_named(function_table, object, function_name, retval_ptr, param_count, params, named_params) \
510+
_call_user_function_impl(object, function_name, retval_ptr, param_count, params, named_params, 1)
508511

509512
ZEND_API extern const zend_fcall_info empty_fcall_info;
510513
ZEND_API extern const zend_fcall_info_cache empty_fcall_info_cache;
@@ -570,14 +573,14 @@ ZEND_API int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci
570573
* called_scope must be provided for instance and static method calls. */
571574
ZEND_API void zend_call_known_function(
572575
zend_function *fn, zend_object *object, zend_class_entry *called_scope, zval *retval_ptr,
573-
uint32_t param_count, zval *params);
576+
uint32_t param_count, zval *params, HashTable *named_params);
574577

575578
/* Call the provided zend_function instance method on an object. */
576579
static zend_always_inline void zend_call_known_instance_method(
577580
zend_function *fn, zend_object *object, zval *retval_ptr,
578581
uint32_t param_count, zval *params)
579582
{
580-
zend_call_known_function(fn, object, object->ce, retval_ptr, param_count, params);
583+
zend_call_known_function(fn, object, object->ce, retval_ptr, param_count, params, NULL);
581584
}
582585

583586
static zend_always_inline void zend_call_known_instance_method_with_0_params(

Zend/zend_closures.c

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,15 @@ static zend_object_handlers closure_handlers;
4848
ZEND_METHOD(Closure, __invoke) /* {{{ */
4949
{
5050
zend_function *func = EX(func);
51-
zval *arguments = ZEND_CALL_ARG(execute_data, 1);
51+
zval *args;
52+
uint32_t num_args;
53+
HashTable *named_args;
5254

53-
if (call_user_function(CG(function_table), NULL, ZEND_THIS, return_value, ZEND_NUM_ARGS(), arguments) == FAILURE) {
55+
ZEND_PARSE_PARAMETERS_START(0, -1)
56+
Z_PARAM_VARIADIC_WITH_NAMED(args, num_args, named_args)
57+
ZEND_PARSE_PARAMETERS_END();
58+
59+
if (call_user_function_named(CG(function_table), NULL, ZEND_THIS, return_value, num_args, args, named_args) == FAILURE) {
5460
RETVAL_FALSE;
5561
}
5662

@@ -123,9 +129,10 @@ ZEND_METHOD(Closure, call)
123129
fci.param_count = 0;
124130
fci.params = NULL;
125131

126-
if (zend_parse_parameters(ZEND_NUM_ARGS(), "o*", &newthis, &fci.params, &fci.param_count) == FAILURE) {
127-
RETURN_THROWS();
128-
}
132+
ZEND_PARSE_PARAMETERS_START(1, -1)
133+
Z_PARAM_OBJECT(newthis)
134+
Z_PARAM_VARIADIC_WITH_NAMED(fci.params, fci.param_count, fci.named_params)
135+
ZEND_PARSE_PARAMETERS_END();
129136

130137
closure = (zend_closure *) Z_OBJ_P(ZEND_THIS);
131138

@@ -168,7 +175,6 @@ ZEND_METHOD(Closure, call)
168175
fci.size = sizeof(fci);
169176
ZVAL_OBJ(&fci.function_name, &closure->std);
170177
fci.retval = &closure_result;
171-
fci.named_params = NULL;
172178
fci.no_separation = 1;
173179

174180
if (zend_call_function(&fci, &fci_cache) == SUCCESS && Z_TYPE(closure_result) != IS_UNDEF) {
@@ -250,6 +256,7 @@ static ZEND_NAMED_FUNCTION(zend_closure_call_magic) /* {{{ */ {
250256

251257
fcc.function_handler = (EX(func)->internal_function.fn_flags & ZEND_ACC_STATIC) ?
252258
EX(func)->internal_function.scope->__callstatic : EX(func)->internal_function.scope->__call;
259+
fci.named_params = NULL;
253260
fci.params = params;
254261
fci.param_count = 2;
255262
ZVAL_STR(&fci.params[0], EX(func)->common.function_name);

Zend/zend_execute.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4306,7 +4306,8 @@ static zend_always_inline uint32_t zend_get_arg_offset_by_name(
43064306
}
43074307

43084308
uint32_t num_args = fbc->common.num_args;
4309-
if (fbc->type == ZEND_USER_FUNCTION) {
4309+
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION)
4310+
|| EXPECTED(fbc->common.fn_flags & ZEND_ACC_USER_ARG_INFO)) {
43104311
for (uint32_t i = 0; i < num_args; i++) {
43114312
zend_arg_info *arg_info = &fbc->op_array.arg_info[i];
43124313
if (zend_string_equals(arg_name, arg_info->name)) {
@@ -4401,6 +4402,11 @@ zval * ZEND_FASTCALL zend_handle_named_arg(
44014402

44024403
static int zend_handle_icall_undef_args(zend_execute_data *call) {
44034404
zend_function *fbc = call->func;
4405+
if (fbc->common.fn_flags & ZEND_ACC_USER_ARG_INFO) {
4406+
/* Magic function, let it deal with it. */
4407+
return SUCCESS;
4408+
}
4409+
44044410
uint32_t num_args = ZEND_CALL_NUM_ARGS(call);
44054411
for (uint32_t i = 0; i < num_args; i++) {
44064412
zval *arg = ZEND_CALL_VAR_NUM(call, i);

Zend/zend_execute_API.c

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ ZEND_API int zval_update_constant(zval *pp) /* {{{ */
619619
}
620620
/* }}} */
621621

622-
int _call_user_function_ex(zval *object, zval *function_name, zval *retval_ptr, uint32_t param_count, zval params[], int no_separation) /* {{{ */
622+
int _call_user_function_impl(zval *object, zval *function_name, zval *retval_ptr, uint32_t param_count, zval params[], HashTable *named_params, int no_separation) /* {{{ */
623623
{
624624
zend_fcall_info fci;
625625

@@ -629,7 +629,7 @@ int _call_user_function_ex(zval *object, zval *function_name, zval *retval_ptr,
629629
fci.retval = retval_ptr;
630630
fci.param_count = param_count;
631631
fci.params = params;
632-
fci.named_params = NULL;
632+
fci.named_params = named_params;
633633
fci.no_separation = (zend_bool) no_separation;
634634

635635
return zend_call_function(&fci, NULL);
@@ -733,8 +733,14 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
733733
}
734734

735735
for (i=0; i<fci->param_count; i++) {
736-
zval *param;
736+
zval *param = ZEND_CALL_ARG(call, i+1);
737737
zval *arg = &fci->params[i];
738+
if (UNEXPECTED(Z_ISUNDEF_P(arg))) {
739+
/* Allow forwarding undef slots. This is only used by Closure::__invoke(). */
740+
ZVAL_UNDEF(param);
741+
ZEND_ADD_CALL_FLAG(call, ZEND_CALL_MAY_HAVE_UNDEF);
742+
continue;
743+
}
738744

739745
if (ARG_SHOULD_BE_SENT_BY_REF(func, i + 1)) {
740746
if (UNEXPECTED(!Z_ISREF_P(arg))) {
@@ -764,7 +770,6 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
764770
}
765771
}
766772

767-
param = ZEND_CALL_ARG(call, i+1);
768773
ZVAL_COPY(param, arg);
769774
}
770775

@@ -834,6 +839,9 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
834839
}
835840
EG(current_execute_data) = call->prev_execute_data;
836841
zend_vm_stack_free_args(call);
842+
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
843+
zend_array_release(call->extra_named_params);
844+
}
837845

838846
if (EG(exception)) {
839847
zval_ptr_dtor(fci->retval);
@@ -879,7 +887,7 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
879887

880888
ZEND_API void zend_call_known_function(
881889
zend_function *fn, zend_object *object, zend_class_entry *called_scope, zval *retval_ptr,
882-
uint32_t param_count, zval *params)
890+
uint32_t param_count, zval *params, HashTable *named_params)
883891
{
884892
zval retval;
885893
zend_fcall_info fci;
@@ -892,7 +900,7 @@ ZEND_API void zend_call_known_function(
892900
fci.retval = retval_ptr ? retval_ptr : &retval;
893901
fci.param_count = param_count;
894902
fci.params = params;
895-
fci.named_params = NULL;
903+
fci.named_params = named_params;
896904
fci.no_separation = 1;
897905
ZVAL_UNDEF(&fci.function_name); /* Unused */
898906

Zend/zend_interfaces.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ ZEND_API zval* zend_call_method(zend_object *object, zend_class_entry *obj_ce, z
7979
called_scope = obj_ce;
8080
}
8181

82-
zend_call_known_function(fn, object, called_scope, retval_ptr, param_count, params);
82+
zend_call_known_function(fn, object, called_scope, retval_ptr, param_count, params, NULL);
8383
return retval_ptr;
8484
}
8585
/* }}} */

Zend/zend_vm_def.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2833,11 +2833,14 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY)
28332833
#ifdef ZEND_PREFER_RELOAD
28342834
call_info = EX_CALL_INFO();
28352835
#endif
2836-
if (UNEXPECTED(call_info & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS))) {
2836+
if (UNEXPECTED(call_info & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS))) {
28372837
if (UNEXPECTED(call_info & ZEND_CALL_HAS_SYMBOL_TABLE)) {
28382838
zend_clean_and_cache_symbol_table(EX(symbol_table));
28392839
}
28402840
zend_vm_stack_free_extra_args_ex(call_info, execute_data);
2841+
if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
2842+
zend_array_destroy(EX(extra_named_params));
2843+
}
28412844
}
28422845
if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) {
28432846
OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func)));
@@ -3989,7 +3992,7 @@ ZEND_VM_HOT_HANDLER(131, ZEND_DO_FCALL_BY_NAME, ANY, ANY, SPEC(RETVAL))
39893992

39903993
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_MAY_HAVE_UNDEF)) {
39913994
if (zend_handle_icall_undef_args(call) == FAILURE) {
3992-
EG(current_execute_data) = execute_data;
3995+
EG(current_execute_data) = execute_data;
39933996
ZEND_VM_C_GOTO(fcall_by_name_end);
39943997
}
39953998
}
@@ -4116,6 +4119,9 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL))
41164119

41174120
ZEND_VM_C_LABEL(fcall_end):
41184121
zend_vm_stack_free_args(call);
4122+
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
4123+
zend_array_destroy(call->extra_named_params);
4124+
}
41194125
if (!RETURN_VALUE_USED(opline)) {
41204126
i_zval_ptr_dtor(ret);
41214127
}

Zend/zend_vm_execute.h

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,11 +1179,14 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_leave_helper
11791179
#ifdef ZEND_PREFER_RELOAD
11801180
call_info = EX_CALL_INFO();
11811181
#endif
1182-
if (UNEXPECTED(call_info & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS))) {
1182+
if (UNEXPECTED(call_info & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS))) {
11831183
if (UNEXPECTED(call_info & ZEND_CALL_HAS_SYMBOL_TABLE)) {
11841184
zend_clean_and_cache_symbol_table(EX(symbol_table));
11851185
}
11861186
zend_vm_stack_free_extra_args_ex(call_info, execute_data);
1187+
if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
1188+
zend_array_destroy(EX(extra_named_params));
1189+
}
11871190
}
11881191
if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) {
11891192
OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func)));
@@ -1438,7 +1441,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S
14381441

14391442
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_MAY_HAVE_UNDEF)) {
14401443
if (zend_handle_icall_undef_args(call) == FAILURE) {
1441-
EG(current_execute_data) = execute_data;
1444+
EG(current_execute_data) = execute_data;
14421445
goto fcall_by_name_end;
14431446
}
14441447
}
@@ -1529,7 +1532,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S
15291532

15301533
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_MAY_HAVE_UNDEF)) {
15311534
if (zend_handle_icall_undef_args(call) == FAILURE) {
1532-
EG(current_execute_data) = execute_data;
1535+
EG(current_execute_data) = execute_data;
15331536
goto fcall_by_name_end;
15341537
}
15351538
}
@@ -1656,6 +1659,9 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV
16561659

16571660
fcall_end:
16581661
zend_vm_stack_free_args(call);
1662+
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
1663+
zend_array_destroy(call->extra_named_params);
1664+
}
16591665
if (!0) {
16601666
i_zval_ptr_dtor(ret);
16611667
}
@@ -1761,6 +1767,9 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV
17611767

17621768
fcall_end:
17631769
zend_vm_stack_free_args(call);
1770+
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
1771+
zend_array_destroy(call->extra_named_params);
1772+
}
17641773
if (!1) {
17651774
i_zval_ptr_dtor(ret);
17661775
}
@@ -53157,11 +53166,14 @@ ZEND_API void execute_ex(zend_execute_data *ex)
5315753166
#ifdef ZEND_PREFER_RELOAD
5315853167
call_info = EX_CALL_INFO();
5315953168
#endif
53160-
if (UNEXPECTED(call_info & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS))) {
53169+
if (UNEXPECTED(call_info & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS))) {
5316153170
if (UNEXPECTED(call_info & ZEND_CALL_HAS_SYMBOL_TABLE)) {
5316253171
zend_clean_and_cache_symbol_table(EX(symbol_table));
5316353172
}
5316453173
zend_vm_stack_free_extra_args_ex(call_info, execute_data);
53174+
if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
53175+
zend_array_destroy(EX(extra_named_params));
53176+
}
5316553177
}
5316653178
if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) {
5316753179
OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func)));

ext/reflection/php_reflection.c

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6517,26 +6517,7 @@ static int call_attribute_constructor(zend_class_entry *ce, zend_object *obj, zv
65176517
return FAILURE;
65186518
}
65196519

6520-
{
6521-
zval retval;
6522-
zend_fcall_info fci;
6523-
zend_fcall_info_cache fcic;
6524-
6525-
fci.size = sizeof(zend_fcall_info);
6526-
fci.object = obj;
6527-
fci.retval = &retval;
6528-
fci.param_count = argc;
6529-
fci.params = args;
6530-
fci.no_separation = 1;
6531-
fci.named_params = named_params;
6532-
ZVAL_UNDEF(&fci.function_name); /* Unused */
6533-
6534-
fcic.function_handler = ctor;
6535-
fcic.object = obj;
6536-
fcic.called_scope = obj->ce;
6537-
6538-
zend_call_function(&fci, &fcic);
6539-
}
6520+
zend_call_known_function(ctor, obj, obj->ce, NULL, argc, args, named_params);
65406521

65416522
if (EG(exception)) {
65426523
zend_object_store_ctor_failed(obj);

ext/spl/php_spl.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ static zend_class_entry *spl_perform_autoload(zend_string *class_name, zend_stri
444444

445445
zval param;
446446
ZVAL_STR(&param, class_name);
447-
zend_call_known_function(func, alfi->obj, alfi->ce, NULL, 1, &param);
447+
zend_call_known_function(func, alfi->obj, alfi->ce, NULL, 1, &param, NULL);
448448
if (EG(exception)) {
449449
break;
450450
}

0 commit comments

Comments
 (0)