ZEND_API void ZEND_FASTCALL zend_objects_store_free_object_storage(zend_objects_store *objects, zend_bool fast_shutdown) { zend_object **obj_ptr, **end, *obj; if (objects->top <= 1) { return; } /* Free object contents, but don't free objects themselves, so they show up as leaks */ end = objects->object_buckets + 1; obj_ptr = objects->object_buckets + objects->top; if (fast_shutdown) { do { obj_ptr--; obj = *obj_ptr; if (IS_OBJ_VALID(obj)) { if (!(OBJ_FLAGS(obj) & IS_OBJ_FREE_CALLED)) { GC_ADD_FLAGS(obj, IS_OBJ_FREE_CALLED); if (obj->handlers->free_obj && obj->handlers->free_obj != zend_object_std_dtor) { GC_ADDREF(obj); obj->handlers->free_obj(obj); GC_DELREF(obj); } } } } while (obj_ptr != end); } else { do { obj_ptr--; obj = *obj_ptr; if (IS_OBJ_VALID(obj)) { if (!(OBJ_FLAGS(obj) & IS_OBJ_FREE_CALLED)) { GC_ADD_FLAGS(obj, IS_OBJ_FREE_CALLED); if (obj->handlers->free_obj) { GC_ADDREF(obj); obj->handlers->free_obj(obj); GC_DELREF(obj); } } } } while (obj_ptr != end); } }
ZEND_API void ZEND_FASTCALL zend_objects_store_del(zend_object *object) /* {{{ */ { ZEND_ASSERT(GC_REFCOUNT(object) == 0); /* GC might have released this object already. */ if (UNEXPECTED(GC_TYPE(object) == IS_NULL)) { return; } /* Make sure we hold a reference count during the destructor call otherwise, when the destructor ends the storage might be freed when the refcount reaches 0 a second time */ if (!(OBJ_FLAGS(object) & IS_OBJ_DESTRUCTOR_CALLED)) { GC_ADD_FLAGS(object, IS_OBJ_DESTRUCTOR_CALLED); if (object->handlers->dtor_obj != zend_objects_destroy_object || object->ce->destructor) { GC_SET_REFCOUNT(object, 1); object->handlers->dtor_obj(object); GC_DELREF(object); } } if (GC_REFCOUNT(object) == 0) { uint32_t handle = object->handle; void *ptr; ZEND_ASSERT(EG(objects_store).object_buckets != NULL); ZEND_ASSERT(IS_OBJ_VALID(EG(objects_store).object_buckets[handle])); EG(objects_store).object_buckets[handle] = SET_OBJ_INVALID(object); if (!(OBJ_FLAGS(object) & IS_OBJ_FREE_CALLED)) { GC_ADD_FLAGS(object, IS_OBJ_FREE_CALLED); GC_SET_REFCOUNT(object, 1); object->handlers->free_obj(object); } ptr = ((char*)object) - object->handlers->offset; GC_REMOVE_FROM_BUFFER(object); efree(ptr); ZEND_OBJECTS_STORE_ADD_TO_FREE_LIST(handle); } }
ZEND_API void ZEND_FASTCALL zend_objects_store_mark_destructed(zend_objects_store *objects) { if (objects->object_buckets && objects->top > 1) { zend_object **obj_ptr = objects->object_buckets + 1; zend_object **end = objects->object_buckets + objects->top; do { zend_object *obj = *obj_ptr; if (IS_OBJ_VALID(obj)) { GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); } obj_ptr++; } while (obj_ptr != end); } }
ZEND_API void ZEND_FASTCALL zend_objects_store_call_destructors(zend_objects_store *objects) { if (objects->top > 1) { uint32_t i; for (i = 1; i < objects->top; i++) { zend_object *obj = objects->object_buckets[i]; if (IS_OBJ_VALID(obj)) { if (!(OBJ_FLAGS(obj) & IS_OBJ_DESTRUCTOR_CALLED)) { GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); if (obj->handlers->dtor_obj != zend_objects_destroy_object || obj->ce->destructor) { GC_ADDREF(obj); obj->handlers->dtor_obj(obj); GC_DELREF(obj); } } } } } }
ZEND_API int zend_gc_collect_cycles(void) { int count = 0; if (GC_G(num_roots)) { gc_root_buffer *current, *last; zend_refcounted *p; uint32_t gc_flags = 0; uint32_t idx, end; gc_stack stack; stack.prev = NULL; stack.next = NULL; if (GC_G(gc_active)) { return 0; } GC_TRACE("Collecting cycles"); GC_G(gc_runs)++; GC_G(gc_active) = 1; GC_TRACE("Marking roots"); gc_mark_roots(&stack); GC_TRACE("Scanning roots"); gc_scan_roots(&stack); GC_TRACE("Collecting roots"); count = gc_collect_roots(&gc_flags, &stack); gc_stack_free(&stack); if (!GC_G(num_roots)) { /* nothing to free */ GC_TRACE("Nothing to free"); GC_G(gc_active) = 0; return 0; } end = GC_G(first_unused); if (gc_flags & GC_HAS_DESTRUCTORS) { uint32_t *refcounts; GC_TRACE("Calling destructors"); // TODO: may be use emalloc() ??? refcounts = pemalloc(sizeof(uint32_t) * end, 1); /* Remember reference counters before calling destructors */ idx = GC_FIRST_ROOT; current = GC_IDX2PTR(GC_FIRST_ROOT); while (idx != end) { if (GC_IS_GARBAGE(current->ref)) { p = GC_GET_PTR(current->ref); refcounts[idx] = GC_REFCOUNT(p); } current++; idx++; } /* Call destructors * * The root buffer might be reallocated during destructors calls, * make sure to reload pointers as necessary. */ idx = GC_FIRST_ROOT; while (idx != end) { current = GC_IDX2PTR(idx); if (GC_IS_GARBAGE(current->ref)) { p = GC_GET_PTR(current->ref); if (GC_TYPE(p) == IS_OBJECT && !(OBJ_FLAGS(p) & IS_OBJ_DESTRUCTOR_CALLED)) { zend_object *obj = (zend_object*)p; GC_TRACE_REF(obj, "calling destructor"); GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); if (obj->handlers->dtor_obj != zend_objects_destroy_object || obj->ce->destructor) { GC_ADDREF(obj); obj->handlers->dtor_obj(obj); GC_DELREF(obj); } } } idx++; } /* Remove values captured in destructors */ idx = GC_FIRST_ROOT; current = GC_IDX2PTR(GC_FIRST_ROOT); while (idx != end) { if (GC_IS_GARBAGE(current->ref)) { p = GC_GET_PTR(current->ref); if (GC_REFCOUNT(p) > refcounts[idx]) { gc_remove_nested_data_from_buffer(p, current); } } current++; idx++; } pefree(refcounts, 1); if (GC_G(gc_protected)) { /* something went wrong */ return 0; } } /* Destroy zvals */ GC_TRACE("Destroying zvals"); GC_G(gc_protected) = 1; current = GC_IDX2PTR(GC_FIRST_ROOT); last = GC_IDX2PTR(GC_G(first_unused)); while (current != last) { if (GC_IS_GARBAGE(current->ref)) { p = GC_GET_PTR(current->ref); GC_TRACE_REF(p, "destroying"); if (GC_TYPE(p) == IS_OBJECT) { zend_object *obj = (zend_object*)p; EG(objects_store).object_buckets[obj->handle] = SET_OBJ_INVALID(obj); GC_TYPE_INFO(obj) = IS_NULL | (GC_TYPE_INFO(obj) & ~GC_TYPE_MASK); if (!(OBJ_FLAGS(obj) & IS_OBJ_FREE_CALLED)) { GC_ADD_FLAGS(obj, IS_OBJ_FREE_CALLED); GC_ADDREF(obj); obj->handlers->free_obj(obj); GC_DELREF(obj); } ZEND_OBJECTS_STORE_ADD_TO_FREE_LIST(obj->handle); current->ref = GC_MAKE_GARBAGE(((char*)obj) - obj->handlers->offset); } else if (GC_TYPE(p) == IS_ARRAY) { zend_array *arr = (zend_array*)p; GC_TYPE_INFO(arr) = IS_NULL | (GC_TYPE_INFO(arr) & ~GC_TYPE_MASK); /* GC may destroy arrays with rc>1. This is valid and safe. */ HT_ALLOW_COW_VIOLATION(arr); zend_hash_destroy(arr); } } current++; } /* Free objects */ current = GC_IDX2PTR(GC_FIRST_ROOT); while (current != last) { if (GC_IS_GARBAGE(current->ref)) { p = GC_GET_PTR(current->ref); GC_LINK_UNUSED(current); GC_G(num_roots)--; efree(p); } current++; } GC_TRACE("Collection finished"); GC_G(collected) += count; GC_G(gc_protected) = 0; GC_G(gc_active) = 0; } gc_compact(); return count; }