diff --git a/Zend/tests/weakrefs/gh10043-001.phpt b/Zend/tests/weakrefs/gh10043-001.phpt new file mode 100644 index 0000000000000..a9caaa96239fe --- /dev/null +++ b/Zend/tests/weakrefs/gh10043-001.phpt @@ -0,0 +1,41 @@ +--TEST-- +Self-referencing map entry GC - 001 +--FILE-- + + array(2) { + ["key"]=> + object(Value)#%d (1) { + ["value"]=> + string(1) "a" + } + ["value"]=> + object(Value)#%d (1) { + ["value"]=> + string(1) "a" + } + } +} +object(WeakMap)#%d (0) { +} diff --git a/Zend/tests/weakrefs/gh10043-002.phpt b/Zend/tests/weakrefs/gh10043-002.phpt new file mode 100644 index 0000000000000..bf6067bc5ea68 --- /dev/null +++ b/Zend/tests/weakrefs/gh10043-002.phpt @@ -0,0 +1,44 @@ +--TEST-- +Self-referencing map entry GC - 002 +--FILE-- + + array(2) { + ["key"]=> + object(Value)#%d (1) { + ["value"]=> + string(1) "a" + } + ["value"]=> + array(1) { + [0]=> + object(Value)#%d (1) { + ["value"]=> + string(1) "a" + } + } + } +} +object(WeakMap)#1 (0) { +} diff --git a/Zend/tests/weakrefs/gh10043-003.phpt b/Zend/tests/weakrefs/gh10043-003.phpt new file mode 100644 index 0000000000000..017b91fe1fc15 --- /dev/null +++ b/Zend/tests/weakrefs/gh10043-003.phpt @@ -0,0 +1,50 @@ +--TEST-- +Self-referencing map entry GC - 003 +--FILE-- +get()); + +gc_collect_cycles(); + +// $obj is first in the root buffer +$obj = null; +$map = null; +gc_collect_cycles(); + +var_dump($ref->get()); + +--EXPECTF-- +object(WeakMap)#%d (1) { + [0]=> + array(2) { + ["key"]=> + object(Value)#%d (1) { + ["value"]=> + string(1) "a" + } + ["value"]=> + array(2) { + [0]=> + object(Value)#%d (1) { + ["value"]=> + string(1) "a" + } + [1]=> + *RECURSION* + } + } +} +NULL diff --git a/Zend/tests/weakrefs/gh10043-004.phpt b/Zend/tests/weakrefs/gh10043-004.phpt new file mode 100644 index 0000000000000..9f3f264ad6437 --- /dev/null +++ b/Zend/tests/weakrefs/gh10043-004.phpt @@ -0,0 +1,50 @@ +--TEST-- +Self-referencing map entry GC - 004 +--FILE-- +get()); + +gc_collect_cycles(); + +// $map is first in the root buffer +$map = null; +$obj = null; +gc_collect_cycles(); + +var_dump($ref->get()); + +--EXPECTF-- +object(WeakMap)#%d (1) { + [0]=> + array(2) { + ["key"]=> + object(Value)#%d (1) { + ["value"]=> + string(1) "a" + } + ["value"]=> + array(2) { + [0]=> + *RECURSION* + [1]=> + object(Value)#%d (1) { + ["value"]=> + string(1) "a" + } + } + } +} +NULL diff --git a/Zend/tests/weakrefs/gh10043-005.phpt b/Zend/tests/weakrefs/gh10043-005.phpt new file mode 100644 index 0000000000000..d495502625181 --- /dev/null +++ b/Zend/tests/weakrefs/gh10043-005.phpt @@ -0,0 +1,49 @@ +--TEST-- +Self-referencing map entry GC - 005 +--FILE-- + + array(2) { + ["key"]=> + object(Value)#%d (1) { + ["value"]=> + string(1) "a" + } + ["value"]=> + array(1) { + [0]=> + object(Value)#%d (1) { + ["value"]=> + string(1) "a" + } + } + } +} +object(WeakMap)#1 (0) { +} diff --git a/Zend/tests/weakrefs/gh10043-006.phpt b/Zend/tests/weakrefs/gh10043-006.phpt new file mode 100644 index 0000000000000..ea91a7d9076d9 --- /dev/null +++ b/Zend/tests/weakrefs/gh10043-006.phpt @@ -0,0 +1,41 @@ +--TEST-- +Self-referencing map entry GC - 006 +--FILE-- + + array(2) { + ["key"]=> + object(Value)#2 (1) { + ["value"]=> + string(1) "a" + } + ["value"]=> + object(Value)#2 (1) { + ["value"]=> + string(1) "a" + } + } +} diff --git a/Zend/tests/weakrefs/gh10043-007.phpt b/Zend/tests/weakrefs/gh10043-007.phpt new file mode 100644 index 0000000000000..ca25a73e2c4e0 --- /dev/null +++ b/Zend/tests/weakrefs/gh10043-007.phpt @@ -0,0 +1,33 @@ +--TEST-- +Self-referencing map entry GC - 007 +--FILE-- +name."\n"; + } +} + +$container = new Canary('container'); +$canary = new Canary('canary'); +$container->canary = $canary; + +$map = new \WeakMap(); +$map[$canary] = $container; + +echo 1; +unset($container, $canary); +gc_collect_cycles(); +echo 2; + +--EXPECT-- +1container +canary +2 diff --git a/Zend/tests/weakrefs/gh10043-008.phpt b/Zend/tests/weakrefs/gh10043-008.phpt new file mode 100644 index 0000000000000..ffbf1fbe44085 --- /dev/null +++ b/Zend/tests/weakrefs/gh10043-008.phpt @@ -0,0 +1,30 @@ +--TEST-- +Self-referencing map entry GC - 008 +--FILE-- +name."\n"; + } +} + +$canary = new Canary('canary'); + +$map = new \WeakMap(); +$map[$canary] = $canary; + +echo 1; +unset($canary); +gc_collect_cycles(); +echo 2; + +--EXPECT-- +1canary +2 diff --git a/Zend/tests/weakrefs/gh10043-009.phpt b/Zend/tests/weakrefs/gh10043-009.phpt new file mode 100644 index 0000000000000..6218d189806f7 --- /dev/null +++ b/Zend/tests/weakrefs/gh10043-009.phpt @@ -0,0 +1,28 @@ +--TEST-- +Self-referencing map entry GC - 009 +--FILE-- +get()); +?> +--EXPECT-- +NULL diff --git a/Zend/tests/weakrefs/gh10043-010.phpt b/Zend/tests/weakrefs/gh10043-010.phpt new file mode 100644 index 0000000000000..03b4b76cd72f5 --- /dev/null +++ b/Zend/tests/weakrefs/gh10043-010.phpt @@ -0,0 +1,32 @@ +--TEST-- +Self-referencing map entry GC - 010 +--FILE-- + +==DONE== +--EXPECT-- +==DONE== diff --git a/Zend/tests/weakrefs/gh10043-011.phpt b/Zend/tests/weakrefs/gh10043-011.phpt new file mode 100644 index 0000000000000..18c07dba1ef21 --- /dev/null +++ b/Zend/tests/weakrefs/gh10043-011.phpt @@ -0,0 +1,24 @@ +--TEST-- +Self-referencing map entry GC - 011 +--FILE-- +k = $k; +$m[$k] = $v; + +$m2 = $m; +unset($m2, $k, $v); + +gc_collect_cycles(); + +var_dump($m); + +--EXPECT-- +object(WeakMap)#1 (0) { +} diff --git a/Zend/tests/weakrefs/gh10043-012.phpt b/Zend/tests/weakrefs/gh10043-012.phpt new file mode 100644 index 0000000000000..951b609a27064 --- /dev/null +++ b/Zend/tests/weakrefs/gh10043-012.phpt @@ -0,0 +1,33 @@ +--TEST-- +Self-referencing map entry GC - 012 +--FILE-- +get()); +?> +--EXPECT-- +NULL diff --git a/Zend/tests/weakrefs/gh10043-014.phpt b/Zend/tests/weakrefs/gh10043-014.phpt new file mode 100644 index 0000000000000..4099427d42add --- /dev/null +++ b/Zend/tests/weakrefs/gh10043-014.phpt @@ -0,0 +1,41 @@ +--TEST-- +Self-referencing map entry GC - 014 +--FILE-- + +--EXPECT-- +object(WeakMap)#1 (1) { + [0]=> + array(2) { + ["key"]=> + object(Value)#2 (1) { + ["value"]=> + string(1) "a" + } + ["value"]=> + object(Value)#2 (1) { + ["value"]=> + string(1) "a" + } + } +} diff --git a/Zend/tests/weakrefs/gh10043-015.phpt b/Zend/tests/weakrefs/gh10043-015.phpt new file mode 100644 index 0000000000000..9a1acc6533628 --- /dev/null +++ b/Zend/tests/weakrefs/gh10043-015.phpt @@ -0,0 +1,24 @@ +--TEST-- +Self-referencing map entry GC - 015 +--FILE-- +get()); + +?> +--EXPECT-- +NULL diff --git a/Zend/tests/weakrefs/gh10043-016.phpt b/Zend/tests/weakrefs/gh10043-016.phpt new file mode 100644 index 0000000000000..88e934da47e20 --- /dev/null +++ b/Zend/tests/weakrefs/gh10043-016.phpt @@ -0,0 +1,45 @@ +--TEST-- +Self-referencing map entry GC - 016 +--FILE-- + +--EXPECT-- +object(WeakMap)#1 (2) { + [0]=> + array(2) { + ["key"]=> + object(K1)#2 (0) { + } + ["value"]=> + array(2) { + [0]=> + object(K1)#2 (0) { + } + [1]=> + *RECURSION* + } + } + [1]=> + array(2) { + ["key"]=> + object(K2)#3 (0) { + } + ["value"]=> + object(K2)#3 (0) { + } + } +} diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index d7b6cc7e6a2a9..6a9762eb54973 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -70,6 +70,11 @@ #include "zend_API.h" #include "zend_fibers.h" #include "zend_hrtime.h" +#include "zend_weakrefs.h" + +#ifndef GC_BENCH +# define GC_BENCH 0 +#endif #ifndef ZEND_GC_DEBUG # define ZEND_GC_DEBUG 0 @@ -183,6 +188,40 @@ /* GC flags */ #define GC_HAS_DESTRUCTORS (1<<0) +/* Weak maps */ +#define Z_FROM_WEAKMAP_KEY (1<<0) +#define Z_FROM_WEAKMAP (1<<1) + +/* The WeakMap entry zv is reachable from roots by following the virtual + * reference from the a WeakMap key to the entry */ +#define GC_FROM_WEAKMAP_KEY(zv) \ + (Z_TYPE_INFO_P((zv)) & (Z_FROM_WEAKMAP_KEY << Z_TYPE_INFO_EXTRA_SHIFT)) + +#define GC_SET_FROM_WEAKMAP_KEY(zv) do { \ + zval *_z = (zv); \ + Z_TYPE_INFO_P(_z) = Z_TYPE_INFO_P(_z) | (Z_FROM_WEAKMAP_KEY << Z_TYPE_INFO_EXTRA_SHIFT); \ +} while (0) + +#define GC_UNSET_FROM_WEAKMAP_KEY(zv) do { \ + zval *_z = (zv); \ + Z_TYPE_INFO_P(_z) = Z_TYPE_INFO_P(_z) & ~(Z_FROM_WEAKMAP_KEY << Z_TYPE_INFO_EXTRA_SHIFT); \ +} while (0) + +/* The WeakMap entry zv is reachable from roots by following the reference from + * the WeakMap */ +#define GC_FROM_WEAKMAP(zv) \ + (Z_TYPE_INFO_P((zv)) & (Z_FROM_WEAKMAP << Z_TYPE_INFO_EXTRA_SHIFT)) + +#define GC_SET_FROM_WEAKMAP(zv) do { \ + zval *_z = (zv); \ + Z_TYPE_INFO_P(_z) = Z_TYPE_INFO_P(_z) | (Z_FROM_WEAKMAP << Z_TYPE_INFO_EXTRA_SHIFT); \ +} while (0) + +#define GC_UNSET_FROM_WEAKMAP(zv) do { \ + zval *_z = (zv); \ + Z_TYPE_INFO_P(_z) = Z_TYPE_INFO_P(_z) & ~(Z_FROM_WEAKMAP << Z_TYPE_INFO_EXTRA_SHIFT); \ +} while (0) + /* unused buffers */ #define GC_HAS_UNUSED() \ (GC_G(unused) != GC_INVALID) @@ -672,6 +711,39 @@ ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref) GC_BENCH_PEAK(root_buf_peak, root_buf_length); } +static void ZEND_FASTCALL gc_extra_root(zend_refcounted *ref) +{ + uint32_t idx; + gc_root_buffer *newRoot; + + if (EXPECTED(GC_HAS_UNUSED())) { + idx = GC_FETCH_UNUSED(); + } else if (EXPECTED(GC_HAS_NEXT_UNUSED_UNDER_THRESHOLD())) { + idx = GC_FETCH_NEXT_UNUSED(); + } else { + gc_grow_root_buffer(); + if (UNEXPECTED(!GC_HAS_NEXT_UNUSED())) { + /* TODO: can this really happen? */ + return; + } + idx = GC_FETCH_NEXT_UNUSED(); + } + + ZEND_ASSERT(GC_TYPE(ref) == IS_ARRAY || GC_TYPE(ref) == IS_OBJECT); + ZEND_ASSERT(GC_REF_ADDRESS(ref) == 0); + + newRoot = GC_IDX2PTR(idx); + newRoot->ref = ref; /* GC_ROOT tag is 0 */ + + idx = gc_compress(idx); + GC_REF_SET_INFO(ref, idx | GC_REF_COLOR(ref)); + GC_G(num_roots)++; + + GC_BENCH_INC(zval_buffered); + GC_BENCH_INC(root_buf_length); + GC_BENCH_PEAK(root_buf_peak, root_buf_length); +} + static zend_never_inline void ZEND_FASTCALL gc_remove_compressed(zend_refcounted *ref, uint32_t idx) { gc_root_buffer *root = gc_decompress(ref, idx); @@ -717,6 +789,82 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack) zval *table; int len; + if (UNEXPECTED(GC_FLAGS(obj) & IS_OBJ_WEAKLY_REFERENCED)) { + zend_weakmap_get_object_key_entry_gc(obj, &table, &len); + n = len; + zv = table; + for (; n != 0; n-=2) { + ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR); + zval *entry = (zval*) Z_PTR_P(zv); + zval *weakmap = zv+1; + ZEND_ASSERT(Z_REFCOUNTED_P(weakmap)); + if (Z_OPT_REFCOUNTED_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 + * ensure that it's eventually scanned in + * gc_scan_roots as well. */ + if (!GC_REF_ADDRESS(Z_COUNTED_P(weakmap))) { + gc_extra_root(Z_COUNTED_P(weakmap)); + } + } else if (/* GC_REF_CHECK_COLOR(Z_COUNTED_P(weakmap), GC_BLACK) && */ !GC_FROM_WEAKMAP(entry)) { + /* Both the entry weakmap and key are BLACK, so we + * can mark the entry BLACK as well. + * !GC_FROM_WEAKMAP(entry) means that the weakmap + * was already scanned black (or will not be + * scanned), so it's our responsibility to mark the + * entry */ + ZEND_ASSERT(GC_REF_CHECK_COLOR(Z_COUNTED_P(weakmap), GC_BLACK)); + ref = Z_COUNTED_P(entry); + GC_ADDREF(ref); + if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) { + GC_REF_SET_BLACK(ref); + GC_STACK_PUSH(ref); + } + } + } + zv+=2; + } + } + + if (UNEXPECTED(obj->handlers->get_gc == zend_weakmap_get_gc)) { + zend_weakmap_get_key_entry_gc(obj, &table, &len); + n = len; + zv = table; + for (; n != 0; n-=2) { + 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)) { + 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 + * ensure that it's eventually scanned in + * gc_scan_roots as well. */ + if (!GC_REF_ADDRESS(Z_COUNTED_P(key))) { + gc_extra_root(Z_COUNTED_P(key)); + } + } else if (/* GC_REF_CHECK_COLOR(Z_COUNTED_P(key), GC_BLACK) && */ !GC_FROM_WEAKMAP_KEY(entry)) { + /* Both the entry weakmap and key are BLACK, so we + * can mark the entry BLACK as well. + * !GC_FROM_WEAKMAP_KEY(entry) means that the key + * was already scanned black (or will not be + * scanned), so it's our responsibility to mark the + * entry */ + ZEND_ASSERT(GC_REF_CHECK_COLOR(Z_COUNTED_P(key), GC_BLACK)); + ref = Z_COUNTED_P(entry); + GC_ADDREF(ref); + if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) { + GC_REF_SET_BLACK(ref); + GC_STACK_PUSH(ref); + } + } + } + zv += 2; + } + goto next; + } + ht = obj->handlers->get_gc(obj, &table, &len); n = len; zv = table; @@ -817,6 +965,7 @@ static void gc_scan_black(zend_refcounted *ref, gc_stack *stack) } } +next: ref = GC_STACK_POP(); if (ref) { goto tail_call; @@ -841,6 +990,57 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack) zval *table; int len; + if (UNEXPECTED(GC_FLAGS(obj) & IS_OBJ_WEAKLY_REFERENCED)) { + zend_weakmap_get_object_key_entry_gc(obj, &table, &len); + n = len; + zv = table; + for (; n != 0; n-=2) { + ZEND_ASSERT(Z_TYPE_P(zv) == IS_PTR); + zval *entry = (zval*) Z_PTR_P(zv); + zval *weakmap = zv+1; + ZEND_ASSERT(Z_REFCOUNTED_P(weakmap)); + if (Z_REFCOUNTED_P(entry)) { + GC_SET_FROM_WEAKMAP_KEY(entry); + ref = Z_COUNTED_P(entry); + /* Only DELREF if the contribution from the weakmap has + * not been cancelled yet */ + if (!GC_FROM_WEAKMAP(entry)) { + GC_DELREF(ref); + } + if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) { + GC_REF_SET_COLOR(ref, GC_GREY); + GC_STACK_PUSH(ref); + } + } + zv+=2; + } + } + + if (UNEXPECTED(obj->handlers->get_gc == zend_weakmap_get_gc)) { + zend_weakmap_get_entry_gc(obj, &table, &len); + n = len; + zv = table; + 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_SET_FROM_WEAKMAP(entry); + ref = Z_COUNTED_P(entry); + /* Only DELREF if the contribution from the weakmap key + * has not been cancelled yet */ + if (!GC_FROM_WEAKMAP_KEY(entry)) { + GC_DELREF(ref); + } + if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) { + GC_REF_SET_COLOR(ref, GC_GREY); + GC_STACK_PUSH(ref); + } + } + zv++; + } + goto next; + } + ht = obj->handlers->get_gc(obj, &table, &len); n = len; zv = table; @@ -940,6 +1140,7 @@ static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack) } } +next: ref = GC_STACK_POP(); if (ref) { goto tail_call; @@ -1035,6 +1236,24 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack) zval *table; int len; + if (UNEXPECTED(GC_FLAGS(obj) & IS_OBJ_WEAKLY_REFERENCED)) { + zend_weakmap_get_object_entry_gc(obj, &table, &len); + n = len; + zv = table; + 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)) { + ref = Z_COUNTED_P(entry); + if (GC_REF_CHECK_COLOR(ref, GC_GREY)) { + GC_REF_SET_COLOR(ref, GC_WHITE); + GC_STACK_PUSH(ref); + } + } + zv++; + } + } + ht = obj->handlers->get_gc(obj, &table, &len); n = len; zv = table; @@ -1082,7 +1301,7 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack) } else if (GC_TYPE(ref) == IS_ARRAY) { ht = (HashTable *)ref; ZEND_ASSERT(ht != &EG(symbol_table)); - + handle_ht: n = ht->nNumUsed; if (HT_IS_PACKED(ht)) { @@ -1139,17 +1358,34 @@ static void gc_scan(zend_refcounted *ref, gc_stack *stack) static void gc_scan_roots(gc_stack *stack) { - gc_root_buffer *current = GC_IDX2PTR(GC_FIRST_ROOT); - gc_root_buffer *last = GC_IDX2PTR(GC_G(first_unused)); + uint32_t idx, end; + gc_root_buffer *current; - while (current != last) { + /* Root buffer might be reallocated during gc_scan, + * make sure to reload pointers. */ + idx = GC_FIRST_ROOT; + end = GC_G(first_unused); + while (idx != end) { + 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); } } - current++; + idx++; + } + + /* 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++; } } @@ -1209,6 +1445,50 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta || obj->ce->destructor != NULL)) { *flags |= GC_HAS_DESTRUCTORS; } + + if (UNEXPECTED(GC_FLAGS(obj) & IS_OBJ_WEAKLY_REFERENCED)) { + zend_weakmap_get_object_entry_gc(obj, &table, &len); + n = len; + zv = table; + 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)) { + GC_UNSET_FROM_WEAKMAP_KEY(entry); + GC_UNSET_FROM_WEAKMAP(entry); + ref = Z_COUNTED_P(entry); + GC_ADDREF(ref); + if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) { + GC_REF_SET_BLACK(ref); + GC_STACK_PUSH(ref); + } + } + zv++; + } + } + + if (UNEXPECTED(obj->handlers->get_gc == zend_weakmap_get_gc)) { + zend_weakmap_get_entry_gc(obj, &table, &len); + n = len; + zv = table; + 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)) { + GC_UNSET_FROM_WEAKMAP_KEY(entry); + GC_UNSET_FROM_WEAKMAP(entry); + ref = Z_COUNTED_P(entry); + GC_ADDREF(ref); + if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) { + GC_REF_SET_BLACK(ref); + GC_STACK_PUSH(ref); + } + } + zv++; + } + goto next; + } + ht = obj->handlers->get_gc(obj, &table, &len); n = len; zv = table; @@ -1231,7 +1511,7 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta } } -handle_zvals: +handle_zvals: for (; n != 0; n--) { if (Z_REFCOUNTED_P(zv)) { ref = Z_COUNTED_P(zv); @@ -1313,6 +1593,7 @@ static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *sta } } +next: ref = GC_STACK_POP(); if (ref) { goto tail_call; @@ -1396,6 +1677,21 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe int len; zval *table; + if (UNEXPECTED(GC_FLAGS(obj) & IS_OBJ_WEAKLY_REFERENCED)) { + zend_weakmap_get_object_entry_gc(obj, &table, &len); + n = len; + zv = table; + 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)) { + ref = Z_COUNTED_P(entry); + GC_STACK_PUSH(ref); + } + zv++; + } + } + ht = obj->handlers->get_gc(obj, &table, &len); n = len; zv = table; @@ -1472,7 +1768,7 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe if (ref) { goto tail_call; } - + return count; } diff --git a/Zend/zend_gc.h b/Zend/zend_gc.h index 9d0cca57f851b..84519aa68ca96 100644 --- a/Zend/zend_gc.h +++ b/Zend/zend_gc.h @@ -140,6 +140,15 @@ static zend_always_inline void zend_get_gc_buffer_add_obj( gc_buffer->cur++; } +static zend_always_inline void zend_get_gc_buffer_add_ptr( + zend_get_gc_buffer *gc_buffer, void *ptr) { + if (UNEXPECTED(gc_buffer->cur == gc_buffer->end)) { + zend_get_gc_buffer_grow(gc_buffer); + } + ZVAL_PTR(gc_buffer->cur, ptr); + gc_buffer->cur++; +} + static zend_always_inline void zend_get_gc_buffer_use( zend_get_gc_buffer *gc_buffer, zval **table, int *n) { *table = gc_buffer->start; diff --git a/Zend/zend_types.h b/Zend/zend_types.h index af5f3821723fa..df9bff9015eac 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -666,6 +666,7 @@ static zend_always_inline uint8_t zval_get_type(const zval* pz) { #define Z_TYPE_FLAGS_MASK 0xff00 #define Z_TYPE_FLAGS_SHIFT 8 +#define Z_TYPE_INFO_EXTRA_SHIFT 16 #define GC_REFCOUNT(p) zend_gc_refcount(&(p)->gc) #define GC_SET_REFCOUNT(p, rc) zend_gc_set_refcount(&(p)->gc, rc) diff --git a/Zend/zend_weakrefs.c b/Zend/zend_weakrefs.c index db95b13a7254a..442b19fb62adb 100644 --- a/Zend/zend_weakrefs.c +++ b/Zend/zend_weakrefs.c @@ -17,6 +17,7 @@ #include "zend.h" #include "zend_interfaces.h" #include "zend_objects_API.h" +#include "zend_types.h" #include "zend_weakrefs.h" #include "zend_weakrefs_arginfo.h" @@ -460,7 +461,7 @@ static HashTable *zend_weakmap_get_properties_for(zend_object *object, zend_prop return ht; } -static HashTable *zend_weakmap_get_gc(zend_object *object, zval **table, int *n) +HashTable *zend_weakmap_get_gc(zend_object *object, zval **table, int *n) { zend_weakmap *wm = zend_weakmap_from(object); zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); @@ -472,6 +473,101 @@ static HashTable *zend_weakmap_get_gc(zend_object *object, zval **table, int *n) return NULL; } +HashTable *zend_weakmap_get_key_entry_gc(zend_object *object, zval **table, int *n) +{ + zend_weakmap *wm = zend_weakmap_from(object); + zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); + zend_ulong h; + zval *val; + ZEND_HASH_MAP_FOREACH_NUM_KEY_VAL(&wm->ht, h, val) { + zend_object *key = zend_weakref_key_to_object(h); + zend_get_gc_buffer_add_obj(gc_buffer, key); + zend_get_gc_buffer_add_ptr(gc_buffer, val); + } ZEND_HASH_FOREACH_END(); + zend_get_gc_buffer_use(gc_buffer, table, n); + return NULL; +} + +HashTable *zend_weakmap_get_entry_gc(zend_object *object, zval **table, int *n) +{ + zend_weakmap *wm = zend_weakmap_from(object); + zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); + zval *val; + ZEND_HASH_MAP_FOREACH_VAL(&wm->ht, val) { + zend_get_gc_buffer_add_ptr(gc_buffer, val); + } ZEND_HASH_FOREACH_END(); + zend_get_gc_buffer_use(gc_buffer, table, n); + return NULL; +} + +HashTable *zend_weakmap_get_object_key_entry_gc(zend_object *object, zval **table, int *n) +{ + zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); + const zend_ulong obj_key = zend_object_to_weakref_key(object); + void *tagged_ptr = zend_hash_index_find_ptr(&EG(weakrefs), obj_key); +#if ZEND_DEBUG + ZEND_ASSERT(tagged_ptr && "Tracking of the IS_OBJ_WEAKLY_REFERENCE flag should be precise"); +#endif + void *ptr = ZEND_WEAKREF_GET_PTR(tagged_ptr); + uintptr_t tag = ZEND_WEAKREF_GET_TAG(tagged_ptr); + + if (tag == ZEND_WEAKREF_TAG_HT) { + HashTable *ht = ptr; + ZEND_HASH_MAP_FOREACH_PTR(ht, tagged_ptr) { + if (ZEND_WEAKREF_GET_TAG(tagged_ptr) == ZEND_WEAKREF_TAG_MAP) { + zend_weakmap *wm = (zend_weakmap*) ZEND_WEAKREF_GET_PTR(tagged_ptr); + zval *zv = zend_hash_index_find(&wm->ht, obj_key); + ZEND_ASSERT(zv); + zend_get_gc_buffer_add_ptr(gc_buffer, zv); + zend_get_gc_buffer_add_obj(gc_buffer, &wm->std); + } + } ZEND_HASH_FOREACH_END(); + } else if (tag == ZEND_WEAKREF_TAG_MAP) { + zend_weakmap *wm = (zend_weakmap*) ptr; + zval *zv = zend_hash_index_find(&wm->ht, obj_key); + ZEND_ASSERT(zv); + zend_get_gc_buffer_add_ptr(gc_buffer, zv); + zend_get_gc_buffer_add_obj(gc_buffer, &wm->std); + } + + zend_get_gc_buffer_use(gc_buffer, table, n); + + return NULL; +} + +HashTable *zend_weakmap_get_object_entry_gc(zend_object *object, zval **table, int *n) +{ + zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); + const zend_ulong obj_key = zend_object_to_weakref_key(object); + void *tagged_ptr = zend_hash_index_find_ptr(&EG(weakrefs), obj_key); +#if ZEND_DEBUG + ZEND_ASSERT(tagged_ptr && "Tracking of the IS_OBJ_WEAKLY_REFERENCE flag should be precise"); +#endif + void *ptr = ZEND_WEAKREF_GET_PTR(tagged_ptr); + uintptr_t tag = ZEND_WEAKREF_GET_TAG(tagged_ptr); + + if (tag == ZEND_WEAKREF_TAG_HT) { + HashTable *ht = ptr; + ZEND_HASH_MAP_FOREACH_PTR(ht, tagged_ptr) { + if (ZEND_WEAKREF_GET_TAG(tagged_ptr) == ZEND_WEAKREF_TAG_MAP) { + zend_weakmap *wm = (zend_weakmap*) ZEND_WEAKREF_GET_PTR(tagged_ptr); + zval *zv = zend_hash_index_find(&wm->ht, obj_key); + ZEND_ASSERT(zv); + zend_get_gc_buffer_add_ptr(gc_buffer, zv); + } + } ZEND_HASH_FOREACH_END(); + } else if (tag == ZEND_WEAKREF_TAG_MAP) { + zend_weakmap *wm = (zend_weakmap*) ptr; + zval *zv = zend_hash_index_find(&wm->ht, obj_key); + ZEND_ASSERT(zv); + zend_get_gc_buffer_add_ptr(gc_buffer, zv); + } + + zend_get_gc_buffer_use(gc_buffer, table, n); + + return NULL; +} + static zend_object *zend_weakmap_clone_obj(zend_object *old_object) { zend_object *new_object = zend_weakmap_create_object(zend_ce_weakmap); diff --git a/Zend/zend_weakrefs.h b/Zend/zend_weakrefs.h index 506e2e9d40c5c..5b1c8ea1c325d 100644 --- a/Zend/zend_weakrefs.h +++ b/Zend/zend_weakrefs.h @@ -62,6 +62,12 @@ static zend_always_inline zend_object *zend_weakref_key_to_object(zend_ulong key return (zend_object *) (((uintptr_t) key) << ZEND_MM_ALIGNMENT_LOG2); } +HashTable *zend_weakmap_get_gc(zend_object *object, zval **table, int *n); +HashTable *zend_weakmap_get_key_entry_gc(zend_object *object, zval **table, int *n); +HashTable *zend_weakmap_get_entry_gc(zend_object *object, zval **table, int *n); +HashTable *zend_weakmap_get_object_key_entry_gc(zend_object *object, zval **table, int *n); +HashTable *zend_weakmap_get_object_entry_gc(zend_object *object, zval **table, int *n); + END_EXTERN_C() #endif