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/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 fd5b7c8db7966..9edcc11bce929 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1770,6 +1770,7 @@ ZEND_API void object_properties_load(zend_object *object, HashTable *properties) ZSTR_VAL(object->ce->name), property_info != ZEND_WRONG_PROPERTY_INFO ? zend_get_unmangled_property_name(key): ""); } + GC_TYPE_INFO(object) &= ~(GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT); prop = zend_hash_update(zend_std_get_properties_ex(object), key, prop); zval_add_ref(prop); } @@ -1782,6 +1783,7 @@ ZEND_API void object_properties_load(zend_object *object, HashTable *properties) ZSTR_VAL(object->ce->name), h); } + GC_TYPE_INFO(object) &= ~(GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT); prop = zend_hash_index_update(zend_std_get_properties_ex(object), h, prop); zval_add_ref(prop); } @@ -2390,6 +2392,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; @@ -2434,6 +2438,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_flags |= ZEND_ACC_MAY_BE_CYCLIC; + } + } ZEND_HASH_FOREACH_END(); + } + return SUCCESS; } /* }}} */ @@ -4494,6 +4514,27 @@ static zend_always_inline bool is_persistent_class(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)) { + 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; @@ -4506,6 +4547,12 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z } } + if (!(access_type & ZEND_ACC_STATIC) + && !(ce->ce_flags & ZEND_ACC_MAY_BE_CYCLIC) + && zend_type_may_be_cyclic(type)) { + ce->ce_flags |= ZEND_ACC_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 629aa7d51a840..f3bc55541cf5c 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -705,6 +705,8 @@ void zend_register_closure_ce(void) /* {{{ */ zend_ce_closure = register_class_Closure(); zend_ce_closure->create_object = zend_closure_new; zend_ce_closure->default_object_handlers = &closure_handlers; + /* FIXME: Potentially infer ZEND_ACC_MAY_BE_CYCLIC during construction of + * closure? static closures not binding by references can't be cyclic. */ memcpy(&closure_handlers, &std_object_handlers, sizeof(zend_object_handlers)); closure_handlers.free_obj = zend_closure_free_storage; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 1eaf3ef686e79..5f3634ae4b894 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -267,7 +267,7 @@ typedef struct _zend_oparray_context { #define ZEND_ACC_PROTECTED_SET (1 << 11) /* | | X | */ #define ZEND_ACC_PRIVATE_SET (1 << 12) /* | | X | */ /* | | | */ -/* Class Flags (unused: 30,31) | | | */ +/* Class Flags (unused: 31) | | | */ /* =========== | | | */ /* | | | */ /* Special class types | | | */ @@ -333,6 +333,9 @@ typedef struct _zend_oparray_context { /* Class cannot be serialized or unserialized | | | */ #define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */ /* | | | */ +/* Object may be the root of a cycle | | | */ +#define ZEND_ACC_MAY_BE_CYCLIC (1 << 30) /* X | | | */ +/* | | | */ /* Function Flags (unused: 29-30) | | | */ /* ============== | | | */ /* | | | */ diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index a966a106def33..b996e7e294ed8 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -242,6 +242,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); typedef struct _gc_root_buffer { @@ -818,7 +821,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_REFCOUNTED_P(entry)) { + if (Z_OPT_REFCOUNTED_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 @@ -855,7 +858,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_REFCOUNTED_P(entry)) { + if (Z_OPT_REFCOUNTED_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 @@ -888,12 +891,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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) { @@ -909,14 +912,14 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack) handle_zvals: for (; n != 0; n--) { - if (Z_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_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)) { @@ -948,7 +951,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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) { @@ -959,7 +962,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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_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)) { @@ -975,7 +978,7 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack) p++; } } else if (GC_TYPE(ref) == IS_REFERENCE) { - if (Z_REFCOUNTED(((zend_reference*)ref)->val)) { + if (Z_REFCOUNTED(((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)) { @@ -1019,7 +1022,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_REFCOUNTED_P(entry)) { + if (Z_REFCOUNTED_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 @@ -1043,7 +1046,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_REFCOUNTED_P(entry)) { + if (Z_REFCOUNTED_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 @@ -1064,12 +1067,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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); GC_DELREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) { @@ -1084,14 +1087,14 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack) } handle_zvals: for (; n != 0; n--) { - if (Z_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_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)) { @@ -1123,7 +1126,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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); GC_DELREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) { @@ -1134,7 +1137,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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_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)) { @@ -1150,7 +1153,7 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack) p++; } } else if (GC_TYPE(ref) == IS_REFERENCE) { - if (Z_REFCOUNTED(((zend_reference*)ref)->val)) { + if (Z_REFCOUNTED(((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)) { @@ -1263,7 +1266,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_REFCOUNTED_P(entry)) { + if (Z_OPT_REFCOUNTED_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); @@ -1277,12 +1280,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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_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); @@ -1297,13 +1300,13 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack) handle_zvals: for (; n != 0; n--) { - if (Z_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_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); @@ -1335,7 +1338,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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_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); @@ -1345,7 +1348,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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_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); @@ -1360,7 +1363,7 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack) p++; } } else if (GC_TYPE(ref) == IS_REFERENCE) { - if (Z_REFCOUNTED(((zend_reference*)ref)->val)) { + if (Z_REFCOUNTED(((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); @@ -1473,7 +1476,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_REFCOUNTED_P(entry) && GC_FROM_WEAKMAP_KEY(entry)) { + if (Z_REFCOUNTED_P(entry) && GC_COLLECTABLE(Z_COUNTED_P(entry)) && GC_FROM_WEAKMAP_KEY(entry)) { GC_UNSET_FROM_WEAKMAP_KEY(entry); GC_UNSET_FROM_WEAKMAP(entry); ref = Z_COUNTED_P(entry); @@ -1494,7 +1497,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_REFCOUNTED_P(entry) && GC_FROM_WEAKMAP(entry)) { + if (Z_REFCOUNTED_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); @@ -1512,12 +1515,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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) { @@ -1533,14 +1536,14 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta handle_zvals: for (; n != 0; n--) { - if (Z_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_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)) { @@ -1576,7 +1579,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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) { @@ -1587,7 +1590,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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_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)) { @@ -1603,7 +1606,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_REFCOUNTED(((zend_reference*)ref)->val)) { + if (Z_REFCOUNTED(((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)) { @@ -1681,7 +1684,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_REFCOUNTED(((zend_reference*)ref)->val)) { + if (Z_REFCOUNTED(((zend_reference*)ref)->val) && GC_COLLECTABLE(Z_COUNTED(((zend_reference*)ref)->val))) { ref = Z_COUNTED(((zend_reference*)ref)->val); goto tail_call; } @@ -1704,7 +1707,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_REFCOUNTED_P(entry)) { + if (Z_OPT_REFCOUNTED_P(entry) && GC_COLLECTABLE(Z_COUNTED_P(entry))) { ref = Z_COUNTED_P(entry); GC_STACK_PUSH(ref); } @@ -1715,9 +1718,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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); GC_STACK_PUSH(ref); } @@ -1732,11 +1735,11 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe handle_zvals: for (; n != 0; n--) { - if (Z_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); zv++; while (--n) { - if (Z_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { zend_refcounted *ref = Z_COUNTED_P(zv); GC_STACK_PUSH(ref); } @@ -1763,7 +1766,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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { ref = Z_COUNTED_P(zv); p++; while (--n) { @@ -1771,7 +1774,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_REFCOUNTED_P(zv)) { + if (Z_REFCOUNTED_P(zv) && GC_COLLECTABLE(Z_COUNTED_P(zv))) { zend_refcounted *ref = Z_COUNTED_P(zv); GC_STACK_PUSH(ref); } @@ -2205,7 +2208,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_REFCOUNTED_P(var)) { + if (Z_REFCOUNTED_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 3557b14bb9740..76aed3fea04c2 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1810,6 +1810,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_flags |= (parent_ce->ce_flags & ZEND_ACC_MAY_BE_CYCLIC); /* Inherit properties */ if (parent_ce->default_properties_count) { @@ -2832,6 +2833,9 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent if (!traits[i]) { continue; } + + ce->ce_flags |= (traits[i]->ce_flags & ZEND_ACC_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 f67033b11161c..6ff9c64857afc 100644 --- a/Zend/zend_iterators.c +++ b/Zend/zend_iterators.c @@ -58,6 +58,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_flags |= ZEND_ACC_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 0709d05580dc8..519beaabac54d 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1169,6 +1169,7 @@ found:; variable_ptr = &EG(error_zval); goto exit; } + GC_TYPE_INFO(zobj) &= ~(GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT); if (UNEXPECTED(!(zobj->ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES))) { if (UNEXPECTED(!zend_deprecated_dynamic_property(zobj, name))) { variable_ptr = &EG(error_zval); @@ -1387,6 +1388,7 @@ ZEND_API zval *zend_std_get_property_ptr_ptr(zend_object *zobj, zend_string *nam return &EG(error_zval); } } + GC_TYPE_INFO(zobj) &= ~(GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT); 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 fd0e97c5f4131..5dbd411c623f8 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_flags & ZEND_ACC_MAY_BE_CYCLIC)) { + GC_TYPE_INFO(object) |= (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT); + } 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 8a6b714c8b3fd..5c7c51ddf32c1 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 f7d234c92c622..f79c3df5a8f3e 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -786,7 +786,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/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index f5e463699b1b5..aca7467efd75b 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -4343,6 +4343,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_flags & ZEND_ACC_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 be511d7ee14cd..b33c27b2af2d1 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -432,6 +432,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 d78a685dde9c9..7bc073f1f5459 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 the .stub.php file instead. - * Stub hash: 3c6be99bb36965139464925a618cb0bf03affa62 */ + * Stub hash: e2086e92426bd71218575f5a4c6dd0fea7049008 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -366,6 +366,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() @@ -847,6 +849,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); @@ -1139,6 +1142,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/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 ] + } } }