Skip to content

Commit d0b408b

Browse files
committed
WIP
1 parent 96ab3b6 commit d0b408b

13 files changed

+681
-619
lines changed

Zend/tests/named_params/call_user_func.phpt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,16 @@ call_user_func($test, c: 'C');
3131
call_user_func($test_variadic, 'A', c: 'C');
3232
call_user_func($test_ref, ref: null);
3333
var_dump(call_user_func('call_user_func', $test, c: 'D'));
34-
var_dump(call_user_func('array_slice', [1, 2, 3, 4, 5], length: 2));
34+
try {
35+
var_dump(call_user_func('array_slice', [1, 2, 3, 4, 5], length: 2));
36+
} catch (ArgumentCountError $e) {
37+
echo $e->getMessage(), "\n";
38+
}
39+
try {
40+
var_dump(call_user_func('array_slice', [1, 2, 3, 4, 'x' => 5], 3, preserve_keys: true));
41+
} catch (ArgumentCountError $e) {
42+
echo $e->getMessage(), "\n";
43+
}
3544
echo "\n";
3645

3746
$test->__invoke('A', c: 'C');
@@ -65,11 +74,12 @@ array(2) {
6574
Warning: {closure}(): Argument #1 ($ref) must be passed by reference, value given in %s on line %d
6675
a = a, b = b, c = D
6776
NULL
77+
array_slice(): Argument #2 ($offset) not passed
6878
array(2) {
69-
[0]=>
70-
int(1)
71-
[1]=>
72-
int(2)
79+
[3]=>
80+
int(4)
81+
["x"]=>
82+
int(5)
7383
}
7484

7585
a = A, b = b, c = C

Zend/zend_compile.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3314,6 +3314,7 @@ uint32_t zend_compile_args(zend_ast *ast, zend_function *fbc) /* {{{ */
33143314
uint32_t i;
33153315
zend_bool uses_arg_unpack = 0;
33163316
zend_bool uses_named_args = 0;
3317+
zend_bool may_have_undef = 0;
33173318
uint32_t arg_count = 0; /* number of arguments not including unpacks */
33183319

33193320
for (i = 0; i < args->children; ++i) {
@@ -3338,6 +3339,9 @@ uint32_t zend_compile_args(zend_ast *ast, zend_function *fbc) /* {{{ */
33383339
opline = zend_emit_op(NULL, ZEND_SEND_UNPACK, &arg_node, NULL);
33393340
opline->op2.num = arg_count;
33403341
opline->result.var = EX_NUM_TO_VAR(arg_count - 1);
3342+
3343+
/* Unpack may contain named arguments. */
3344+
may_have_undef = 1;
33413345
continue;
33423346
}
33433347

@@ -3361,6 +3365,10 @@ uint32_t zend_compile_args(zend_ast *ast, zend_function *fbc) /* {{{ */
33613365
} else {
33623366
arg_num = (uint32_t) -1;
33633367
}
3368+
3369+
if (arg_name) {
3370+
may_have_undef = 1;
3371+
}
33643372
} else {
33653373
if (uses_arg_unpack) {
33663374
zend_error_noreturn(E_COMPILE_ERROR,
@@ -3480,6 +3488,10 @@ uint32_t zend_compile_args(zend_ast *ast, zend_function *fbc) /* {{{ */
34803488
}
34813489
}
34823490

3491+
if (may_have_undef) {
3492+
zend_emit_op(NULL, ZEND_CHECK_NAMED, NULL, NULL);
3493+
}
3494+
34833495
return arg_count;
34843496
}
34853497
/* }}} */
@@ -3863,6 +3875,7 @@ int zend_compile_func_cufa(znode *result, zend_ast_list *args, zend_string *lcna
38633875
}
38643876
zend_compile_expr(&arg_node, args->child[1]);
38653877
zend_emit_op(NULL, ZEND_SEND_ARRAY, &arg_node, NULL);
3878+
zend_emit_op(NULL, ZEND_CHECK_NAMED, NULL, NULL);
38663879
zend_emit_op(result, ZEND_DO_FCALL, NULL, NULL);
38673880

38683881
return SUCCESS;

Zend/zend_execute.c

