From 083333877bbcc212ee617b034856e584f0ebd7d9 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Fri, 14 Aug 2026 15:06:34 +0100 Subject: [PATCH] Zend: declare __invoke() method to Closure Rather than declaring it ad hoc at runtime --- Zend/zend_builtin_functions.c | 22 ++-------------------- Zend/zend_closures.stub.php | 2 ++ Zend/zend_closures_arginfo.h | 8 +++++++- ext/reflection/php_reflection.c | 30 +++++++++++++++++++----------- 4 files changed, 30 insertions(+), 32 deletions(-) diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 6aa47abd2c6b..85fa7d5e198a 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -957,11 +957,6 @@ ZEND_FUNCTION(get_class_methods) zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &method_name); } } ZEND_HASH_FOREACH_END(); - - if (ce == zend_ce_closure) { - ZVAL_STR_COPY(&method_name, ZSTR_KNOWN(ZEND_STR_MAGIC_INVOKE)); - zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &method_name); - } } /* }}} */ @@ -1004,23 +999,10 @@ ZEND_FUNCTION(method_exists) zend_object *obj = Z_OBJ_P(klass); func = Z_OBJ_HT_P(klass)->get_method(&obj, method_name, NULL); if (func != NULL) { - if (func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) { - /* Returns true for the fake Closure's __invoke */ - RETVAL_BOOL(func->common.scope == zend_ce_closure - && zend_string_equals_literal_ci(method_name, ZEND_INVOKE_FUNC_NAME)); - - zend_string_release_ex(func->common.function_name, 0); - zend_free_trampoline(func); - return; - } + ZEND_ASSERT((func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) == 0 + && "Closure::__invoke() should have been handled already"); RETURN_TRUE; } - } else { - /* Returns true for fake Closure::__invoke */ - if (ce == zend_ce_closure - && zend_string_equals_literal_ci(method_name, ZEND_INVOKE_FUNC_NAME)) { - RETURN_TRUE; - } } RETURN_FALSE; } diff --git a/Zend/zend_closures.stub.php b/Zend/zend_closures.stub.php index 46b51617eef9..e9cb11a76eec 100644 --- a/Zend/zend_closures.stub.php +++ b/Zend/zend_closures.stub.php @@ -23,4 +23,6 @@ public function call(object $newThis, mixed ...$args): mixed {} public static function fromCallable(callable $callback): Closure {} public static function getCurrent(): Closure {} + + public function __invoke(mixed ...$values): mixed {} } diff --git a/Zend/zend_closures_arginfo.h b/Zend/zend_closures_arginfo.h index 5bc983a97c2c..1c523dbf6e2f 100644 --- a/Zend/zend_closures_arginfo.h +++ b/Zend/zend_closures_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit zend_closures.stub.php instead. - * Stub hash: e0626e52adb2d38dad1140c1a28cc7774cc84500 */ + * Stub hash: 213756ada15a12d8eb91f4f64f40184530bffc20 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Closure___construct, 0, 0, 0) ZEND_END_ARG_INFO() @@ -27,12 +27,17 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_Closure_getCurrent, 0, 0, Closure, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Closure___invoke, 0, 0, IS_MIXED, 0) + ZEND_ARG_VARIADIC_TYPE_INFO(0, values, IS_MIXED, 0) +ZEND_END_ARG_INFO() + ZEND_METHOD(Closure, __construct); ZEND_METHOD(Closure, bind); ZEND_METHOD(Closure, bindTo); ZEND_METHOD(Closure, call); ZEND_METHOD(Closure, fromCallable); ZEND_METHOD(Closure, getCurrent); +ZEND_METHOD(Closure, __invoke); static const zend_function_entry class_Closure_methods[] = { ZEND_ME(Closure, __construct, arginfo_class_Closure___construct, ZEND_ACC_PRIVATE) @@ -41,6 +46,7 @@ static const zend_function_entry class_Closure_methods[] = { ZEND_ME(Closure, call, arginfo_class_Closure_call, ZEND_ACC_PUBLIC) ZEND_ME(Closure, fromCallable, arginfo_class_Closure_fromCallable, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(Closure, getCurrent, arginfo_class_Closure_getCurrent, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + ZEND_ME(Closure, __invoke, arginfo_class_Closure___invoke, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 1accd39e2578..0d248773e51a 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -3239,8 +3239,7 @@ static void instantiate_reflection_method(INTERNAL_FUNCTION_PARAMETERS, bool is_ char *lcname = zend_str_tolower_dup(method_name, method_name_len); zend_function *mptr; - if (ce == zend_ce_closure && orig_obj && (method_name_len == sizeof(ZEND_INVOKE_FUNC_NAME)-1) - && memcmp(lcname, ZEND_INVOKE_FUNC_NAME, sizeof(ZEND_INVOKE_FUNC_NAME)-1) == 0 + if (ce == zend_ce_closure && orig_obj && zend_string_equals_cstr(ZSTR_KNOWN(ZEND_STR_MAGIC_INVOKE), lcname, method_name_len) && (mptr = zend_get_closure_invoke_method(orig_obj)) != NULL) { /* Store the original closure object so we can validate it in invoke/invokeArgs. @@ -4500,15 +4499,26 @@ ZEND_METHOD(ReflectionClass, getMethods) GET_REFLECTION_OBJECT_PTR(ce); - array_init(return_value); + if (EXPECTED(ce != zend_ce_closure)) { + array_init_size(return_value, zend_hash_num_elements(&ce->function_table)); + ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, zend_function *mptr) { + _addmethod(mptr, ce, Z_ARRVAL_P(return_value), filter); + } ZEND_HASH_FOREACH_END(); + return; + } + + /* For Closure we need to special case the __invoke() method. + * This is so we can get accurate information about parameters. */ + if ((filter & ZEND_ACC_PUBLIC) == 0) { + RETURN_EMPTY_ARRAY(); + } + array_init_size(return_value, zend_hash_num_elements(&ce->function_table)); ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, zend_function *mptr) { + if (zend_string_equals_ci(mptr->common.function_name, ZSTR_KNOWN(ZEND_STR_MAGIC_INVOKE))) { + continue; + } _addmethod(mptr, ce, Z_ARRVAL_P(return_value), filter); } ZEND_HASH_FOREACH_END(); - - // No need for instanceof_function, the Closure class is final - if (ce != zend_ce_closure) { - return; - } bool has_obj = Z_TYPE(intern->obj) != IS_UNDEF; zval obj_tmp; zend_object *obj; @@ -4519,9 +4529,7 @@ ZEND_METHOD(ReflectionClass, getMethods) obj = Z_OBJ(intern->obj); } zend_function *closure = zend_get_closure_invoke_method(obj); - if (closure - && !_addmethod(closure, ce, Z_ARRVAL_P(return_value), filter) - ) { + if (closure && !_addmethod(closure, ce, Z_ARRVAL_P(return_value), filter)) { _free_function(closure); } if (!has_obj) {