diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 65834adbafff..789b3ac2642e 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -4096,6 +4096,52 @@ 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) /* {{{ */ +{ + 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; + + 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); +} +/* }}} */ + ZEND_API zend_string *zend_get_callable_name(const zval *callable) /* {{{ */ { return zend_get_callable_name_ex(callable, NULL); diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 2487c8b632f2..593be26788d7 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -758,20 +758,27 @@ 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 */ +ZEND_API bool zend_fcc_closure_equals_ex(const zend_fcall_info_cache* a, const zend_fcall_info_cache* b); + 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 + && (a->closure == b->closure || zend_fcc_closure_equals_ex(a, b)) + ; + } 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..635d876c2740 --- /dev/null +++ b/ext/spl/tests/autoloading/gh22118.phpt @@ -0,0 +1,58 @@ +--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'); + + spl_autoload_register($this->load(...)); + var_dump(spl_autoload_unregister([$this, 'load'])); + spl_autoload_call('MissingArray'); + + spl_autoload_register($this(...)); + var_dump(spl_autoload_unregister($this)); + spl_autoload_call('MissingInvokable'); + } +} + +spl_autoload_register(autoload_string(...)); +var_dump(spl_autoload_unregister('autoload_string')); +spl_autoload_call('MissingString'); + +(new AutoloadTest())->run(); +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +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..32c1a0fa1dec --- /dev/null +++ b/ext/standard/tests/general_functions/gh22118.phpt @@ -0,0 +1,75 @@ +--TEST-- +GH-22118: unregister_tick_function() unregisters equivalent first-class method callables +--FILE-- +direct++; + } + + public function arrayCallable(): void + { + $this->array++; + } + + public function __invoke(): void + { + $this->invoke++; + } + + 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"; + + register_tick_function($this->arrayCallable(...)); + unregister_tick_function([$this, 'arrayCallable']); + echo "array: {$this->array}\n"; + + register_tick_function($this(...)); + unregister_tick_function($this); + echo "invoke: {$this->invoke}\n"; + } +} + +register_tick_function(tick_string(...)); +unregister_tick_function('tick_string'); +echo "string: {$string}\n"; + +(new TickTest())->run(); +echo "done\n"; +?> +--EXPECT-- +string: 1 +direct: 1 +trampoline: 1 +array: 1 +invoke: 1 +done