Skip to content

Commit e3f4882

Browse files
committed
Also exclude simple stateless closures
1 parent d445bf1 commit e3f4882

File tree

4 files changed

+116
-4
lines changed

4 files changed

+116
-4
lines changed

Zend/tests/gc/gc_051.phpt

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
--TEST--
2+
GC 051: Acyclic objects are not added to GC buffer
3+
--FILE--
4+
<?php
5+
6+
function test($x) {}
7+
8+
enum E {
9+
case A;
10+
case B;
11+
case C;
12+
}
13+
14+
#[AllowDynamicProperties]
15+
class AcyclicC {
16+
public int $a;
17+
public string $b;
18+
}
19+
20+
class CyclicC {
21+
public array $a;
22+
}
23+
24+
echo "Enums\n";
25+
test(E::A);
26+
test(E::B);
27+
test(E::C);
28+
var_dump(gc_status()['roots']);
29+
30+
echo "Acyclic object\n";
31+
$o = new AcyclicC;
32+
test($o);
33+
var_dump(gc_status()['roots']);
34+
unset($o);
35+
36+
echo "Cyclic object\n";
37+
$o = new CyclicC;
38+
test($o);
39+
var_dump(gc_status()['roots']);
40+
unset($o);
41+
42+
echo "Acyclic object with dynamic properties\n";
43+
$o = new AcyclicC;
44+
$o->dyn = 42;
45+
test($o);
46+
var_dump(gc_status()['roots']);
47+
unset($o);
48+
49+
echo "Stateless closure\n";
50+
$o = static function () {};
51+
test($o);
52+
var_dump(gc_status()['roots']);
53+
unset($o);
54+
55+
echo "Closure with bindings\n";
56+
$x = [];
57+
$o = static function () use ($x) {};
58+
unset($x);
59+
test($o);
60+
var_dump(gc_status()['roots']);
61+
unset($o);
62+
63+
echo "Closure with static vars\n";
64+
$o = static function () {
65+
static $x;
66+
};
67+
test($o);
68+
var_dump(gc_status()['roots']);
69+
unset($o);
70+
71+
echo "Non-static closure\n";
72+
$o = function () {};
73+
test($o);
74+
var_dump(gc_status()['roots']);
75+
unset($o);
76+
77+
echo "Generator\n";
78+
$c = static function () { yield; }; // Not collectable
79+
test($c);
80+
$o = $c(); // Collectable
81+
test($o);
82+
var_dump(gc_status()['roots']);
83+
unset($o);
84+
unset($c);
85+
86+
?>
87+
--EXPECT--
88+
Enums
89+
int(0)
90+
Acyclic object
91+
int(0)
92+
Cyclic object
93+
int(1)
94+
Acyclic object with dynamic properties
95+
int(1)
96+
Stateless closure
97+
int(0)
98+
Closure with bindings
99+
int(1)
100+
Closure with static vars
101+
int(1)
102+
Non-static closure
103+
int(1)
104+
Generator
105+
int(1)

Zend/zend_closures.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -733,8 +733,6 @@ void zend_register_closure_ce(void) /* {{{ */
733733
zend_ce_closure = register_class_Closure();
734734
zend_ce_closure->create_object = zend_closure_new;
735735
zend_ce_closure->default_object_handlers = &closure_handlers;
736-
/* FIXME: Potentially infer ZEND_ACC2_MAY_BE_CYCLIC during construction of
737-
* closure? static closures not binding by references can't be cyclic. */
738736

739737
memcpy(&closure_handlers, &std_object_handlers, sizeof(zend_object_handlers));
740738
closure_handlers.free_obj = zend_closure_free_storage;
@@ -864,6 +862,10 @@ ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_ent
864862
{
865863
zend_create_closure_ex(res, func, scope, called_scope, this_ptr,
866864
/* is_fake */ (func->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0);
865+
866+
if (!(func->common.fn_flags2 & ZEND_ACC2_MAY_BE_CYCLIC)) {
867+
GC_ADD_FLAGS(Z_OBJ_P(res), GC_NOT_COLLECTABLE);
868+
}
867869
}
868870

869871
ZEND_API void zend_create_fake_closure(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr) /* {{{ */

Zend/zend_compile.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5140,7 +5140,7 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg
51405140
* breaking for the generated call.
51415141
*/
51425142
if (callback->kind == ZEND_AST_CALL
5143-
&& callback->child[0]->kind == ZEND_AST_ZVAL
5143+
&& callback->child[0]->kind == ZEND_AST_ZVAL
51445144
&& Z_TYPE_P(zend_ast_get_zval(callback->child[0])) == IS_STRING
51455145
&& zend_string_equals_literal_ci(zend_ast_get_str(callback->child[0]), "assert")) {
51465146
return FAILURE;
@@ -8906,6 +8906,11 @@ static zend_op_array *zend_compile_func_decl_ex(
89068906
zend_do_extended_stmt(NULL);
89078907
zend_emit_final_return(false);
89088908

8909+
if ((decl->kind == ZEND_AST_CLOSURE || decl->kind == ZEND_AST_ARROW_FUNC)
8910+
&& (!(op_array->fn_flags & ZEND_ACC_STATIC) || op_array->static_variables)) {
8911+
op_array->fn_flags2 |= ZEND_ACC2_MAY_BE_CYCLIC;
8912+
}
8913+
89098914
pass_two(CG(active_op_array));
89108915
zend_oparray_context_end(&orig_oparray_context);
89118916

Zend/zend_compile.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ typedef struct _zend_oparray_context {
345345
/* ========================= | | | */
346346
/* | | | */
347347
/* Object may be the root of a cycle | | | */
348-
#define ZEND_ACC2_MAY_BE_CYCLIC (1 << 0) /* X | | | */
348+
#define ZEND_ACC2_MAY_BE_CYCLIC (1 << 0) /* X | X | | */
349349
/* | | | */
350350
/* Function Flags (unused: 30) | | | */
351351
/* ============== | | | */

0 commit comments

Comments
 (0)