Skip to content

Commit b49ce7c

Browse files
committed
Fix GH-22118: Compare equivalent fake closures in FCCs
1 parent 05afc37 commit b49ce7c

4 files changed

Lines changed: 101 additions & 5 deletions

File tree

Zend/zend_API.h

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

749749
/* Zend FCC API to store and handle PHP userland functions */
750+
static zend_always_inline bool zend_fcc_closure_objects_equals(zend_object *closure1, zend_object *closure2)
751+
{
752+
extern ZEND_API zend_class_entry *zend_ce_closure;
753+
zval closure_zv1, closure_zv2;
754+
755+
if (closure1 == closure2) {
756+
return true;
757+
}
758+
if (!closure1 || !closure2) {
759+
return false;
760+
}
761+
if (closure1->ce != zend_ce_closure || closure2->ce != zend_ce_closure) {
762+
return false;
763+
}
764+
765+
ZVAL_OBJ(&closure_zv1, closure1);
766+
ZVAL_OBJ(&closure_zv2, closure2);
767+
768+
return zend_compare_objects(&closure_zv1, &closure_zv2) == 0;
769+
}
770+
750771
static zend_always_inline bool zend_fcc_equals(const zend_fcall_info_cache* a, const zend_fcall_info_cache* b)
751772
{
773+
if (a->closure || b->closure) {
774+
return zend_fcc_closure_objects_equals(a->closure, b->closure);
775+
}
752776
if (UNEXPECTED((a->function_handler->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) &&
753777
(b->function_handler->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))) {
754778
return a->object == b->object
755779
&& a->calling_scope == b->calling_scope
756-
&& a->closure == b->closure
757780
&& zend_string_equals(a->function_handler->common.function_name, b->function_handler->common.function_name)
758781
;
759782
}
760783
return a->function_handler == b->function_handler
761784
&& a->object == b->object
762785
&& a->calling_scope == b->calling_scope
763-
&& a->closure == b->closure
764786
;
765787
}
766788

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: 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)