diff --git a/Zend/tests/gc/gc_045.phpt b/Zend/tests/gc/gc_045.phpt index 1762be5db1ad9..cbb1fc71e79f9 100644 --- a/Zend/tests/gc/gc_045.phpt +++ b/Zend/tests/gc/gc_045.phpt @@ -11,6 +11,9 @@ class GlobalData class Value { + /* Force object to be added to GC, even though it is acyclic. */ + public $dummy; + public function __destruct() { new Bar(); @@ -19,6 +22,9 @@ class Value class Bar { + /* Force object to be added to GC, even though it is acyclic. */ + public $dummy; + public function __construct() { GlobalData::$bar = $this; diff --git a/Zend/tests/gc/gc_051.phpt b/Zend/tests/gc/gc_051.phpt new file mode 100644 index 0000000000000..e4e0880e0ac7f --- /dev/null +++ b/Zend/tests/gc/gc_051.phpt @@ -0,0 +1,107 @@ +--TEST-- +GC 051: Acyclic objects are not added to GC buffer +--FILE-- +dyn = 42; +test($o); +var_dump(gc_status()['roots']); +unset($o); + +echo "Stateless closure\n"; +$o = static function () {}; +test($o); +var_dump(gc_status()['roots']); +unset($o); + +echo "Closure with bindings\n"; +$x = []; +$o = static function () use ($x) {}; +unset($x); +test($o); +var_dump(gc_status()['roots']); +unset($o); + +echo "Closure with static vars\n"; +$o = static function () { + static $x; +}; +test($o); +var_dump(gc_status()['roots']); +unset($o); + +echo "Non-static closure\n"; +$o = function () {}; +test($o); +var_dump(gc_status()['roots']); +unset($o); + +echo "Generator\n"; +$c = static function () { yield; }; // Not collectable +test($c); +$o = $c(); // Collectable +test($o); +var_dump(gc_status()['roots']); +unset($o); +unset($c); + +?> +--EXPECT-- +Enums +int(0) +Acyclic object +int(0) +Cyclic object +int(1) +Acyclic object with dynamic properties +int(1) +Stateless closure +int(0) +Closure with bindings +int(1) +Closure with static vars +int(1) +Non-static closure +int(1) +Generator +int(1) diff --git a/Zend/tests/weakrefs/gh10043-008.phpt b/Zend/tests/weakrefs/gh10043-008.phpt index f39c2ddbe1c2e..91645b8ff36b0 100644 --- a/Zend/tests/weakrefs/gh10043-008.phpt +++ b/Zend/tests/weakrefs/gh10043-008.phpt @@ -5,6 +5,9 @@ Self-referencing map entry GC - 008 class Canary extends stdClass { + /* Force object to be added to GC, even though it is acyclic. */ + public $dummy; + public function __construct(public string $name) { } diff --git a/Zend/zend_API.c b/Zend/zend_API.c index e72043ead5870..aa696d9f60011 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1767,6 +1767,7 @@ ZEND_API void object_properties_load(zend_object *object, const HashTable *prope ZSTR_VAL(object->ce->name), property_info != ZEND_WRONG_PROPERTY_INFO ? zend_get_unmangled_property_name(key): ""); } + GC_DEL_FLAGS(object, GC_NOT_COLLECTABLE); prop = zend_hash_update(zend_std_get_properties_ex(object), key, prop); zval_add_ref(prop); } @@ -1779,6 +1780,7 @@ ZEND_API void object_properties_load(zend_object *object, const HashTable *prope ZSTR_VAL(object->ce->name), h); } + GC_DEL_FLAGS(object, GC_NOT_COLLECTABLE); prop = zend_hash_index_update(zend_std_get_properties_ex(object), h, prop); zval_add_ref(prop); } @@ -2391,6 +2393,8 @@ ZEND_API zend_result zend_startup_module_ex(zend_module_entry *module) /* {{{ */ } module->module_started = 1; + uint32_t prev_class_count = zend_hash_num_elements(CG(class_table)); + /* Check module dependencies */ if (module->deps) { const zend_module_dep *dep = module->deps; @@ -2433,6 +2437,22 @@ ZEND_API zend_result zend_startup_module_ex(zend_module_entry *module) /* {{{ */ } EG(current_module) = NULL; } + + /* Mark classes with custom get_gc handler as potentially cyclic, even if + * their properties don't indicate so. */ + if (prev_class_count != zend_hash_num_elements(CG(class_table))) { + Bucket *p; + ZEND_HASH_MAP_FOREACH_BUCKET_FROM(CG(class_table), p, prev_class_count) { + zend_class_entry *ce = Z_PTR(p->val); + if ((ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES) + || ce->create_object + || ce->default_object_handlers->get_gc != zend_std_get_gc + || ce->default_object_handlers->get_properties != zend_std_get_properties) { + ce->ce_flags2 |= ZEND_ACC2_MAY_BE_CYCLIC; + } + } ZEND_HASH_FOREACH_END(); + } + return SUCCESS; } /* }}} */ @@ -4414,6 +4434,27 @@ static zend_always_inline bool is_persistent_class(const zend_class_entry *ce) { && ce->info.internal.module->type == MODULE_PERSISTENT; } +static bool zend_type_may_be_cyclic(zend_type type) +{ + if (!ZEND_TYPE_IS_SET(type)) { + return true; + } + + if (!ZEND_TYPE_IS_COMPLEX(type)) { + return ZEND_TYPE_PURE_MASK(type) & (MAY_BE_OBJECT|MAY_BE_ARRAY); + } else if (ZEND_TYPE_IS_UNION(type)) { + const zend_type *list_type; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) { + if (zend_type_may_be_cyclic(*list_type)) { + return true; + } + } ZEND_TYPE_LIST_FOREACH_END(); + return false; + } + + return true; +} + ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment, zend_type type) /* {{{ */ { zend_property_info *property_info, *property_info_ptr; @@ -4426,6 +4467,12 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z } } + if (!(access_type & ZEND_ACC_STATIC) + && !(ce->ce_flags2 & ZEND_ACC2_MAY_BE_CYCLIC) + && zend_type_may_be_cyclic(type)) { + ce->ce_flags2 |= ZEND_ACC2_MAY_BE_CYCLIC; + } + if (ce->type == ZEND_INTERNAL_CLASS) { property_info = pemalloc(sizeof(zend_property_info), 1); } else { diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index cca69985a0dfe..d0479428d67ff 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -862,6 +862,10 @@ ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_ent { zend_create_closure_ex(res, func, scope, called_scope, this_ptr, /* is_fake */ (func->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0); + + if (!(func->common.fn_flags2 & ZEND_ACC2_MAY_BE_CYCLIC)) { + GC_ADD_FLAGS(Z_OBJ_P(res), GC_NOT_COLLECTABLE); + } } ZEND_API void zend_create_fake_closure(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr) /* {{{ */ diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 23db72bb4fda1..c92c401e348b8 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5140,7 +5140,7 @@ static zend_result zend_compile_func_array_map(znode *result, zend_ast_list *arg * breaking for the generated call. */ if (callback->kind == ZEND_AST_CALL - && callback->child[0]->kind == ZEND_AST_ZVAL + && callback->child[0]->kind == ZEND_AST_ZVAL && Z_TYPE_P(zend_ast_get_zval(callback->child[0])) == IS_STRING && zend_string_equals_literal_ci(zend_ast_get_str(callback->child[0]), "assert")) { return FAILURE; @@ -8906,6 +8906,11 @@ static zend_op_array *zend_compile_func_decl_ex( zend_do_extended_stmt(NULL); zend_emit_final_return(false); + if ((decl->kind == ZEND_AST_CLOSURE || decl->kind == ZEND_AST_ARROW_FUNC) + && (!(op_array->fn_flags & ZEND_ACC_STATIC) || op_array->static_variables)) { + op_array->fn_flags2 |= ZEND_ACC2_MAY_BE_CYCLIC; + } + pass_two(CG(active_op_array)); zend_oparray_context_end(&orig_oparray_context); diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 587ae485ec821..9f5faa2a7e216 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -341,10 +341,11 @@ typedef struct _zend_oparray_context { /* Class cannot be serialized or unserialized | | | */ #define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */ /* | | | */ -/* Class Flags 2 (ce_flags2) (unused: 0-31) | | | */ +/* Class Flags 2 (ce_flags2) (unused: 1-31) | | | */ /* ========================= | | | */ /* | | | */ -/* #define ZEND_ACC2_EXAMPLE (1 << 0) X | | | */ +/* Object may be the root of a cycle | | | */ +#define ZEND_ACC2_MAY_BE_CYCLIC (1 << 0) /* X | X | | */ /* | | | */ /* Function Flags (unused: 30) | | | */ /* ============== | | | */ diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index fc0c9fc0634af..4ac0fc82710db 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -251,6 +251,9 @@ #define GC_FETCH_NEXT_UNUSED() \ gc_fetch_next_unused() +#define GC_COLLECTABLE(ref) \ + (!(GC_TYPE_INFO(ref) & (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT))) + ZEND_API int (*gc_collect_cycles)(void); /* The type of a root buffer entry. @@ -864,7 +867,7 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack) zval *entry = (zval*) Z_PTR_P(zv); zval *weakmap = zv+1; ZEND_ASSERT(Z_REFCOUNTED_P(weakmap)); - if (Z_OPT_COLLECTABLE_P(entry)) { + if (Z_OPT_COLLECTABLE_P(entry) && GC_COLLECTABLE(Z_COUNTED_P(entry))) { GC_UNSET_FROM_WEAKMAP_KEY(entry); if (GC_REF_CHECK_COLOR(Z_COUNTED_P(weakmap), GC_GREY)) { /* Weakmap was scanned in gc_mark_roots, we must @@ -901,7 +904,7 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack) ZEND_ASSERT(Z_TYPE_P(zv+1) == IS_PTR); zval *key = zv; zval *entry = (zval*) Z_PTR_P(zv+1); - if (Z_OPT_COLLECTABLE_P(entry)) { + if (Z_OPT_COLLECTABLE_P(entry) && GC_COLLECTABLE(Z_COUNTED_P(entry))) { GC_UNSET_FROM_WEAKMAP(entry); if (GC_REF_CHECK_COLOR(Z_COUNTED_P(key), GC_GREY)) { /* Key was scanned in gc_mark_roots, we must @@ -934,12 +937,12 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack) ht = obj->handlers->get_gc(obj, &table, &len); n = len; zv = table; - if (UNEXPECTED(ht)) { + if (UNEXPECTED(ht) && GC_COLLECTABLE(ht)) { GC_ADDREF(ht); if (!GC_REF_CHECK_COLOR(ht, GC_BLACK)) { GC_REF_SET_BLACK(ht); for (; n != 0; n--) { - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) { @@ -955,14 +958,14 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack) handle_zvals: for (; n != 0; n--) { - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) { GC_REF_SET_BLACK(ref); zv++; while (--n) { - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { zend_refcounted *ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) { @@ -994,7 +997,7 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack) if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) { @@ -1005,7 +1008,7 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack) if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { zend_refcounted *ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) { @@ -1021,7 +1024,7 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack) p++; } } else if (GC_TYPE(ref) == IS_REFERENCE) { - if (Z_COLLECTABLE(((zend_reference*)ref)->val)) { + if (Z_COLLECTABLE(((zend_reference*)ref)->val) && GC_COLLECTABLE(Z_COUNTED(((zend_reference*)ref)->val))) { ref = Z_COUNTED(((zend_reference*)ref)->val); GC_ADDREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) { @@ -1067,7 +1070,7 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack) zval *entry = (zval*) Z_PTR_P(zv); zval *weakmap = zv+1; ZEND_ASSERT(Z_REFCOUNTED_P(weakmap)); - if (Z_COLLECTABLE_P(entry)) { + if (Z_COLLECTABLE_P(entry) && GC_COLLECTABLE(Z_COUNTED_P(entry))) { GC_SET_FROM_WEAKMAP_KEY(entry); ref = Z_COUNTED_P(entry); /* Only DELREF if the contribution from the weakmap has @@ -1091,7 +1094,7 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack) for (; n != 0; n--) { ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR); zval *entry = (zval*) Z_PTR_P(zv); - if (Z_COLLECTABLE_P(entry)) { + if (Z_COLLECTABLE_P(entry) && GC_COLLECTABLE(Z_COUNTED_P(entry))) { GC_SET_FROM_WEAKMAP(entry); ref = Z_COUNTED_P(entry); /* Only DELREF if the contribution from the weakmap key @@ -1112,12 +1115,12 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack) ht = obj->handlers->get_gc(obj, &table, &len); n = len; zv = table; - if (UNEXPECTED(ht)) { + if (UNEXPECTED(ht) && GC_COLLECTABLE(ht)) { GC_DELREF(ht); if (!GC_REF_CHECK_COLOR(ht, GC_GREY)) { GC_REF_SET_COLOR(ht, GC_GREY); for (; n != 0; n--) { - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); GC_DELREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) { @@ -1132,14 +1135,14 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack) } handle_zvals: for (; n != 0; n--) { - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); GC_DELREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) { GC_REF_SET_COLOR(ref, GC_GREY); zv++; while (--n) { - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { zend_refcounted *ref = Z_COUNTED_P(zv); GC_DELREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) { @@ -1171,7 +1174,7 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack) if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); GC_DELREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) { @@ -1182,7 +1185,7 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack) if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { zend_refcounted *ref = Z_COUNTED_P(zv); GC_DELREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) { @@ -1198,7 +1201,7 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack) p++; } } else if (GC_TYPE(ref) == IS_REFERENCE) { - if (Z_COLLECTABLE(((zend_reference*)ref)->val)) { + if (Z_COLLECTABLE(((zend_reference*)ref)->val) && GC_COLLECTABLE(Z_COUNTED(((zend_reference*)ref)->val))) { ref = Z_COUNTED(((zend_reference*)ref)->val); GC_DELREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) { @@ -1318,7 +1321,7 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack) for (; n != 0; n--) { ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR); zval *entry = (zval*) Z_PTR_P(zv); - if (Z_OPT_COLLECTABLE_P(entry)) { + if (Z_OPT_COLLECTABLE_P(entry) && GC_COLLECTABLE(Z_COUNTED_P(entry))) { ref = Z_COUNTED_P(entry); if (GC_REF_CHECK_COLOR(ref, GC_GREY)) { GC_REF_SET_COLOR(ref, GC_WHITE); @@ -1332,12 +1335,12 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack) ht = obj->handlers->get_gc(obj, &table, &len); n = len; zv = table; - if (UNEXPECTED(ht)) { + if (UNEXPECTED(ht) && GC_COLLECTABLE(ht)) { if (GC_REF_CHECK_COLOR(ht, GC_GREY)) { GC_REF_SET_COLOR(ht, GC_WHITE); GC_STACK_PUSH((zend_refcounted *) ht); for (; n != 0; n--) { - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); if (GC_REF_CHECK_COLOR(ref, GC_GREY)) { GC_REF_SET_COLOR(ref, GC_WHITE); @@ -1352,13 +1355,13 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack) handle_zvals: for (; n != 0; n--) { - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); if (GC_REF_CHECK_COLOR(ref, GC_GREY)) { GC_REF_SET_COLOR(ref, GC_WHITE); zv++; while (--n) { - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { zend_refcounted *ref = Z_COUNTED_P(zv); if (GC_REF_CHECK_COLOR(ref, GC_GREY)) { GC_REF_SET_COLOR(ref, GC_WHITE); @@ -1390,7 +1393,7 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack) if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); if (GC_REF_CHECK_COLOR(ref, GC_GREY)) { GC_REF_SET_COLOR(ref, GC_WHITE); @@ -1400,7 +1403,7 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack) if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { zend_refcounted *ref = Z_COUNTED_P(zv); if (GC_REF_CHECK_COLOR(ref, GC_GREY)) { GC_REF_SET_COLOR(ref, GC_WHITE); @@ -1415,7 +1418,7 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack) p++; } } else if (GC_TYPE(ref) == IS_REFERENCE) { - if (Z_COLLECTABLE(((zend_reference*)ref)->val)) { + if (Z_COLLECTABLE(((zend_reference*)ref)->val) && GC_COLLECTABLE(Z_COUNTED(((zend_reference*)ref)->val))) { ref = Z_COUNTED(((zend_reference*)ref)->val); if (GC_REF_CHECK_COLOR(ref, GC_GREY)) { GC_REF_SET_COLOR(ref, GC_WHITE); @@ -1442,6 +1445,7 @@ static void gc_scan_roots(gc_stack *stack) idx = GC_FIRST_ROOT; end = GC_G(first_unused); while (idx != end) { +repeat: current = GC_IDX2PTR(idx); if (GC_IS_ROOT(current->ref)) { if (GC_REF_CHECK_COLOR(current->ref, GC_GREY)) { @@ -1453,15 +1457,9 @@ static void gc_scan_roots(gc_stack *stack) } /* Scan extra roots added during gc_scan */ - while (idx != GC_G(first_unused)) { - current = GC_IDX2PTR(idx); - if (GC_IS_ROOT(current->ref)) { - if (GC_REF_CHECK_COLOR(current->ref, GC_GREY)) { - GC_REF_SET_COLOR(current->ref, GC_WHITE); - gc_scan(current->ref, stack); - } - } - idx++; + if (idx != GC_G(first_unused)) { + end = GC_G(first_unused); + goto repeat; } } @@ -1532,7 +1530,7 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta for (; n != 0; n--) { ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR); zval *entry = (zval*) Z_PTR_P(zv); - if (Z_COLLECTABLE_P(entry) && GC_FROM_WEAKMAP_KEY(entry)) { + if (Z_COLLECTABLE_P(entry) && GC_FROM_WEAKMAP_KEY(entry) && GC_COLLECTABLE(Z_COUNTED_P(entry))) { GC_UNSET_FROM_WEAKMAP_KEY(entry); GC_UNSET_FROM_WEAKMAP(entry); ref = Z_COUNTED_P(entry); @@ -1553,7 +1551,7 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta for (; n != 0; n--) { ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR); zval *entry = (zval*) Z_PTR_P(zv); - if (Z_COLLECTABLE_P(entry) && GC_FROM_WEAKMAP(entry)) { + if (Z_COLLECTABLE_P(entry) && GC_COLLECTABLE(Z_COUNTED_P(entry)) && GC_FROM_WEAKMAP(entry)) { GC_UNSET_FROM_WEAKMAP_KEY(entry); GC_UNSET_FROM_WEAKMAP(entry); ref = Z_COUNTED_P(entry); @@ -1571,12 +1569,12 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta ht = obj->handlers->get_gc(obj, &table, &len); n = len; zv = table; - if (UNEXPECTED(ht)) { + if (UNEXPECTED(ht) && GC_COLLECTABLE(ht)) { GC_ADDREF(ht); if (GC_REF_CHECK_COLOR(ht, GC_WHITE)) { GC_REF_SET_BLACK(ht); for (; n != 0; n--) { - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) { @@ -1592,14 +1590,14 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta handle_zvals: for (; n != 0; n--) { - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) { GC_REF_SET_BLACK(ref); zv++; while (--n) { - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { zend_refcounted *ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) { @@ -1635,7 +1633,7 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) { @@ -1646,7 +1644,7 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { zend_refcounted *ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) { @@ -1662,7 +1660,7 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta p++; } } else if (GC_TYPE(ref) == IS_REFERENCE) { - if (Z_COLLECTABLE(((zend_reference*)ref)->val)) { + if (Z_COLLECTABLE(((zend_reference*)ref)->val) && GC_COLLECTABLE(Z_COUNTED(((zend_reference*)ref)->val))) { ref = Z_COUNTED(((zend_reference*)ref)->val); GC_ADDREF(ref); if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) { @@ -1741,7 +1739,7 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe GC_REMOVE_FROM_BUFFER(ref); count++; } else if (GC_TYPE(ref) == IS_REFERENCE) { - if (Z_COLLECTABLE(((zend_reference*)ref)->val)) { + if (Z_COLLECTABLE(((zend_reference*)ref)->val) && GC_COLLECTABLE(Z_COUNTED(((zend_reference*)ref)->val))) { ref = Z_COUNTED(((zend_reference*)ref)->val); goto tail_call; } @@ -1764,7 +1762,7 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe for (; n != 0; n--) { ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR); zval *entry = (zval*) Z_PTR_P(zv); - if (Z_OPT_COLLECTABLE_P(entry)) { + if (Z_OPT_COLLECTABLE_P(entry) && GC_COLLECTABLE(Z_COUNTED_P(entry))) { ref = Z_COUNTED_P(entry); GC_STACK_PUSH(ref); } @@ -1775,9 +1773,9 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe ht = obj->handlers->get_gc(obj, &table, &len); n = len; zv = table; - if (UNEXPECTED(ht)) { + if (UNEXPECTED(ht) && GC_COLLECTABLE(ht)) { for (; n != 0; n--) { - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); GC_STACK_PUSH(ref); } @@ -1792,11 +1790,11 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe handle_zvals: for (; n != 0; n--) { - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); zv++; while (--n) { - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { zend_refcounted *ref = Z_COUNTED_P(zv); GC_STACK_PUSH(ref); } @@ -1823,7 +1821,7 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); p++; while (--n) { @@ -1831,7 +1829,7 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } - if (Z_COLLECTABLE_P(zv)) { + if (Z_COLLECTABLE_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { zend_refcounted *ref = Z_COUNTED_P(zv); GC_STACK_PUSH(ref); } @@ -2285,7 +2283,7 @@ static void zend_gc_remove_root_tmpvars(void) { if (kind == ZEND_LIVE_TMPVAR || kind == ZEND_LIVE_LOOP) { uint32_t var_num = range->var & ~ZEND_LIVE_MASK; zval *var = ZEND_CALL_VAR(ex, var_num); - if (Z_COLLECTABLE_P(var)) { + if (Z_COLLECTABLE_P(var) && GC_COLLECTABLE(Z_COUNTED_P(var))) { GC_REMOVE_FROM_BUFFER(Z_COUNTED_P(var)); } } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index bac92ccafc4fc..121c3a93b1d5f 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1880,6 +1880,7 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par ce->parent = parent_ce; ce->default_object_handlers = parent_ce->default_object_handlers; ce->ce_flags |= ZEND_ACC_RESOLVED_PARENT; + ce->ce_flags2 |= (parent_ce->ce_flags2 & ZEND_ACC2_MAY_BE_CYCLIC); /* Inherit properties */ if (parent_ce->default_properties_count) { @@ -2878,6 +2879,9 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent if (!traits[i]) { continue; } + + ce->ce_flags2 |= (traits[i]->ce_flags2 & ZEND_ACC2_MAY_BE_CYCLIC); + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&traits[i]->properties_info, prop_name, property_info) { uint32_t flags = property_info->flags; diff --git a/Zend/zend_iterators.c b/Zend/zend_iterators.c index 64dbb0541a80d..9ee882c4f5445 100644 --- a/Zend/zend_iterators.c +++ b/Zend/zend_iterators.c @@ -59,6 +59,7 @@ ZEND_API void zend_register_iterator_wrapper(void) { INIT_CLASS_ENTRY(zend_iterator_class_entry, "__iterator_wrapper", NULL); zend_iterator_class_entry.default_object_handlers = &iterator_object_handlers; + zend_iterator_class_entry.ce_flags2 |= ZEND_ACC2_MAY_BE_CYCLIC; } static void iter_wrapper_free(zend_object *object) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 45eac02949d15..7ace4d93abf5d 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1257,6 +1257,7 @@ found:; variable_ptr = &EG(error_zval); goto exit; } + GC_DEL_FLAGS(zobj, GC_NOT_COLLECTABLE); if (UNEXPECTED(!(zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES))) { if (UNEXPECTED(!zend_deprecated_dynamic_property(zobj, name))) { variable_ptr = &EG(error_zval); @@ -1466,6 +1467,7 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam return &EG(error_zval); } } + GC_DEL_FLAGS(zobj, GC_NOT_COLLECTABLE); if (UNEXPECTED(zend_lazy_object_must_init(zobj))) { zobj = zend_lazy_object_init(zobj); if (!zobj) { diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c index 6f6a826389442..960c30d34beba 100644 --- a/Zend/zend_objects.c +++ b/Zend/zend_objects.c @@ -31,6 +31,9 @@ static zend_always_inline void _zend_object_std_init(zend_object *object, zend_c { GC_SET_REFCOUNT(object, 1); GC_TYPE_INFO(object) = GC_OBJECT; + if (!(ce->ce_flags2 & ZEND_ACC2_MAY_BE_CYCLIC)) { + GC_ADD_FLAGS(object, GC_NOT_COLLECTABLE); + } object->ce = ce; object->extra_flags = 0; object->handlers = ce->default_object_handlers; diff --git a/Zend/zend_objects_API.c b/Zend/zend_objects_API.c index c19873cf3be30..5f775c944e660 100644 --- a/Zend/zend_objects_API.c +++ b/Zend/zend_objects_API.c @@ -55,7 +55,9 @@ ZEND_API void ZEND_FASTCALL zend_objects_store_call_destructors(zend_objects_sto || obj->ce->destructor) { GC_ADDREF(obj); obj->handlers->dtor_obj(obj); - GC_DELREF(obj); + if (UNEXPECTED(GC_DELREF(obj) == 0)) { + zend_objects_store_del(obj); + } } } } diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 22dbfa9be879b..435addb9e7533 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -810,7 +810,7 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { #define GC_ARRAY IS_ARRAY #define GC_OBJECT IS_OBJECT #define GC_RESOURCE (IS_RESOURCE | (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT)) -#define GC_REFERENCE (IS_REFERENCE | (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT)) +#define GC_REFERENCE IS_REFERENCE #define GC_CONSTANT_AST (IS_CONSTANT_AST | (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT)) /* zval.u1.v.type_flags */ diff --git a/Zend/zend_weakrefs.c b/Zend/zend_weakrefs.c index 8c1263885bf6c..84675d7e774d1 100644 --- a/Zend/zend_weakrefs.c +++ b/Zend/zend_weakrefs.c @@ -421,6 +421,10 @@ static void zend_weakmap_write_dimension(zend_object *object, zval *offset, zval return; } + if (GC_TYPE_INFO(obj_addr) & (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT)) { + GC_DEL_FLAGS(obj_addr, GC_NOT_COLLECTABLE); + } + zend_weakref_register(obj_addr, ZEND_WEAKREF_ENCODE(&wm->ht, ZEND_WEAKREF_TAG_MAP)); zend_hash_index_add_new(&wm->ht, obj_key, value); } diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index ace1206682042..845750d7949a5 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -11510,6 +11510,25 @@ static int zend_jit_bind_global(zend_jit_ctx *jit, const zend_op *opline, uint32 ir_END_list(end_inputs); ir_IF_TRUE(if_non_zero); + if (op1_info & (MAY_BE_REF|MAY_BE_GUARD)) { + ir_ref if_ref, ref_ref, if_collectable; + + if_ref = jit_if_Z_TYPE(jit, op1_addr, IS_REFERENCE); + ir_IF_TRUE(if_ref); + + ref_ref = ir_ADD_OFFSET(ref2, offsetof(zend_reference, val)); + + if_collectable = jit_if_COLLECTABLE_ref(jit, ref_ref); + ir_IF_FALSE(if_collectable); + ir_END_list(end_inputs); + ir_IF_TRUE(if_collectable); + + ref_ref = jit_Z_PTR_ref(jit, ref_ref); + + ir_MERGE_WITH_EMPTY_FALSE(if_ref); + ref2 = ir_PHI_2(IR_ADDR, ref_ref, ref2); + } + // JIT: GC_ZVAL_CHECK_POSSIBLE_ROOT(variable_ptr) if_may_not_leak = jit_if_GC_MAY_NOT_LEAK(jit, ref2); ir_IF_TRUE(if_may_not_leak); diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 256468e39a444..355254ad7537c 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -4400,6 +4400,16 @@ ZEND_METHOD(ReflectionClass, getAttributes) } /* }}} */ +ZEND_METHOD(ReflectionClass, mayBeCyclic) +{ + reflection_object *intern; + zend_class_entry *ce; + + GET_REFLECTION_OBJECT_PTR(ce); + + RETURN_BOOL(ce->ce_flags2 & ZEND_ACC2_MAY_BE_CYCLIC); +} + /* {{{ Returns the class' constructor if there is one, NULL otherwise */ ZEND_METHOD(ReflectionClass, getConstructor) { diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 147e2f18c9e2c..945f9536301bd 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -436,6 +436,8 @@ public function getNamespaceName(): string {} public function getShortName(): string {} public function getAttributes(?string $name = null, int $flags = 0): array {} + + public function mayBeCyclic(): bool {} } class ReflectionObject extends ReflectionClass diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 5d45245100185..8f899d89413d9 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit php_reflection.stub.php instead. - * Stub hash: dba3ec692c7c90d59d67f6e5323dc31997fc92e0 + * Stub hash: 9b7d8c3c3fc5e1f11d2c17e0df2c368bd93000b7 * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) @@ -367,6 +367,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClass_getAttributes arginfo_class_ReflectionFunctionAbstract_getAttributes +#define arginfo_class_ReflectionClass_mayBeCyclic arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType + ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionObject___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0) ZEND_END_ARG_INFO() @@ -852,6 +854,7 @@ ZEND_METHOD(ReflectionClass, inNamespace); ZEND_METHOD(ReflectionClass, getNamespaceName); ZEND_METHOD(ReflectionClass, getShortName); ZEND_METHOD(ReflectionClass, getAttributes); +ZEND_METHOD(ReflectionClass, mayBeCyclic); ZEND_METHOD(ReflectionObject, __construct); ZEND_METHOD(ReflectionProperty, __construct); ZEND_METHOD(ReflectionProperty, __toString); @@ -1146,6 +1149,7 @@ static const zend_function_entry class_ReflectionClass_methods[] = { ZEND_ME(ReflectionClass, getNamespaceName, arginfo_class_ReflectionClass_getNamespaceName, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getShortName, arginfo_class_ReflectionClass_getShortName, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getAttributes, arginfo_class_ReflectionClass_getAttributes, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, mayBeCyclic, arginfo_class_ReflectionClass_mayBeCyclic, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/ext/reflection/php_reflection_decl.h b/ext/reflection/php_reflection_decl.h index 7a458bccd1e5a..745b7528597f7 100644 --- a/ext/reflection/php_reflection_decl.h +++ b/ext/reflection/php_reflection_decl.h @@ -1,12 +1,12 @@ /* This is a generated file, edit php_reflection.stub.php instead. - * Stub hash: dba3ec692c7c90d59d67f6e5323dc31997fc92e0 */ + * Stub hash: 9b7d8c3c3fc5e1f11d2c17e0df2c368bd93000b7 */ -#ifndef ZEND_PHP_REFLECTION_DECL_dba3ec692c7c90d59d67f6e5323dc31997fc92e0_H -#define ZEND_PHP_REFLECTION_DECL_dba3ec692c7c90d59d67f6e5323dc31997fc92e0_H +#ifndef ZEND_PHP_REFLECTION_DECL_9b7d8c3c3fc5e1f11d2c17e0df2c368bd93000b7_H +#define ZEND_PHP_REFLECTION_DECL_9b7d8c3c3fc5e1f11d2c17e0df2c368bd93000b7_H typedef enum zend_enum_PropertyHookType { ZEND_ENUM_PropertyHookType_Get = 1, ZEND_ENUM_PropertyHookType_Set = 2, } zend_enum_PropertyHookType; -#endif /* ZEND_PHP_REFLECTION_DECL_dba3ec692c7c90d59d67f6e5323dc31997fc92e0_H */ +#endif /* ZEND_PHP_REFLECTION_DECL_9b7d8c3c3fc5e1f11d2c17e0df2c368bd93000b7_H */ diff --git a/ext/reflection/tests/ReflectionClass_toString_001.phpt b/ext/reflection/tests/ReflectionClass_toString_001.phpt index fd5d83e917419..67828502b3bdb 100644 --- a/ext/reflection/tests/ReflectionClass_toString_001.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_001.phpt @@ -30,7 +30,7 @@ Class [ class ReflectionClass implements Stringable, Refle Property [ public string $name ] } - - Methods [64] { + - Methods [65] { Method [ private method __clone ] { - Parameters [0] { @@ -514,5 +514,12 @@ Class [ class ReflectionClass implements Stringable, Refle } - Return [ array ] } + + Method [ public method mayBeCyclic ] { + + - Parameters [0] { + } + - Return [ bool ] + } } } diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index 353c7086d4304..12d85792b932c 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -654,6 +654,7 @@ declared_property: } } + GC_DEL_FLAGS(Z_OBJ_P(rval), GC_NOT_COLLECTABLE); data = zend_hash_add_new(ht, Z_STR(key), &EG(uninitialized_zval)); } else if (ret < 0) { goto failure;