Skip to content

Commit 8094bd1

Browse files
authored
Make ReflectionGenerator::getFunction() legal after generator termination (#14167)
* Make `ReflectionGenerator::getFunction()` legal after generator termination * Expose the generator function name via `Generator::__debugInfo()` * Allow creating `ReflectionGenerator` after termination * Reorder `struct _zend_generator` to avoid a hole * Adjust `ext/reflection/tests/028.phpt` This is legal now. * Fix Generator Closure collection * Add test to verify the Closure dies with the generator * NEWS / UPGRADING
1 parent 14b92d5 commit 8094bd1

24 files changed

+367
-58
lines changed

NEWS

+2
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ PHP NEWS
207207
- Reflection:
208208
. Implement GH-12908 (Show attribute name/class in ReflectionAttribute dump).
209209
(nielsdos)
210+
. Make ReflectionGenerator::getFunction() legal after generator termination.
211+
(timwolla)
210212

211213
- SimpleXML:
212214
. Fixed bug GH-12192 (SimpleXML infinite loop when getName() is called

UPGRADING

+4
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,10 @@ PHP 8.4 UPGRADE NOTES
437437
. posix_isatty now sets the error number when the file descriptor/stream argument
438438
is invalid.
439439

440+
- Reflection:
441+
. ReflectionGenerator::getFunction() may now be called after the generator
442+
finished executing.
443+
440444
- Sockets:
441445
. Parameter $backlog of socket_create_listen() now has a default value of SOMAXCONN.
442446
Previously, it was 128.
+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
--TEST--
2+
Generators expose the underlying function name in __debugInfo().
3+
--FILE--
4+
<?php
5+
6+
function foo() {
7+
yield;
8+
}
9+
10+
$gens = [
11+
(new class() {
12+
function a() {
13+
yield from foo();
14+
}
15+
})->a(),
16+
(function() {
17+
yield;
18+
})(),
19+
foo(),
20+
];
21+
22+
foreach ($gens as $gen) {
23+
echo "Before:", PHP_EOL;
24+
var_dump($gen);
25+
26+
foreach ($gen as $dummy) {
27+
echo "Inside:", PHP_EOL;
28+
var_dump($gen);
29+
}
30+
31+
echo "After:", PHP_EOL;
32+
33+
var_dump($gen);
34+
}
35+
36+
?>
37+
--EXPECTF--
38+
Before:
39+
object(Generator)#%d (1) {
40+
["function"]=>
41+
string(%d) "class@anonymous%s::a"
42+
}
43+
Inside:
44+
object(Generator)#%d (1) {
45+
["function"]=>
46+
string(%d) "class@anonymous%s::a"
47+
}
48+
After:
49+
object(Generator)#%d (1) {
50+
["function"]=>
51+
string(%d) "class@anonymous%s::a"
52+
}
53+
Before:
54+
object(Generator)#%d (1) {
55+
["function"]=>
56+
string(%d) "{closure:%s:%d}"
57+
}
58+
Inside:
59+
object(Generator)#%d (1) {
60+
["function"]=>
61+
string(%d) "{closure:%s:%d}"
62+
}
63+
After:
64+
object(Generator)#%d (1) {
65+
["function"]=>
66+
string(%d) "{closure:%s:%d}"
67+
}
68+
Before:
69+
object(Generator)#%d (1) {
70+
["function"]=>
71+
string(3) "foo"
72+
}
73+
Inside:
74+
object(Generator)#%d (1) {
75+
["function"]=>
76+
string(3) "foo"
77+
}
78+
After:
79+
object(Generator)#%d (1) {
80+
["function"]=>
81+
string(3) "foo"
82+
}

Zend/tests/generators/gc_with_yield_from.phpt

+13-5
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,29 @@ gc_collect_cycles();
2727
print "end\n";
2828

