Skip to content

Commit 7c2e35c

Browse files
committed
Fix GH-22118: Compare equivalent fake closures in FCCs
1 parent 4659762 commit 7c2e35c

4 files changed

Lines changed: 86 additions & 5 deletions

File tree

Zend/zend_API.h

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -753,20 +753,42 @@ ZEND_API void zend_fcall_info_argn(zend_fcall_info *fci, uint32_t argc, ...);
753753
ZEND_API zend_result zend_fcall_info_call(zend_fcall_info *fci, zend_fcall_info_cache *fcc, zval *retval, zval *args);
754754

755755
/* Zend FCC API to store and handle PHP userland functions */
756+
static zend_always_inline bool zend_fcc_closure_objects_equals(zend_object *closure1, zend_object *closure2)
757+
{
758+
extern ZEND_API zend_class_entry *zend_ce_closure;
759+
zval closure_zv1, closure_zv2;
760+
761+
if (closure1 == closure2) {
762+
return true;
763+
}
764+
if (!closure1 || !closure2) {
765+
return false;
766+
}
767+
if (closure1->ce != zend_ce_closure || closure2->ce != zend_ce_closure) {
768+
return false;
769+
}
770+
771+
ZVAL_OBJ(&closure_zv1, closure1);
772+
ZVAL_OBJ(&closure_zv2, closure2);
773+
774+
return zend_compare_objects(&closure_zv1, &closure_zv2) == 0;
775+
}
776+
756777
static zend_always_inline bool zend_fcc_equals(const zend_fcall_info_cache* a, const zend_fcall_info_cache* b)
757778
{
779+
if (a->closure || b->closure) {
780+
return zend_fcc_closure_objects_equals(a->closure, b->closure);
781+
}
758782
if (UNEXPECTED((a->function_handler->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) &&
759783
(b->function_handler->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))) {
760784
return a->object == b->object
761785
&& a->calling_scope == b->calling_scope
762-
&& a->closure == b->closure
763786
&& zend_string_equals(a->function_handler->common.function_name, b->function_handler->common.function_name)
764787
;
765788
}
766789
return a->function_handler == b->function_handler
767790
&& a->object == b->object
768791
&& a->calling_scope == b->calling_scope
769-
&& a->closure == b->closure
770792
;
771793
}
772794

ext/spl/php_spl.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -398,20 +398,21 @@ static autoload_func_info *autoload_func_info_from_fci(
398398

399399
static bool autoload_func_info_equals(
400400
const autoload_func_info *alfi1, const autoload_func_info *alfi2) {
401+
if (alfi1->closure || alfi2->closure) {
402+
return zend_fcc_closure_objects_equals(alfi1->closure, alfi2->closure);
403+
}
401404
if (UNEXPECTED(
402405
(alfi1->func_ptr->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) &&
403406
(alfi2->func_ptr->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)
404407
)) {
405408
return alfi1->obj == alfi2->obj
406409
&& alfi1->ce == alfi2->ce
407-
&& alfi1->closure == alfi2->closure
408410
&& zend_string_equals(alfi1->func_ptr->common.function_name, alfi2->func_ptr->common.function_name)
409411
;
410412
}
411413
return alfi1->func_ptr == alfi2->func_ptr
412414
&& alfi1->obj == alfi2->obj
413-
&& alfi1->ce == alfi2->ce
414-
&& alfi1->closure == alfi2->closure;
415+
&& alfi1->ce == alfi2->ce;
415416
}
416417

417418
static zend_class_entry *spl_perform_autoload(zend_string *class_name, zend_string *lc_name) {

ext/spl/tests/gh22118.phpt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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 run(): void
13+
{
14+
spl_autoload_register($this->load(...));
15+
echo count(spl_autoload_functions()), "\n";
16+
var_dump(spl_autoload_unregister($this->load(...)));
17+
echo count(spl_autoload_functions()), "\n";
18+
spl_autoload_call('MissingClass');
19+
}
20+
}
21+
22+
(new AutoloadTest())->run();
23+
?>
24+
--EXPECT--
25+
1
26+
bool(true)
27+
0
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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 function kick(): void
10+
{
11+
echo "tick\n";
12+
}
13+
14+
public function run(): void
15+
{
16+
register_tick_function($this->kick(...));
17+
echo "registered\n";
18+
unregister_tick_function($this->kick(...));
19+
echo "unregistered\n";
20+
}
21+
}
22+
23+
(new TickTest())->run();
24+
echo "done\n";
25+
?>
26+
--EXPECT--
27+
tick
28+
registered
29+
tick
30+
unregistered
31+
done

0 commit comments

Comments
 (0)