From d58e95aece64ca6bead8481fc29b8ecc0172a0e6 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 30 Sep 2026 01:17:08 +0100 Subject: [PATCH 1/3] Add new test --- .../gh22118_internal_fake_closures.phpt | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 ext/spl/tests/autoloading/gh22118_internal_fake_closures.phpt diff --git a/ext/spl/tests/autoloading/gh22118_internal_fake_closures.phpt b/ext/spl/tests/autoloading/gh22118_internal_fake_closures.phpt new file mode 100644 index 000000000000..a0410ede489d --- /dev/null +++ b/ext/spl/tests/autoloading/gh22118_internal_fake_closures.phpt @@ -0,0 +1,37 @@ +--TEST-- +GH-22118: spl_autoload_unregister() should also unregister fake closure representing internal function +--FILE-- + +--EXPECT-- +array(1) { + [0]=> + object(Closure)#1 (2) { + ["function"]=> + string(6) "strlen" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } + } +} +bool(false) +array(1) { + [0]=> + object(Closure)#1 (2) { + ["function"]=> + string(6) "strlen" + ["parameter"]=> + array(1) { + ["$string"]=> + string(10) "" + } + } +} From 76bb4a426f2d8f9e0615b7cf2de964254cc68852 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 30 Sep 2026 01:06:12 +0100 Subject: [PATCH 2/3] Zend: fix zend_fcc_closure_equals_ex() implementation Fake closures for internal functions could not return that they were equal due to the zif_handler being wrapped --- Zend/zend_API.c | 48 +++++--------------------------------- Zend/zend_closures.c | 55 ++++++++++++++++++++++++++++++++++++++++++++ Zend/zend_closures.h | 5 ++++ 3 files changed, 66 insertions(+), 42 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 5ef6bbddc3bf..2007796e494f 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -4100,51 +4100,15 @@ ZEND_API zend_string *zend_get_callable_name_ex(const zval *callable, const zend } /* }}} */ -static bool zend_fcc_function_handler_equals(const zend_function *func1, const zend_function *func2) /* {{{ */ +ZEND_API bool zend_fcc_closure_equals_ex(const zend_fcall_info_cache* a, const zend_fcall_info_cache* b) { - if (func1 == func2) { - return true; - } - - const bool fake_closure1 = (func1->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0; - const bool fake_closure2 = (func2->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0; - - if (!fake_closure1 && !fake_closure2) { - return false; - } - if (((func1->common.fn_flags & ZEND_ACC_CLOSURE) && !fake_closure1) || - ((func2->common.fn_flags & ZEND_ACC_CLOSURE) && !fake_closure2)) { - return false; - } - if (func1->type != func2->type || - func1->common.scope != func2->common.scope || - !zend_string_equals(func1->common.function_name, func2->common.function_name)) { - return false; - } - - if (func1->type == ZEND_USER_FUNCTION) { - return func1->op_array.opcodes == func2->op_array.opcodes; - } - - return func1->internal_function.handler == func2->internal_function.handler; -} -/* }}} */ - -ZEND_API bool zend_fcc_closure_equals_ex(const zend_fcall_info_cache* a, const zend_fcall_info_cache* b) /* {{{ */ -{ - const zend_function *func1 = a->function_handler; - const zend_function *func2 = b->function_handler; + const zend_function *fn_a = a->function_handler; + const zend_object *closure_zobj_a = a->closure && a->closure->ce == zend_ce_closure ? a->closure : NULL; + const zend_function *fn_b = b->function_handler; + const zend_object *closure_zobj_b = b->closure && b->closure->ce == zend_ce_closure ? b->closure : NULL; - if (a->closure && a->closure->ce == zend_ce_closure) { - func1 = zend_get_closure_method_def(a->closure); - } - if (b->closure && b->closure->ce == zend_ce_closure) { - func2 = zend_get_closure_method_def(b->closure); - } - - return zend_fcc_function_handler_equals(func1, func2); + return zend_fn_closure_equals_ex(fn_a, closure_zobj_a, fn_b, closure_zobj_b); } -/* }}} */ ZEND_API bool zend_is_callable_at_frame( const zval *callable, zend_object *object, const zend_execute_data *frame, diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index d4c0b5881369..4862e42017e7 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -535,6 +535,61 @@ static int zend_closure_compare(zval *o1, zval *o2) /* {{{ */ } /* }}} */ +ZEND_API bool zend_fn_closure_equals_ex( + const zend_function *fn_a, const zend_object *closure_zobj_a, + const zend_function *fn_b, const zend_object *closure_zobj_b +) { + const zend_closure *closure_a = NULL; + bool is_closure_a_fake = false; + + const zend_closure *closure_b = NULL; + bool is_closure_b_fake = false; + + if (closure_zobj_a) { + closure_a = (const zend_closure *) closure_zobj_a; + is_closure_a_fake = zend_closure_is_fake(closure_a); + fn_a = &closure_a->func; + } + if (closure_zobj_b) { + closure_b = (const zend_closure *) closure_zobj_b; + is_closure_b_fake = zend_closure_is_fake(closure_b); + fn_b = &closure_b->func; + } + + // TODO is this even possible as closure allocates a new zend_function pointer on creation? + if (fn_a == fn_b) { + return true; + } + + if (!is_closure_a_fake && !is_closure_b_fake) { + return false; + } + if ((closure_a && !is_closure_a_fake) || (closure_b && !is_closure_b_fake)) { + return false; + } + if (fn_a->type != fn_b->type || + fn_a->common.scope != fn_b->common.scope || + !zend_string_equals(fn_a->common.function_name, fn_b->common.function_name)) { + return false; + } + + if (fn_a->type == ZEND_USER_FUNCTION) { + return fn_a->op_array.opcodes == fn_b->op_array.opcodes; + } else { + zif_handler zif_a = fn_a->internal_function.handler; + zif_handler zif_b = fn_b->internal_function.handler; + + if (closure_a) { + zif_a = closure_a->orig_internal_handler; + } + if (closure_b) { + zif_b = closure_b->orig_internal_handler; + } + + return zif_a == zif_b; + } +} + ZEND_API zend_function *zend_get_closure_invoke_method(zend_object *object) /* {{{ */ { zend_closure *closure = (zend_closure *)object; diff --git a/Zend/zend_closures.h b/Zend/zend_closures.h index c421c100833a..1d3fc89c1dfd 100644 --- a/Zend/zend_closures.h +++ b/Zend/zend_closures.h @@ -41,6 +41,11 @@ ZEND_API zend_function *zend_get_closure_invoke_method(zend_object *obj); ZEND_API const zend_function *zend_get_closure_method_def(zend_object *obj); ZEND_API zend_object* zend_get_closure_this_ptr(zval *obj); +ZEND_API bool zend_fn_closure_equals_ex( + const zend_function *fn_a, const zend_object *closure_zobj_a, + const zend_function *fn_b, const zend_object *closure_zobj_b +); + END_EXTERN_C() #endif From 648949e818ee8e892aec1d9a6286b41c628c7aff Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Wed, 30 Sep 2026 01:19:26 +0100 Subject: [PATCH 3/3] fix tests --- .../gh22118_internal_fake_closures.phpt | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/ext/spl/tests/autoloading/gh22118_internal_fake_closures.phpt b/ext/spl/tests/autoloading/gh22118_internal_fake_closures.phpt index a0410ede489d..085e0fd6d6bb 100644 --- a/ext/spl/tests/autoloading/gh22118_internal_fake_closures.phpt +++ b/ext/spl/tests/autoloading/gh22118_internal_fake_closures.phpt @@ -22,16 +22,6 @@ array(1) { } } } -bool(false) -array(1) { - [0]=> - object(Closure)#1 (2) { - ["function"]=> - string(6) "strlen" - ["parameter"]=> - array(1) { - ["$string"]=> - string(10) "" - } - } +bool(true) +array(0) { }