Skip to content

Commit 3d1bc50

Browse files
committed
Fix ReflectionMethod::invoke() crash with internal closures
The closure identity check added in GH-21366 accessed op_array.opcodes unconditionally, but internal closures (e.g. var_dump(...)) use internal_function, not op_array. This caused undefined behavior when comparing closures created via first-class callable syntax on internal functions. Check the function type first: compare op_array.opcodes for user closures, compare the function pointer directly for internal closures.
1 parent c56e8ca commit 3d1bc50

File tree

2 files changed

+31
-2
lines changed

2 files changed

+31
-2
lines changed

ext/reflection/php_reflection.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3447,12 +3447,19 @@ static void reflection_method_invoke(INTERNAL_FUNCTION_PARAMETERS, int variadic)
34473447
/* For Closure::__invoke(), closures from different source locations have
34483448
* different signatures, so we must reject those. However, closures created
34493449
* from the same source (e.g. in a loop) share the same op_array and should
3450-
* be allowed. Compare the underlying function pointer via op_array. */
3450+
* be allowed. For user closures compare op_array.opcodes, for internal
3451+
* closures (e.g. var_dump(...)) compare the handler pointer. */
34513452
if (obj_ce == zend_ce_closure && !Z_ISUNDEF(intern->obj)
34523453
&& Z_OBJ_P(object) != Z_OBJ(intern->obj)) {
34533454
const zend_function *orig_func = zend_get_closure_method_def(Z_OBJ(intern->obj));
34543455
const zend_function *given_func = zend_get_closure_method_def(Z_OBJ_P(object));
3455-
if (orig_func->op_array.opcodes != given_func->op_array.opcodes) {
3456+
bool same_closure;
3457+
if (orig_func->type == ZEND_USER_FUNCTION && given_func->type == ZEND_USER_FUNCTION) {
3458+
same_closure = orig_func->op_array.opcodes == given_func->op_array.opcodes;
3459+
} else {
3460+
same_closure = orig_func == given_func;
3461+
}
3462+
if (!same_closure) {
34563463
if (!variadic) {
34573464
efree(params);
34583465
}

ext/reflection/tests/gh21362.phpt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,25 @@ $m2 = new ReflectionMethod($closures[0], '__invoke');
4343
foreach ($closures as $closure) {
4444
var_dump($m2->invoke($closure));
4545
}
46+
47+
// Internal closures (first-class callable syntax) should also be validated
48+
$vd = var_dump(...);
49+
$pr = print_r(...);
50+
51+
$m3 = new ReflectionMethod($vd, '__invoke');
52+
$m3->invoke($vd, 'internal closure OK');
53+
54+
// Same internal closure, different instance - should work
55+
$vd2 = var_dump(...);
56+
$m3->invoke($vd2, 'same internal closure OK');
57+
58+
// Different internal closure should throw
59+
try {
60+
$m3->invoke($pr, 'should not print');
61+
echo "No exception thrown\n";
62+
} catch (ReflectionException $e) {
63+
echo $e->getMessage() . "\n";
64+
}
4665
?>
4766
--EXPECT--
4867
c1: foo=FOO, bar=BAR
@@ -51,3 +70,6 @@ Given Closure is not the same as the reflected Closure
5170
int(0)
5271
int(1)
5372
int(2)
73+
string(19) "internal closure OK"
74+
string(24) "same internal closure OK"
75+
Given Closure is not the same as the reflected Closure

0 commit comments

Comments
 (0)