2929
?>
30-
--EXPECT--
30+
--EXPECTF--
3131
int(1)
3232
collect
3333
array(4) {
3434
[0]=>
35-
object(Generator)#1 (0) {
35+
object(Generator)#%d (1) {
36+
["function"]=>
37+
string(3) "gen"
3638
}
3739
[1]=>
38-
object(Generator)#2 (0) {
40+
object(Generator)#%d (1) {
41+
["function"]=>
42+
string(3) "gen"
3943
}
4044
[2]=>
41-
object(Generator)#3 (0) {
45+
object(Generator)#%d (1) {
46+
["function"]=>
47+
string(3) "gen"
4248
}
4349
[3]=>
44-
object(Generator)#4 (0) {
50+
object(Generator)#%d (1) {
51+
["function"]=>
52+
string(4) "root"
4553
}
4654
}
4755
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
--TEST--
2+
The Closure object of a generator is freed when the generator is freed.
3+
--FILE--
4+
<?php
5+
6+
$genFactory = function() {
7+
yield 1;
8+
yield 2;
9+
yield 3;
10+
};
11+
12+
$r = WeakReference::create($genFactory);
13+
$generator = $genFactory();
14+
unset($genFactory);
15+
16+
var_dump($r->get());
17+
18+
foreach ($generator as $value) var_dump($value);
19+
20+
var_dump($r->get());
21+
22+
unset($generator);
23+
24+
var_dump($r->get());
25+
26+
?>
27+
--EXPECTF--
28+
object(Closure)#%d (3) {
29+
["name"]=>
30+
string(%d) "{closure:%s:%d}"
31+
["file"]=>
32+
string(%d) "%s"
33+
["line"]=>
34+
int(%d)
35+
}
36+
int(1)
37+
int(2)
38+
int(3)
39+
object(Closure)#%d (3) {
40+
["name"]=>
41+
string(%d) "{closure:%s:%d}"
42+
["file"]=>
43+
string(%d) "%s"
44+
["line"]=>
45+
int(%d)
46+
}
47+
NULL

Zend/tests/generators/generator_return_without_value.phpt

+12-4
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,19 @@ var_dump(gen4());
3333

3434
?>
3535
--EXPECTF--
36-
object(Generator)#%d (0) {
36+
object(Generator)#%d (1) {
37+
["function"]=>
38+
string(3) "gen"
3739
}
38-
object(Generator)#%d (0) {
40+
object(Generator)#%d (1) {
41+
["function"]=>
42+
string(4) "gen2"
3943
}
40-
object(Generator)#%d (0) {
44+
object(Generator)#%d (1) {
45+
["function"]=>
46+
string(4) "gen3"
4147
}
42-
object(Generator)#%d (0) {
48+
object(Generator)#%d (1) {
49+
["function"]=>
50+
string(4) "gen4"
4351
}

Zend/tests/return_types/generators001.phpt

+14
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,30 @@ var_dump(
4242
?>
4343
--EXPECTF--
4444
object(Generator)#%d (%d) {
45+
["function"]=>
46+
string(5) "test1"
4547
}
4648
object(Generator)#%d (%d) {
49+
["function"]=>
50+
string(5) "test2"
4751
}
4852
object(Generator)#%d (%d) {
53+
["function"]=>
54+
string(5) "test3"
4955
}
5056
object(Generator)#%d (%d) {
57+
["function"]=>
58+
string(5) "test4"
5159
}
5260
object(Generator)#%d (%d) {
61+
["function"]=>
62+
string(5) "test5"
5363
}
5464
object(Generator)#%d (%d) {
65+
["function"]=>
66+
string(5) "test6"
5567
}
5668
object(Generator)#%d (%d) {
69+
["function"]=>
70+
string(5) "test7"
5771
}

Zend/tests/return_types/generators005.phpt

+2
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ var_dump($some->getIterator());
1919
?>
2020
--EXPECTF--
2121
object(Generator)#%d (%d) {
22+
["function"]=>
23+
string(27) "SomeCollection::getIterator"
2224
}

Zend/tests/type_declarations/iterable/iterable_001.phpt

+3-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ array(3) {
3232
[2]=>
3333
int(3)
3434
}
35-
object(Generator)#1 (0) {
35+
object(Generator)#%d (1) {
36+
["function"]=>
37+
string(3) "gen"
3638
}
3739
object(ArrayIterator)#1 (1) {
3840
["storage":"ArrayIterator":private]=>

Zend/tests/type_declarations/iterable/iterable_003.phpt

+4-2
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ try {
2424
}
2525

2626
?>
27-
--EXPECT--
27+
--EXPECTF--
2828
array(0) {
2929
}
30-
object(Generator)#2 (0) {
30+
object(Generator)#%d (1) {
31+
["function"]=>
32+
string(17) "{closure:bar():7}"
3133
}
3234
baz(): Return value must be of type Traversable|array, int returned

Zend/tests/type_declarations/union_types/generator_return_containing_extra_types.phpt

+4-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ class C implements I {
1616
var_dump((new C)->test());
1717

1818
?>
19-
--EXPECT--
20-
object(Generator)#2 (0) {
19+
--EXPECTF--
20+
object(Generator)#%d (1) {
21+
["function"]=>
22+
string(7) "C::test"
2123
}

