Skip to content

Make ReflectionGenerator::getFunction() legal after generator termination #14167

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 21, 2024
Merged
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
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ PHP NEWS
- Reflection:
. Implement GH-12908 (Show attribute name/class in ReflectionAttribute dump).
(nielsdos)
. Make ReflectionGenerator::getFunction() legal after generator termination.
(timwolla)

- SimpleXML:
. Fixed bug GH-12192 (SimpleXML infinite loop when getName() is called
Expand Down
4 changes: 4 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,10 @@ PHP 8.4 UPGRADE NOTES
. posix_isatty now sets the error number when the file descriptor/stream argument
is invalid.

- Reflection:
. ReflectionGenerator::getFunction() may now be called after the generator
finished executing.

- Sockets:
. Parameter $backlog of socket_create_listen() now has a default value of SOMAXCONN.
Previously, it was 128.
Expand Down
82 changes: 82 additions & 0 deletions Zend/tests/generators/debugInfo_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
--TEST--
Generators expose the underlying function name in __debugInfo().
--FILE--
<?php

function foo() {
yield;
}

$gens = [
(new class() {
function a() {
yield from foo();
}
})->a(),
(function() {
yield;
})(),
foo(),
];

foreach ($gens as $gen) {
echo "Before:", PHP_EOL;
var_dump($gen);

foreach ($gen as $dummy) {
echo "Inside:", PHP_EOL;
var_dump($gen);
}

echo "After:", PHP_EOL;

var_dump($gen);
}

?>
--EXPECTF--
Before:
object(Generator)#%d (1) {
["function"]=>
string(%d) "class@anonymous%s::a"
}
Inside:
object(Generator)#%d (1) {
["function"]=>
string(%d) "class@anonymous%s::a"
}
After:
object(Generator)#%d (1) {
["function"]=>
string(%d) "class@anonymous%s::a"
}
Before:
object(Generator)#%d (1) {
["function"]=>
string(%d) "{closure:%s:%d}"
}
Inside:
object(Generator)#%d (1) {
["function"]=>
string(%d) "{closure:%s:%d}"
}
After:
object(Generator)#%d (1) {
["function"]=>
string(%d) "{closure:%s:%d}"
}
Before:
object(Generator)#%d (1) {
["function"]=>
string(3) "foo"
}
Inside:
object(Generator)#%d (1) {
["function"]=>
string(3) "foo"
}
After:
object(Generator)#%d (1) {
["function"]=>
string(3) "foo"
}
18 changes: 13 additions & 5 deletions Zend/tests/generators/gc_with_yield_from.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,29 @@ gc_collect_cycles();
print "end\n";

?>
--EXPECT--
--EXPECTF--
int(1)
collect
array(4) {
[0]=>
object(Generator)#1 (0) {
object(Generator)#%d (1) {
["function"]=>
string(3) "gen"
}
[1]=>
object(Generator)#2 (0) {
object(Generator)#%d (1) {
["function"]=>
string(3) "gen"
}
[2]=>
object(Generator)#3 (0) {
object(Generator)#%d (1) {
["function"]=>
string(3) "gen"
}
[3]=>
object(Generator)#4 (0) {
object(Generator)#%d (1) {
["function"]=>
string(4) "root"
}
}
end
47 changes: 47 additions & 0 deletions Zend/tests/generators/generator_closure_collection.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
--TEST--
The Closure object of a generator is freed when the generator is freed.
--FILE--
<?php

$genFactory = function() {
yield 1;
yield 2;
yield 3;
};

$r = WeakReference::create($genFactory);
$generator = $genFactory();
unset($genFactory);

var_dump($r->get());

foreach ($generator as $value) var_dump($value);

var_dump($r->get());

unset($generator);

var_dump($r->get());

?>
--EXPECTF--
object(Closure)#%d (3) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(%d)
}
int(1)
int(2)
int(3)
object(Closure)#%d (3) {
["name"]=>
string(%d) "{closure:%s:%d}"
["file"]=>
string(%d) "%s"
["line"]=>
int(%d)
}
NULL
16 changes: 12 additions & 4 deletions Zend/tests/generators/generator_return_without_value.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,19 @@ var_dump(gen4());

?>
--EXPECTF--
object(Generator)#%d (0) {
object(Generator)#%d (1) {
["function"]=>
string(3) "gen"
}
object(Generator)#%d (0) {
object(Generator)#%d (1) {
["function"]=>
string(4) "gen2"
}
object(Generator)#%d (0) {
object(Generator)#%d (1) {
["function"]=>
string(4) "gen3"
}
object(Generator)#%d (0) {
object(Generator)#%d (1) {
["function"]=>
string(4) "gen4"
}
14 changes: 14 additions & 0 deletions Zend/tests/return_types/generators001.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,30 @@ var_dump(
?>
--EXPECTF--
object(Generator)#%d (%d) {
["function"]=>
string(5) "test1"
}
object(Generator)#%d (%d) {
["function"]=>
string(5) "test2"
}
object(Generator)#%d (%d) {
["function"]=>
string(5) "test3"
}
object(Generator)#%d (%d) {
["function"]=>
string(5) "test4"
}
object(Generator)#%d (%d) {
["function"]=>
string(5) "test5"
}
object(Generator)#%d (%d) {
["function"]=>
string(5) "test6"
}
object(Generator)#%d (%d) {
["function"]=>
string(5) "test7"
}
2 changes: 2 additions & 0 deletions Zend/tests/return_types/generators005.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ var_dump($some->getIterator());
?>
--EXPECTF--
object(Generator)#%d (%d) {
["function"]=>
string(27) "SomeCollection::getIterator"
}
4 changes: 3 additions & 1 deletion Zend/tests/type_declarations/iterable/iterable_001.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ array(3) {
[2]=>
int(3)
}
object(Generator)#1 (0) {
object(Generator)#%d (1) {
["function"]=>
string(3) "gen"
}
object(ArrayIterator)#1 (1) {
["storage":"ArrayIterator":private]=>
Expand Down
6 changes: 4 additions & 2 deletions Zend/tests/type_declarations/iterable/iterable_003.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ try {
}