Lines changed: 95 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3536,8 +3536,7 @@ static zend_always_inline void i_init_func_execute_data(zend_op_array *op_array,
35363536
if (!may_be_trampoline || EXPECTED(!(op_array->fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))) {
35373537
zend_copy_extra_args(EXECUTE_DATA_C);
35383538
}
3539-
} else if (EXPECTED((op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) == 0)
3540-
&& !(EX_CALL_INFO() & ZEND_CALL_MAY_HAVE_UNDEF)) {
3539+
} else if (EXPECTED((op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) == 0)) {
35413540
/* Skip useless ZEND_RECV and ZEND_RECV_INIT opcodes */
35423541
#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID))
35433542
opline += num_args;
@@ -3796,6 +3795,7 @@ static void cleanup_unfinished_calls(zend_execute_data *execute_data, uint32_t o
37963795
break;
37973796
case ZEND_SEND_ARRAY:
37983797
case ZEND_SEND_UNPACK:
3798+
case ZEND_CHECK_NAMED:
37993799
if (level == 0) {
38003800
do_exit = 1;
38013801
}
@@ -4444,40 +4444,109 @@ zval * ZEND_FASTCALL zend_handle_named_arg(
44444444
return arg;
44454445
}
44464446

4447-
ZEND_API int ZEND_FASTCALL zend_handle_icall_undef_args(zend_execute_data *call) {
4448-
zend_function *fbc = call->func;
4449-
if (fbc->common.fn_flags & ZEND_ACC_USER_ARG_INFO) {
4450-
/* Magic function, let it deal with it. */
4451-
return SUCCESS;
4447+
static void start_fake_frame(zend_execute_data *call, const zend_op *opline) {
4448+
zend_execute_data *prev_execute_data = EG(current_execute_data);
4449+
call->prev_execute_data = prev_execute_data;
4450+
call->opline = opline;
4451+
EG(current_execute_data) = call;
4452+
}
4453+
4454+
static void end_fake_frame(zend_execute_data *call) {
4455+
zend_execute_data *prev_execute_data = call->prev_execute_data;
4456+
EG(current_execute_data) = prev_execute_data;
4457+
call->prev_execute_data = NULL;
4458+
if (UNEXPECTED(EG(exception)) && ZEND_USER_CODE(prev_execute_data->func->common.type)) {
4459+
zend_rethrow_exception(prev_execute_data);
44524460
}
4461+
}
44534462

4454-
uint32_t num_args = ZEND_CALL_NUM_ARGS(call);
4455-
for (uint32_t i = 0; i < num_args; i++) {
4456-
zval *arg = ZEND_CALL_VAR_NUM(call, i);
4457-
if (!Z_ISUNDEF_P(arg)) {
4458-
continue;
4459-
}
4463+
ZEND_API int ZEND_FASTCALL zend_handle_undef_args(zend_execute_data *call) {
4464+
zend_function *fbc = call->func;
4465+
if (fbc->type == ZEND_USER_FUNCTION) {
4466+
uint32_t num_args = ZEND_CALL_NUM_ARGS(call);
4467+
for (uint32_t i = 0; i < num_args; i++) {
4468+
zval *arg = ZEND_CALL_VAR_NUM(call, i);
4469+
if (!Z_ISUNDEF_P(arg)) {
4470+
continue;
4471+
}
44604472

4461-
zend_internal_arg_info *arg_info = &fbc->internal_function.arg_info[i];
4462-
if (i < fbc->common.required_num_args) {
4463-
zend_argument_error(zend_ce_argument_count_error, i + 1, "not passed");
4464-
return FAILURE;
4473+
zend_op *opline = &fbc->op_array.opcodes[i];
4474+
if (EXPECTED(opline->opcode == ZEND_RECV_INIT)) {
4475+
zval *default_value = RT_CONSTANT(opline, opline->op2);
4476+
if (Z_OPT_TYPE_P(default_value) == IS_CONSTANT_AST) {
4477+
zval *cache_val =
4478+
(zval *) ((char *) call->run_time_cache + Z_CACHE_SLOT_P(default_value));
4479+
4480+
/* We keep in cache only not refcounted values */
4481+
if (Z_TYPE_P(cache_val) != IS_UNDEF) {
4482+
ZVAL_COPY_VALUE(arg, cache_val);
4483+
} else {
4484+
ZVAL_COPY(arg, default_value);
4485+
start_fake_frame(call, opline);
4486+
int ret = zval_update_constant_ex(arg, fbc->op_array.scope);
4487+
end_fake_frame(call);
4488+
if (UNEXPECTED(ret == FAILURE)) {
4489+
zval_ptr_dtor_nogc(arg);
4490+
ZVAL_UNDEF(arg);
4491+
return FAILURE;
4492+
}
4493+
if (!Z_REFCOUNTED_P(arg)) {
4494+
ZVAL_COPY_VALUE(cache_val, arg);
4495+
}
4496+
}
4497+
} else {
4498+
ZVAL_COPY(arg, default_value);
4499+
}
4500+
} else {
4501+
ZEND_ASSERT(opline->opcode == ZEND_RECV);
4502+
start_fake_frame(call, opline);
4503+
zend_missing_arg_error(call, i + 1);
4504+
end_fake_frame(call);
4505+
}
44654506
}
44664507

4467-
zval default_value;
4468-
if (zend_get_default_from_internal_arg_info(&default_value, arg_info) == FAILURE) {
4469-
zend_argument_error(zend_ce_argument_count_error, i + 1,
4470-
"must be passed explicitly, because the default value is not known");
4471-
return FAILURE;
4508+
return SUCCESS;
4509+
} else {
4510+
if (fbc->common.fn_flags & ZEND_ACC_USER_ARG_INFO) {
4511+
/* Magic function, let it deal with it. */
4512+
return SUCCESS;
44724513
}
44734514

4474-
if (Z_TYPE(default_value) == IS_CONSTANT_AST) {
4475-
if (zval_update_constant_ex(&default_value, fbc->common.scope) == FAILURE) {
4515+
uint32_t num_args = ZEND_CALL_NUM_ARGS(call);
4516+
for (uint32_t i = 0; i < num_args; i++) {
4517+
zval *arg = ZEND_CALL_VAR_NUM(call, i);
4518+
if (!Z_ISUNDEF_P(arg)) {
4519+
continue;
4520+
}
4521+
4522+
zend_internal_arg_info *arg_info = &fbc->internal_function.arg_info[i];
4523+
if (i < fbc->common.required_num_args) {
4524+
start_fake_frame(call, NULL);
4525+
zend_argument_error(zend_ce_argument_count_error, i + 1, "not passed");
4526+
end_fake_frame(call);
4527+
return FAILURE;
4528+
}
4529+
4530+
zval default_value;
4531+
if (zend_get_default_from_internal_arg_info(&default_value, arg_info) == FAILURE) {
4532+
start_fake_frame(call, NULL);
4533+
zend_argument_error(zend_ce_argument_count_error, i + 1,
4534+
"must be passed explicitly, because the default value is not known");
4535+
end_fake_frame(call);
44764536
return FAILURE;
44774537
}
4478-
}
44794538

4480-
ZVAL_COPY_VALUE(arg, &default_value);
4539+
if (Z_TYPE(default_value) == IS_CONSTANT_AST) {
4540+
start_fake_frame(call, NULL);
4541+
int ret = zval_update_constant_ex(&default_value, fbc->common.scope);
4542+
end_fake_frame(call);
4543+
if (ret == FAILURE) {
4544+
return FAILURE;
4545+
}
4546+
}
4547+
4548+
ZVAL_COPY_VALUE(arg, &default_value);
4549+
}
44814550
}
44824551

44834552
return SUCCESS;

Zend/zend_execute.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ ZEND_API void zend_cleanup_unfinished_execution(zend_execute_data *execute_data,
349349
zval * ZEND_FASTCALL zend_handle_named_arg(
350350
zend_execute_data **call_ptr, zend_string *arg_name,
351351
uint32_t *arg_num_ptr, void **cache_slot);
352-
ZEND_API int ZEND_FASTCALL zend_handle_icall_undef_args(zend_execute_data *call);
352+
ZEND_API int ZEND_FASTCALL zend_handle_undef_args(zend_execute_data *call);
353353

354354
#define CACHE_ADDR(num) \
355355
((void**)((char*)EX(run_time_cache) + (num)))

Zend/zend_execute_API.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,17 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
833833
ZEND_ADD_CALL_FLAG(call, call_info);
834834
}
835835

836+
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_MAY_HAVE_UNDEF)) {
837+
if (zend_handle_undef_args(call) == FAILURE) {
838+
zend_vm_stack_free_args(call);
839+
zend_vm_stack_free_call_frame(call);
840+
if (EG(current_execute_data) == &dummy_execute_data) {
841+
EG(current_execute_data) = dummy_execute_data.prev_execute_data;
842+
}
843+
return SUCCESS;
844+
}
845+
}
846+
836847
orig_fake_scope = EG(fake_scope);
837848
EG(fake_scope) = NULL;
838849
if (func->type == ZEND_USER_FUNCTION) {

Zend/zend_vm_def.h

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3887,13 +3887,6 @@ ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL))
38873887
ret = RETURN_VALUE_USED(opline) ? EX_VAR(opline->result.var) : &retval;
38883888
ZVAL_NULL(ret);
38893889

3890-
/* TODO: Don't use the ICALL specialization if named params are used? */
3891-
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_MAY_HAVE_UNDEF)) {
3892-
if (zend_handle_icall_undef_args(call) == FAILURE) {
3893-
ZEND_VM_C_GOTO(do_icall_cleanup);
3894-
}
3895-
}
3896-
38973890
fbc->internal_function.handler(call, ret);
38983891

38993892
#if ZEND_DEBUG
@@ -3908,7 +3901,6 @@ ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL))
39083901
}
39093902
#endif
39103903

3911-
ZEND_VM_C_LABEL(do_icall_cleanup):
39123904
EG(current_execute_data) = execute_data;
39133905
zend_vm_stack_free_args(call);
39143906
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
@@ -4000,13 +3992,6 @@ ZEND_VM_HOT_HANDLER(131, ZEND_DO_FCALL_BY_NAME, ANY, ANY, SPEC(RETVAL))
40003992
ret = RETURN_VALUE_USED(opline) ? EX_VAR(opline->result.var) : &retval;
40013993
ZVAL_NULL(ret);
40023994

4003-
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_MAY_HAVE_UNDEF)) {
4004-
if (zend_handle_icall_undef_args(call) == FAILURE) {
4005-
EG(current_execute_data) = execute_data;
4006-
ZEND_VM_C_GOTO(fcall_by_name_end);
4007-
}
4008-
}
4009-
40103995
fbc->internal_function.handler(call, ret);
40113996

