Skip to content

Commit 5024c3e

Browse files
committed
Fix GH-22118: Compare equivalent fake closures in FCCs
1 parent 3447c58 commit 5024c3e

3 files changed

Lines changed: 118 additions & 2 deletions

File tree

Zend/zend_API.h

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -758,20 +758,63 @@ 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+
}
798+
761799
static zend_always_inline bool zend_fcc_equals(const zend_fcall_info_cache* a, const zend_fcall_info_cache* b)
762800
{
801+
if (a->closure || b->closure) {
802+
return a->object == b->object
803+
&& a->calling_scope == b->calling_scope
804+
&& a->called_scope == b->called_scope
805+
&& zend_fcc_closure_objects_equals(a->closure, b->closure)
806+
;
807+
}
763808
if (UNEXPECTED((a->function_handler->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) &&
764809
(b->function_handler->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))) {
765810
return a->object == b->object
766811
&& a->calling_scope == b->calling_scope
767-
&& a->closure == b->closure
768812
&& zend_string_equals(a->function_handler->common.function_name, b->function_handler->common.function_name)
769813
;
770814
}
771815
return a->function_handler == b->function_handler
772816
&& a->object == b->object
773817
&& a->calling_scope == b->calling_scope
774-
&& a->closure == b->closure
775818
;
776819
}
777820

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
GH-22118: spl_autoload_unregister() unregisters equivalent first-class method callables
3+
--FILE--
4+
<?php
5+
class AutoloadTest
6+
{
7+
public function load(string $class): void
8+
{
9+
echo "autoload $class\n";
10+
}
11+
12+
public function __call(string $name, array $arguments): void
13+
{
14+
echo "autoload {$arguments[0]}\n";
15+
}
16+
17+
public function run(): void
18+
{
19+
spl_autoload_register($this->load(...));
20+
var_dump(spl_autoload_unregister($this->load(...)));
21+
spl_autoload_call('MissingDirect');
22+
23+
spl_autoload_register($this->missing(...));
24+
var_dump(spl_autoload_unregister($this->missing(...)));
25+
spl_autoload_call('MissingTrampoline');
26+
}
27+
}
28+
29+
(new AutoloadTest())->run();
30+
?>
31+
--EXPECT--
32+
bool(true)
33+
bool(true)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
--TEST--
2+
GH-22118: unregister_tick_function() unregisters equivalent first-class method callables
3+
--FILE--
4+
<?php
5+
declare(ticks=1);
6+
7+
class TickTest
8+
{
9+
public int $direct = 0;
10+
public int $trampoline = 0;
11+
12+
public function kick(): void
13+
{
14+
$this->direct++;
15+
}
16+
17+
public function __call(string $name, array $arguments): void
18+
{
19+
$this->trampoline++;
20+
}
21+
22+
public function run(): void
23+
{
24+
register_tick_function($this->kick(...));
25+
unregister_tick_function($this->kick(...));
26+
echo "direct: {$this->direct}\n";
27+
28+
register_tick_function($this->missing(...));
29+
unregister_tick_function($this->missing(...));
30+
echo "trampoline: {$this->trampoline}\n";
31+
}
32+
}
33+
34+
(new TickTest())->run();
35+
echo "done\n";
36+
?>
37+
--EXPECT--
38+
direct: 1
39+
trampoline: 1
40+
done

0 commit comments

Comments
 (0)