?>
--EXPECT--
--EXPECTF--
array(0) {
}
object(Generator)#2 (0) {
object(Generator)#%d (1) {
["function"]=>
string(17) "{closure:bar():7}"
}
baz(): Return value must be of type Traversable|array, int returned
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class C implements I {
var_dump((new C)->test());

?>
--EXPECT--
object(Generator)#2 (0) {
--EXPECTF--
object(Generator)#%d (1) {
["function"]=>
string(7) "C::test"
}
55 changes: 46 additions & 9 deletions Zend/zend_generators.c
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,6 @@ ZEND_API void zend_generator_close(zend_generator *generator, bool finished_exec
zend_generator_cleanup_unfinished_execution(generator, execute_data, 0);
}

/* Free closure object */
if (EX_CALL_INFO() & ZEND_CALL_CLOSURE) {
OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func)));
}

efree(execute_data);
}
}
Expand Down Expand Up @@ -330,6 +325,10 @@ static void zend_generator_free_storage(zend_object *object) /* {{{ */

zend_generator_close(generator, 0);

if (generator->func && (generator->func->common.fn_flags & ZEND_ACC_CLOSURE)) {
OBJ_RELEASE(ZEND_CLOSURE_OBJECT(generator->func));
}

/* we can't immediately free them in zend_generator_close() else yield from won't be able to fetch it */
zval_ptr_dtor(&generator->value);
zval_ptr_dtor(&generator->key);
Expand All @@ -354,10 +353,19 @@ static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int *
zend_execute_data *call = NULL;

if (!execute_data) {
/* If the generator has been closed, it can only hold on to three values: The value, key
* and retval. These three zvals are stored sequentially starting at &generator->value. */
*table = &generator->value;
*n = 3;
if (UNEXPECTED(generator->func->common.fn_flags & ZEND_ACC_CLOSURE)) {
zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
zend_get_gc_buffer_add_zval(gc_buffer, &generator->value);
zend_get_gc_buffer_add_zval(gc_buffer, &generator->key);
zend_get_gc_buffer_add_zval(gc_buffer, &generator->retval);
zend_get_gc_buffer_add_obj(gc_buffer, ZEND_CLOSURE_OBJECT(generator->func));
zend_get_gc_buffer_use(gc_buffer, table, n);
} else {
/* If the non-closure generator has been closed, it can only hold on to three values: The value, key
* and retval. These three zvals are stored sequentially starting at &generator->value. */
*table = &generator->value;
*n = 3;
}
return NULL;
}

Expand Down Expand Up @@ -1010,6 +1018,35 @@ ZEND_METHOD(Generator, getReturn)
}
/* }}} */

ZEND_METHOD(Generator, __debugInfo)
{
zend_generator *generator;

ZEND_PARSE_PARAMETERS_NONE();

generator = (zend_generator *) Z_OBJ_P(ZEND_THIS);

array_init(return_value);

zend_function *func = generator->func;

zval val;
if (func->common.scope) {
zend_string *class_name = func->common.scope->name;
zend_string *func_name = func->common.function_name;
zend_string *combined = zend_string_concat3(
ZSTR_VAL(class_name), ZSTR_LEN(class_name),
"::", strlen("::"),
ZSTR_VAL(func_name), ZSTR_LEN(func_name)
);
ZVAL_NEW_STR(&val, combined);
} else {
ZVAL_STR_COPY(&val, func->common.function_name);
}

zend_hash_update(Z_ARR_P(return_value), ZSTR_KNOWN(ZEND_STR_FUNCTION), &val);
}

/* get_iterator implementation */

static void zend_generator_iterator_dtor(zend_object_iterator *iterator) /* {{{ */
Expand Down
4 changes: 4 additions & 0 deletions Zend/zend_generators.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ struct _zend_generator {
/* Fake execute_data for stacktraces */
zend_execute_data execute_fake;

/* The underlying function, equivalent to execute_data->func while
* the generator is alive. */
zend_function *func;

/* ZEND_GENERATOR_* flags */
uint8_t flags;
};
Expand Down
2 changes: 2 additions & 0 deletions Zend/zend_generators.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public function send(mixed $value): mixed {}
public function throw(Throwable $exception): mixed {}

public function getReturn(): mixed {}

public function __debugInfo(): array {}
}

class ClosedGeneratorException extends Exception
Expand Down
Loading
Loading