diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 2487c8b632f2..8b5b53b63b5e 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -758,20 +758,63 @@ ZEND_API void zend_fcall_info_argn(zend_fcall_info *fci, uint32_t argc, ...); ZEND_API zend_result zend_fcall_info_call(zend_fcall_info *fci, zend_fcall_info_cache *fcc, zval *retval, zval *args); /* Zend FCC API to store and handle PHP userland functions */ +extern ZEND_API zend_class_entry *zend_ce_closure; +ZEND_API const zend_function *zend_get_closure_method_def(zend_object *obj); + +static zend_always_inline bool zend_fcc_closure_objects_equals(zend_object *closure1, zend_object *closure2) +{ + if (closure1 == closure2) { + return true; + } + if (!closure1 || !closure2) { + return false; + } + if (closure1->ce != zend_ce_closure || closure2->ce != zend_ce_closure) { + return false; + } + + const zend_function *func1 = zend_get_closure_method_def(closure1); + const zend_function *func2 = zend_get_closure_method_def(closure2); + + if (!(func1->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) || + !(func2->common.fn_flags & ZEND_ACC_FAKE_CLOSURE)) { + return false; + } + if (func1 == func2) { + return true; + } + 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; +} + static zend_always_inline bool zend_fcc_equals(const zend_fcall_info_cache* a, const zend_fcall_info_cache* b) { + if (a->closure || b->closure) { + return a->object == b->object + && a->calling_scope == b->calling_scope + && a->called_scope == b->called_scope + && zend_fcc_closure_objects_equals(a->closure, b->closure) + ; + } if (UNEXPECTED((a->function_handler->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) && (b->function_handler->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))) { return a->object == b->object && a->calling_scope == b->calling_scope - && a->closure == b->closure && zend_string_equals(a->function_handler->common.function_name, b->function_handler->common.function_name) ; } return a->function_handler == b->function_handler && a->object == b->object && a->calling_scope == b->calling_scope - && a->closure == b->closure ; } diff --git a/ext/spl/tests/autoloading/gh22118.phpt b/ext/spl/tests/autoloading/gh22118.phpt new file mode 100644 index 000000000000..11af0e47ccc5 --- /dev/null +++ b/ext/spl/tests/autoloading/gh22118.phpt @@ -0,0 +1,33 @@ +--TEST-- +GH-22118: spl_autoload_unregister() unregisters equivalent first-class method callables +--FILE-- +load(...)); + var_dump(spl_autoload_unregister($this->load(...))); + spl_autoload_call('MissingDirect'); + + spl_autoload_register($this->missing(...)); + var_dump(spl_autoload_unregister($this->missing(...))); + spl_autoload_call('MissingTrampoline'); + } +} + +(new AutoloadTest())->run(); +?> +--EXPECT-- +bool(true) +bool(true) diff --git a/ext/standard/tests/general_functions/gh22118.phpt b/ext/standard/tests/general_functions/gh22118.phpt new file mode 100644 index 000000000000..a4e09fa7ecb2 --- /dev/null +++ b/ext/standard/tests/general_functions/gh22118.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-22118: unregister_tick_function() unregisters equivalent first-class method callables +--FILE-- +direct++; + } + + public function __call(string $name, array $arguments): void + { + $this->trampoline++; + } + + public function run(): void + { + register_tick_function($this->kick(...)); + unregister_tick_function($this->kick(...)); + echo "direct: {$this->direct}\n"; + + register_tick_function($this->missing(...)); + unregister_tick_function($this->missing(...)); + echo "trampoline: {$this->trampoline}\n"; + } +} + +(new TickTest())->run(); +echo "done\n"; +?> +--EXPECT-- +direct: 1 +trampoline: 1 +done