40123997
#if ZEND_DEBUG
@@ -4099,13 +4084,6 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL))
40994084
ret = RETURN_VALUE_USED(opline) ? EX_VAR(opline->result.var) : &retval;
41004085
ZVAL_NULL(ret);
41014086

4102-
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_MAY_HAVE_UNDEF)) {
4103-
if (zend_handle_icall_undef_args(call) == FAILURE) {
4104-
EG(current_execute_data) = execute_data;
4105-
ZEND_VM_C_GOTO(fcall_end);
4106-
}
4107-
}
4108-
41094087
if (!zend_execute_internal) {
41104088
/* saves one function call if zend_execute_internal is not used */
41114089
fbc->internal_function.handler(call, ret);
@@ -5296,6 +5274,18 @@ ZEND_VM_HANDLER(120, ZEND_SEND_USER, CONST|TMP|VAR|CV, NUM)
52965274
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
52975275
}
52985276

5277+
ZEND_VM_HOT_HANDLER(199, ZEND_CHECK_NAMED, UNUSED, UNUSED)
5278+
{
5279+
zend_execute_data *call = execute_data->call;
5280+
if (EXPECTED(!(ZEND_CALL_INFO(call) & ZEND_CALL_MAY_HAVE_UNDEF))) {
5281+
ZEND_VM_NEXT_OPCODE();
5282+
}
5283+
5284+
SAVE_OPLINE();
5285+
zend_handle_undef_args(call);
5286+
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
5287+
}
5288+
52995289
ZEND_VM_COLD_HELPER(zend_missing_arg_helper, ANY, ANY, uint32_t arg_num)
53005290
{
53015291
#ifdef ZEND_VM_IP_GLOBAL_REG

0 commit comments

Comments
 (0)