Skip to content

Commit b35da50

Browse files
committed
Refine fake closure FCC comparison
1 parent 5024c3e commit b35da50

4 files changed

Lines changed: 108 additions & 38 deletions

File tree

Zend/zend_API.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4096,6 +4096,52 @@ ZEND_API zend_string *zend_get_callable_name_ex(const zval *callable, const zend
40964096
}
40974097
/* }}} */
40984098

4099+
static bool zend_fcc_function_handler_equals(const zend_function *func1, const zend_function *func2) /* {{{ */
4100+
{
4101+
if (func1 == func2) {
4102+
return true;
4103+
}
4104+
4105+
const bool fake_closure1 = (func1->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0;
4106+
const bool fake_closure2 = (func2->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0;
4107+
4108+
if (!fake_closure1 && !fake_closure2) {
4109+
return false;
4110+
}
4111+
if (((func1->common.fn_flags & ZEND_ACC_CLOSURE) && !fake_closure1) ||
4112+
((func2->common.fn_flags & ZEND_ACC_CLOSURE) && !fake_closure2)) {
4113+
return false;
4114+
}
4115+
if (func1->type != func2->type ||
4116+
func1->common.scope != func2->common.scope ||
4117+
!zend_string_equals(func1->common.function_name, func2->common.function_name)) {
4118+
return false;
4119+
}
4120+
4121+
if (func1->type == ZEND_USER_FUNCTION) {
4122+
return func1->op_array.opcodes == func2->op_array.opcodes;
4123+
}
4124+
4125+
return func1->internal_function.handler == func2->internal_function.handler;
4126+
}
4127+
/* }}} */
4128+
4129+
ZEND_API bool zend_fcc_closure_equals_ex(const zend_fcall_info_cache* a, const zend_fcall_info_cache* b) /* {{{ */
4130+
{
4131+
const zend_function *func1 = a->function_handler;
4132+
const zend_function *func2 = b->function_handler;
4133+
4134+
if (a->closure && a->closure->ce == zend_ce_closure) {
4135+
func1 = zend_get_closure_method_def(a->closure);
4136+
}
4137+
if (b->closure && b->closure->ce == zend_ce_closure) {
4138+
func2 = zend_get_closure_method_def(b->closure);
4139+
}
4140+
4141+
return zend_fcc_function_handler_equals(func1, func2);
4142+
}
4143+
/* }}} */
4144+
40994145
ZEND_API zend_string *zend_get_callable_name(const zval *callable) /* {{{ */
41004146
{
41014147
return zend_get_callable_name_ex(callable, NULL);

Zend/zend_API.h

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -758,51 +758,15 @@ ZEND_API void zend_fcall_info_argn(zend_fcall_info *fci, uint32_t argc, ...);
758758
ZEND_API zend_result zend_fcall_info_call(zend_fcall_info *fci, zend_fcall_info_cache *fcc, zval *retval, zval *args);
759759

760760
/* Zend FCC API to store and handle PHP userland functions */
761-
extern ZEND_API zend_class_entry *zend_ce_closure;
762-
ZEND_API const zend_function *zend_get_closure_method_def(zend_object *obj);
763-
764-
static zend_always_inline bool zend_fcc_closure_objects_equals(zend_object *closure1, zend_object *closure2)
765-
{
766-
if (closure1 == closure2) {
767-
return true;
768-
}
769-
if (!closure1 || !closure2) {
770-
return false;
771-
}
772-
if (closure1->ce != zend_ce_closure || closure2->ce != zend_ce_closure) {
773-
return false;
774-
}
775-
776-
const zend_function *func1 = zend_get_closure_method_def(closure1);
777-
const zend_function *func2 = zend_get_closure_method_def(closure2);
778-
779-
if (!(func1->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) ||
780-
!(func2->common.fn_flags & ZEND_ACC_FAKE_CLOSURE)) {
781-
return false;
782-
}
783-
if (func1 == func2) {
784-
return true;
785-
}
786-
if (func1->type != func2->type ||
787-
func1->common.scope != func2->common.scope ||
788-
!zend_string_equals(func1->common.function_name, func2->common.function_name)) {
789-
return false;
790-
}
791-
792-
if (func1->type == ZEND_USER_FUNCTION) {
793-
return func1->op_array.opcodes == func2->op_array.opcodes;
794-
}
795-
796-
return func1->internal_function.handler == func2->internal_function.handler;
797-
}
761+
ZEND_API bool zend_fcc_closure_equals_ex(const zend_fcall_info_cache* a, const zend_fcall_info_cache* b);
798762

799763
static zend_always_inline bool zend_fcc_equals(const zend_fcall_info_cache* a, const zend_fcall_info_cache* b)
800764
{
801765
if (a->closure || b->closure) {
802766
return a->object == b->object
803767
&& a->calling_scope == b->calling_scope
804768
&& a->called_scope == b->called_scope
805-
&& zend_fcc_closure_objects_equals(a->closure, b->closure)
769+
&& (a->closure == b->closure || zend_fcc_closure_equals_ex(a, b))
806770
;
807771
}
808772
if (UNEXPECTED((a->function_handler->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) &&

ext/spl/tests/autoloading/gh22118.phpt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
GH-22118: spl_autoload_unregister() unregisters equivalent first-class method callables
33
--FILE--
44
<?php
5+
function autoload_string(string $class): void
6+
{
7+
echo "autoload $class\n";
8+
}
9+
510
class AutoloadTest
611
{
712
public function load(string $class): void
@@ -14,6 +19,11 @@ class AutoloadTest
1419
echo "autoload {$arguments[0]}\n";
1520
}
1621

22+
public function __invoke(string $class): void
23+
{
24+
echo "autoload $class\n";
25+
}
26+
1727
public function run(): void
1828
{
1929
spl_autoload_register($this->load(...));
@@ -23,11 +33,26 @@ class AutoloadTest
2333
spl_autoload_register($this->missing(...));
2434
var_dump(spl_autoload_unregister($this->missing(...)));
2535
spl_autoload_call('MissingTrampoline');
36+
37+
spl_autoload_register($this->load(...));
38+
var_dump(spl_autoload_unregister([$this, 'load']));
39+
spl_autoload_call('MissingArray');
40+
41+
spl_autoload_register($this(...));
42+
var_dump(spl_autoload_unregister($this));
43+
spl_autoload_call('MissingInvokable');
2644
}
2745
}
2846

47+
spl_autoload_register(autoload_string(...));
48+
var_dump(spl_autoload_unregister('autoload_string'));
49+
spl_autoload_call('MissingString');
50+
2951
(new AutoloadTest())->run();
3052
?>
3153
--EXPECT--
3254
bool(true)
3355
bool(true)
56+
bool(true)
57+
bool(true)
58+
bool(true)

ext/standard/tests/general_functions/gh22118.phpt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,36 @@ GH-22118: unregister_tick_function() unregisters equivalent first-class method c
44
<?php
55
declare(ticks=1);
66

7+
$string = 0;
8+
9+
function tick_string(): void
10+
{
11+
global $string;
12+
$string++;
13+
}
14+
715
class TickTest
816
{
917
public int $direct = 0;
1018
public int $trampoline = 0;
19+
public int $array = 0;
20+
public int $invoke = 0;
1121

1222
public function kick(): void
1323
{
1424
$this->direct++;
1525
}
1626

27+
public function arrayCallable(): void
28+
{
29+
$this->array++;
30+
}
31+
32+
public function __invoke(): void
33+
{
34+
$this->invoke++;
35+
}
36+
1737
public function __call(string $name, array $arguments): void
1838
{
1939
$this->trampoline++;
@@ -28,13 +48,28 @@ class TickTest
2848
register_tick_function($this->missing(...));
2949
unregister_tick_function($this->missing(...));
3050
echo "trampoline: {$this->trampoline}\n";
51+
52+
register_tick_function($this->arrayCallable(...));
53+
unregister_tick_function([$this, 'arrayCallable']);
54+
echo "array: {$this->array}\n";
55+
56+
register_tick_function($this(...));
57+
unregister_tick_function($this);
58+
echo "invoke: {$this->invoke}\n";
3159
}
3260
}
3361

62+
register_tick_function(tick_string(...));
63+
unregister_tick_function('tick_string');
64+
echo "string: {$string}\n";
65+
3466
(new TickTest())->run();
3567
echo "done\n";
3668
?>
3769
--EXPECT--
70+
string: 1
3871
direct: 1
3972
trampoline: 1
73+
array: 1
74+
invoke: 1
4075
done

0 commit comments

Comments
 (0)