Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 4 additions & 2 deletions Zend/zend_closures.c
Original file line number Diff line number Diff line change
Expand Up @@ -361,11 +361,12 @@ static zend_result zend_create_closure_from_callable(zval *return_value, zval *c

memset(&call, 0, sizeof(zend_internal_function));
call.type = ZEND_INTERNAL_FUNCTION;
call.fn_flags = mptr->common.fn_flags & ZEND_ACC_STATIC;
call.fn_flags = mptr->common.fn_flags & (ZEND_ACC_STATIC | ZEND_ACC_DEPRECATED);
call.handler = zend_closure_call_magic;
call.function_name = mptr->common.function_name;
call.scope = mptr->common.scope;
call.doc_comment = NULL;
call.attributes = mptr->common.attributes;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand, why I do not need to increase the refcount here: If I do, I get a leak.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call isn't a trampoline, and the function call is freed in zend_closure_free_storage. The attributes are ignored by zend_closure_free_storage. So you're counting on that the attributes of the original function outlive the closure instance.

Copy link
Member

@nielsdos nielsdos Feb 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I actually wonder why we refcount the attributes for trampolines in the first place. They always come from a real function, and real functions should outlive trampolines I think. Maybe I'm missing something...
EDIT: I'm running the test suite now with refcounting removed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems to work fine.


zend_free_trampoline(mptr);
mptr = (zend_function *) &call;
Expand Down Expand Up @@ -871,14 +872,15 @@ void zend_closure_from_frame(zval *return_value, zend_execute_data *call) { /* {

memset(&trampoline, 0, sizeof(zend_internal_function));
trampoline.type = ZEND_INTERNAL_FUNCTION;
trampoline.fn_flags = mptr->common.fn_flags & (ZEND_ACC_STATIC | ZEND_ACC_VARIADIC | ZEND_ACC_RETURN_REFERENCE);
trampoline.fn_flags = mptr->common.fn_flags & (ZEND_ACC_STATIC | ZEND_ACC_VARIADIC | ZEND_ACC_RETURN_REFERENCE | ZEND_ACC_DEPRECATED);
trampoline.handler = zend_closure_call_magic;
trampoline.function_name = mptr->common.function_name;
trampoline.scope = mptr->common.scope;
trampoline.doc_comment = NULL;
if (trampoline.fn_flags & ZEND_ACC_VARIADIC) {
trampoline.arg_info = trampoline_arg_info;
}
trampoline.attributes = mptr->common.attributes;

zend_free_trampoline(mptr);
mptr = (zend_function *) &trampoline;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
--TEST--
GH-17913: ReflectionClassConstant::isDeprecated() with __call() and __callStatic()
--FILE--
<?php

class Clazz {
#[\Deprecated]
function __call(string $name, array $params) {
}

#[\Deprecated("due to some reason")]
static function __callStatic(string $name, array $params) {
}
}

$foo = new Clazz;
$closure = Closure::fromCallable([$foo, 'test']);

$rc = new ReflectionFunction($closure);
var_dump($rc->getAttributes()[0]->newInstance());
var_dump($rc->isDeprecated());

$closure = $foo->test(...);

$rc = new ReflectionFunction($closure);
var_dump($rc->getAttributes()[0]->newInstance());
var_dump($rc->isDeprecated());

$closure = Closure::fromCallable('Clazz::test');

$rc = new ReflectionFunction($closure);
var_dump($rc->getAttributes()[0]->newInstance());
var_dump($rc->isDeprecated());

$closure = Clazz::test(...);

$rc = new ReflectionFunction($closure);
var_dump($rc->getAttributes()[0]->newInstance());
var_dump($rc->isDeprecated());

?>
--EXPECTF--
object(Deprecated)#%d (2) {
["message"]=>
NULL
["since"]=>
NULL
}
bool(true)
object(Deprecated)#%d (2) {
["message"]=>
NULL
["since"]=>
NULL
}
bool(true)
object(Deprecated)#%d (2) {
["message"]=>
string(18) "due to some reason"
["since"]=>
NULL
}
bool(true)
object(Deprecated)#%d (2) {
["message"]=>
string(18) "due to some reason"
["since"]=>
NULL
}
bool(true)
Loading