Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions ext/reflection/php_reflection.c
Original file line number Diff line number Diff line change
Expand Up @@ -3445,14 +3445,27 @@ static void reflection_method_invoke(INTERNAL_FUNCTION_PARAMETERS, int variadic)
}

/* For Closure::__invoke(), closures from different source locations have
* different signatures, so we must reject those. However, closures created
* from the same source (e.g. in a loop) share the same op_array and should
* be allowed. Compare the underlying function pointer via op_array. */
* different signatures, so we must reject those. */
if (obj_ce == zend_ce_closure && !Z_ISUNDEF(intern->obj)
&& Z_OBJ_P(object) != Z_OBJ(intern->obj)) {
const zend_function *orig_func = zend_get_closure_method_def(Z_OBJ(intern->obj));
const zend_function *given_func = zend_get_closure_method_def(Z_OBJ_P(object));
if (orig_func->op_array.opcodes != given_func->op_array.opcodes) {

bool same_closure = false;
/* Check if they are either both fake closures or they both are not. */
if ((orig_func->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) == (given_func->common.fn_flags & ZEND_ACC_FAKE_CLOSURE)) {
if (orig_func->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) {
/* For fake closures, scope and name must match. */
same_closure = orig_func->common.scope == given_func->common.scope
&& orig_func->common.function_name == given_func->common.function_name;
} else {
/* Otherwise the opcode structure must be identical. */
ZEND_ASSERT(orig_func->type == ZEND_USER_FUNCTION);
same_closure = orig_func->op_array.opcodes == given_func->op_array.opcodes;
}
}

if (!same_closure) {
if (!variadic) {
efree(params);
}
Expand Down
62 changes: 62 additions & 0 deletions ext/reflection/tests/gh21362.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,60 @@ $m2 = new ReflectionMethod($closures[0], '__invoke');
foreach ($closures as $closure) {
var_dump($m2->invoke($closure));
}

// First-class callable of a userland function
function my_func($x) { return "my_func: $x"; }
function other_func($x) { return "other_func: $x"; }

$mf = my_func(...);
$mf2 = my_func(...);
$of = other_func(...);

$m3 = new ReflectionMethod($mf, '__invoke');
var_dump($m3->invoke($mf, 'test'));
var_dump($m3->invoke($mf2, 'test'));

try {
$m3->invoke($of, 'test');
echo "No exception thrown\n";
} catch (ReflectionException $e) {
echo $e->getMessage() . "\n";
}

// Internal closures (first-class callable syntax) should also be validated
$vd = var_dump(...);
$pr = print_r(...);

$m4 = new ReflectionMethod($vd, '__invoke');
$m4->invoke($vd, 'internal closure OK');

// Cloned internal closure is a different object but same function - should work
$vd2 = clone $vd;
$m4->invoke($vd2, 'cloned internal closure OK');

// Different internal closure should throw
try {
$m4->invoke($pr, 'should not print');
echo "No exception thrown\n";
} catch (ReflectionException $e) {
echo $e->getMessage() . "\n";
}

// Cross-type: userland Closure to internal Closure's invoke should throw
try {
$m4->invoke($c1, 'should not print');
echo "No exception thrown\n";
} catch (ReflectionException $e) {
echo $e->getMessage() . "\n";
}

// Cross-type: internal Closure to userland Closure's invoke should throw
try {
$m->invoke($vd, 'should not print');
echo "No exception thrown\n";
} catch (ReflectionException $e) {
echo $e->getMessage() . "\n";
}
?>
--EXPECT--
c1: foo=FOO, bar=BAR
Expand All @@ -51,3 +105,11 @@ Given Closure is not the same as the reflected Closure
int(0)
int(1)
int(2)
string(13) "my_func: test"
string(13) "my_func: test"
Given Closure is not the same as the reflected Closure
string(19) "internal closure OK"
string(26) "cloned internal closure OK"
Given Closure is not the same as the reflected Closure
Given Closure is not the same as the reflected Closure
Given Closure is not the same as the reflected Closure
Loading