Zend/zend_generators.c

+46-9
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,6 @@ ZEND_API void zend_generator_close(zend_generator *generator, bool finished_exec
166166
zend_generator_cleanup_unfinished_execution(generator, execute_data, 0);
167167
}
168168

169-
/* Free closure object */
170-
if (EX_CALL_INFO() & ZEND_CALL_CLOSURE) {
171-
OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func)));
172-
}
173-
174169
efree(execute_data);
175170
}
176171
}
@@ -330,6 +325,10 @@ static void zend_generator_free_storage(zend_object *object) /* {{{ */
330325

331326
zend_generator_close(generator, 0);
332327

328+
if (generator->func && (generator->func->common.fn_flags & ZEND_ACC_CLOSURE)) {
329+
OBJ_RELEASE(ZEND_CLOSURE_OBJECT(generator->func));
330+
}
331+
333332
/* we can't immediately free them in zend_generator_close() else yield from won't be able to fetch it */
334333
zval_ptr_dtor(&generator->value);
335334
zval_ptr_dtor(&generator->key);
@@ -354,10 +353,19 @@ static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int *
354353
zend_execute_data *call = NULL;
355354

356355
if (!execute_data) {
357-
/* If the generator has been closed, it can only hold on to three values: The value, key
358-
* and retval. These three zvals are stored sequentially starting at &generator->value. */
359-
*table = &generator->value;
360-
*n = 3;
356+
if (UNEXPECTED(generator->func->common.fn_flags & ZEND_ACC_CLOSURE)) {
357+
zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
358+
zend_get_gc_buffer_add_zval(gc_buffer, &generator->value);
359+
zend_get_gc_buffer_add_zval(gc_buffer, &generator->key);
360+
zend_get_gc_buffer_add_zval(gc_buffer, &generator->retval);
361+
zend_get_gc_buffer_add_obj(gc_buffer, ZEND_CLOSURE_OBJECT(generator->func));
362+
zend_get_gc_buffer_use(gc_buffer, table, n);
363+
} else {
364+
/* If the non-closure generator has been closed, it can only hold on to three values: The value, key
365+
* and retval. These three zvals are stored sequentially starting at &generator->value. */
366+
*table = &generator->value;
367+
*n = 3;
368+
}
361369
return NULL;
362370
}
363371

@@ -1010,6 +1018,35 @@ ZEND_METHOD(Generator, getReturn)
10101018
}
10111019
/* }}} */
10121020

1021+
ZEND_METHOD(Generator, __debugInfo)
1022+
{
1023+
zend_generator *generator;
1024+
1025+
ZEND_PARSE_PARAMETERS_NONE();
1026+
1027+
generator = (zend_generator *) Z_OBJ_P(ZEND_THIS);
1028+
1029+
array_init(return_value);
1030+
1031+
zend_function *func = generator->func;
1032+
1033+
zval val;
1034+
if (func->common.scope) {
1035+
zend_string *class_name = func->common.scope->name;
1036+
zend_string *func_name = func->common.function_name;
1037+
zend_string *combined = zend_string_concat3(
1038+
ZSTR_VAL(class_name), ZSTR_LEN(class_name),
1039+
"::", strlen("::"),
1040+
ZSTR_VAL(func_name), ZSTR_LEN(func_name)
1041+
);
1042+
ZVAL_NEW_STR(&val, combined);
1043+
} else {
1044+
ZVAL_STR_COPY(&val, func->common.function_name);
1045+
}
1046+
1047+
zend_hash_update(Z_ARR_P(return_value), ZSTR_KNOWN(ZEND_STR_FUNCTION), &val);
1048+
}
1049+
10131050
/* get_iterator implementation */
10141051

10151052
static void zend_generator_iterator_dtor(zend_object_iterator *iterator) /* {{{ */

Zend/zend_generators.h

+4
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ struct _zend_generator {
8888
/* Fake execute_data for stacktraces */
8989
zend_execute_data execute_fake;
9090

91+
/* The underlying function, equivalent to execute_data->func while
92+
* the generator is alive. */
93+
zend_function *func;
94+
9195
/* ZEND_GENERATOR_* flags */
9296
uint8_t flags;
9397
};

Zend/zend_generators.stub.php

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public function send(mixed $value): mixed {}
2323
public function throw(Throwable $exception): mixed {}
2424

2525
public function getReturn(): mixed {}
26+
27+
public function __debugInfo(): array {}
2628
}
2729

2830
class ClosedGeneratorException extends Exception

0 commit comments

Comments
 (0)