From b434248f2cab83c4f851b24ab9770b655fccbdf7 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sun, 26 Apr 2026 20:52:40 -0400 Subject: [PATCH] Fix GH-17126: get_class_methods omits __invoke for Closure Closure::__invoke is documented and reachable via method_exists() and ReflectionClass (getMethod, hasMethod, getMethods), but get_class_methods() walks ce->function_table directly and Closure does not register __invoke there: the parameter list is materialized per-instance by zend_get_closure_invoke_method(). Append __invoke to the result when ce == zend_ce_closure, the same predicate is_closure_invoke() already uses in ext/reflection. Closes GH-17126 --- Zend/tests/get_class_methods/gh17126.phpt | 48 +++++++++++++++++++++++ Zend/zend_builtin_functions.c | 5 +++ 2 files changed, 53 insertions(+) create mode 100644 Zend/tests/get_class_methods/gh17126.phpt diff --git a/Zend/tests/get_class_methods/gh17126.phpt b/Zend/tests/get_class_methods/gh17126.phpt new file mode 100644 index 000000000000..94cc28a8a9b4 --- /dev/null +++ b/Zend/tests/get_class_methods/gh17126.phpt @@ -0,0 +1,48 @@ +--TEST-- +GH-17126 (get_class_methods($closure) doesn't return __invoke) +--FILE-- + "hello {$str}"; + +echo "from object:\n"; +$methods = get_class_methods($closure); +sort($methods); +print_r($methods); + +echo "from class name:\n"; +$methods = get_class_methods('Closure'); +sort($methods); +print_r($methods); + +echo "unrelated class unaffected:\n"; +class NoInvoke { public function foo() {} } +print_r(get_class_methods('NoInvoke')); + +?> +--EXPECT-- +from object: +Array +( + [0] => __invoke + [1] => bind + [2] => bindTo + [3] => call + [4] => fromCallable + [5] => getCurrent +) +from class name: +Array +( + [0] => __invoke + [1] => bind + [2] => bindTo + [3] => call + [4] => fromCallable + [5] => getCurrent +) +unrelated class unaffected: +Array +( + [0] => foo +) diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index c19bf2779fbf..0d5f218ba12b 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -945,6 +945,11 @@ 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); + } } /* }}} */