ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref) { uint32_t idx; gc_root_buffer *newRoot; if (UNEXPECTED(GC_G(gc_protected))) { return; } GC_BENCH_INC(zval_possible_root); 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_possible_root_when_full(ref); return; } ZEND_ASSERT(GC_TYPE(ref) == IS_ARRAY || GC_TYPE(ref) == IS_OBJECT); ZEND_ASSERT(GC_INFO(ref) == 0); newRoot = GC_IDX2PTR(idx); newRoot->ref = ref; /* GC_ROOT tag is 0 */ GC_TRACE_SET_COLOR(ref, GC_PURPLE); idx = gc_compress(idx); GC_REF_SET_INFO(ref, idx | GC_PURPLE); 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_possible_root_when_full(zend_refcounted *ref) { uint32_t idx; gc_root_buffer *newRoot; ZEND_ASSERT(GC_TYPE(ref) == IS_ARRAY || GC_TYPE(ref) == IS_OBJECT); ZEND_ASSERT(GC_INFO(ref) == 0); if (GC_G(gc_enabled) && !GC_G(gc_active)) { GC_ADDREF(ref); gc_adjust_threshold(gc_collect_cycles()); if (UNEXPECTED(GC_DELREF(ref)) == 0) { rc_dtor_func(ref); return; } else if (UNEXPECTED(GC_INFO(ref))) { return; } } if (GC_HAS_UNUSED()) { idx = GC_FETCH_UNUSED(); } else if (EXPECTED(GC_HAS_NEXT_UNUSED())) { idx = GC_FETCH_NEXT_UNUSED(); } else { gc_grow_root_buffer(); if (UNEXPECTED(!GC_HAS_NEXT_UNUSED())) { return; } idx = GC_FETCH_NEXT_UNUSED(); } newRoot = GC_IDX2PTR(idx); newRoot->ref = ref; /* GC_ROOT tag is 0 */ GC_TRACE_SET_COLOR(ref, GC_PURPLE); idx = gc_compress(idx); GC_REF_SET_INFO(ref, idx | GC_PURPLE); 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 void gc_trace_ref(zend_refcounted *ref) { if (GC_TYPE(ref) == IS_OBJECT) { zend_object *obj = (zend_object *) ref; fprintf(stderr, "[%p] rc=%d addr=%d %s object(%s)#%d ", ref, GC_REFCOUNT(ref), GC_REF_ADDRESS(ref), gc_color_name(GC_REF_COLOR(ref)), obj->ce->name->val, obj->handle); } else if (GC_TYPE(ref) == IS_ARRAY) { zend_array *arr = (zend_array *) ref; fprintf(stderr, "[%p] rc=%d addr=%d %s array(%d) ", ref, GC_REFCOUNT(ref), GC_REF_ADDRESS(ref), gc_color_name(GC_REF_COLOR(ref)), zend_hash_num_elements(arr)); } else { fprintf(stderr, "[%p] rc=%d addr=%d %s %s ", ref, GC_REFCOUNT(ref), GC_REF_ADDRESS(ref), gc_color_name(GC_REF_COLOR(ref)), zend_get_type_by_const(GC_TYPE(ref))); } }
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); } }
int zephir_call_func_aparams_fast(zval *return_value_ptr, zephir_fcall_cache_entry **cache_entry, zend_uint param_count, zval *params[]) { uint32_t i; zend_class_entry *calling_scope = NULL; zend_execute_data *call, dummy_execute_data; zval retval_local; zval *retval_ptr = return_value_ptr ? return_value_ptr : &retval_local; zend_class_entry *orig_scope; zend_function *func; if (return_value_ptr) { zval_ptr_dtor(return_value_ptr); ZVAL_UNDEF(return_value_ptr); } else { ZVAL_UNDEF(&retval_local); } if (!EG(active)) { return FAILURE; /* executor is already inactive */ } if (EG(exception)) { return FAILURE; /* we would result in an instable executor otherwise */ } orig_scope = EG(scope); /* Initialize execute_data */ if (!EG(current_execute_data)) { /* This only happens when we're called outside any execute()'s * It shouldn't be strictly necessary to NULL execute_data out, * but it may make bugs easier to spot */ memset(&dummy_execute_data, 0, sizeof(zend_execute_data)); EG(current_execute_data) = &dummy_execute_data; } else if (EG(current_execute_data)->func && ZEND_USER_CODE(EG(current_execute_data)->func->common.type) && EG(current_execute_data)->opline->opcode != ZEND_DO_FCALL && EG(current_execute_data)->opline->opcode != ZEND_DO_ICALL && EG(current_execute_data)->opline->opcode != ZEND_DO_UCALL && EG(current_execute_data)->opline->opcode != ZEND_DO_FCALL_BY_NAME) { /* Insert fake frame in case of include or magic calls */ dummy_execute_data = *EG(current_execute_data); dummy_execute_data.prev_execute_data = EG(current_execute_data); dummy_execute_data.call = NULL; dummy_execute_data.opline = NULL; dummy_execute_data.func = NULL; EG(current_execute_data) = &dummy_execute_data; } #ifndef ZEPHIR_RELEASE func = (*cache_entry)->f; ++(*cache_entry)->times; #else func = *cache_entry; #endif calling_scope = NULL; call = zend_vm_stack_push_call_frame(ZEND_CALL_TOP_FUNCTION, func, param_count, NULL, NULL); for (i = 0; i < param_count; i++) { zval *param; zval *arg = params[i]; if (ARG_SHOULD_BE_SENT_BY_REF(func, i + 1)) { if (!Z_ISREF_P(arg)) { /*if (!ARG_MAY_BE_SENT_BY_REF(func, i + 1)) { if (i) { // hack to clean up the stack ZEND_CALL_NUM_ARGS(call) = i; zend_vm_stack_free_args(call); } zend_vm_stack_free_call_frame(call); zend_error(E_WARNING, "Parameter %d to %s%s%s() expected to be a reference, value given", i+1, func->common.scope ? ZSTR_VAL(func->common.scope->name) : "", func->common.scope ? "::" : "", ZSTR_VAL(func->common.function_name)); if (EG(current_execute_data) == &dummy_execute_data) { EG(current_execute_data) = dummy_execute_data.prev_execute_data; } return FAILURE; }*/ ZVAL_NEW_REF(arg, arg); } Z_ADDREF_P(arg); } else { if (Z_ISREF_P(arg) && !(func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { /* don't separate references for __call */ arg = Z_REFVAL_P(arg); } if (Z_OPT_REFCOUNTED_P(arg)) { Z_ADDREF_P(arg); } } param = ZEND_CALL_ARG(call, i+1); ZVAL_COPY_VALUE(param, arg); } EG(scope) = calling_scope; Z_OBJ(call->This) = NULL; if (func->type == ZEND_USER_FUNCTION) { int call_via_handler = (func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) != 0; EG(scope) = func->common.scope; call->symbol_table = NULL; if (UNEXPECTED(func->op_array.fn_flags & ZEND_ACC_CLOSURE)) { ZEND_ASSERT(GC_TYPE((zend_object*)func->op_array.prototype) == IS_OBJECT); GC_REFCOUNT((zend_object*)func->op_array.prototype)++; ZEND_ADD_CALL_FLAG(call, ZEND_CALL_CLOSURE); } if (EXPECTED((func->op_array.fn_flags & ZEND_ACC_GENERATOR) == 0)) { zend_init_execute_data(call, &func->op_array, retval_ptr); zend_execute_ex(call); } else { zend_generator_create_zval(call, &func->op_array, retval_ptr); } if (call_via_handler) { /* We must re-initialize function again */ *cache_entry = NULL; } } else if (func->type == ZEND_INTERNAL_FUNCTION) { int call_via_handler = (func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) != 0; if (func->common.scope) { EG(scope) = func->common.scope; } call->prev_execute_data = EG(current_execute_data); call->return_value = NULL; /* this is not a constructor call */ EG(current_execute_data) = call; if (EXPECTED(zend_execute_internal == NULL)) { /* saves one function call if zend_execute_internal is not used */ func->internal_function.handler(call, retval_ptr); } else { zend_execute_internal(call, retval_ptr); } EG(current_execute_data) = call->prev_execute_data; zend_vm_stack_free_args(call); /* We shouldn't fix bad extensions here, because it can break proper ones (Bug #34045) if (!EX(function_state).function->common.return_reference) { INIT_PZVAL(f->retval); }*/ if (EG(exception)) { zval_ptr_dtor(retval_ptr); ZVAL_UNDEF(retval_ptr); } if (call_via_handler) { /* We must re-initialize function again */ *cache_entry = NULL; } } else { /* ZEND_OVERLOADED_FUNCTION */ ZVAL_NULL(retval_ptr); zend_throw_error(NULL, "Cannot call overloaded function for non-object"); zend_vm_stack_free_args(call); if (func->type == ZEND_OVERLOADED_FUNCTION_TEMPORARY) { zend_string_release(func->common.function_name); } efree(func); if (EG(exception)) { zval_ptr_dtor(retval_ptr); ZVAL_UNDEF(retval_ptr); } } EG(scope) = orig_scope; zend_vm_stack_free_call_frame(call); if (EG(current_execute_data) == &dummy_execute_data) { EG(current_execute_data) = dummy_execute_data.prev_execute_data; } if (EG(exception)) { zend_throw_exception_internal(NULL); } return SUCCESS; }
/* IS_NULL */ (zend_rc_dtor_func_t)zend_empty_destroy, /* IS_FALSE */ (zend_rc_dtor_func_t)zend_empty_destroy, /* IS_TRUE */ (zend_rc_dtor_func_t)zend_empty_destroy, /* IS_LONG */ (zend_rc_dtor_func_t)zend_empty_destroy, /* IS_DOUBLE */ (zend_rc_dtor_func_t)zend_empty_destroy, /* IS_STRING */ (zend_rc_dtor_func_t)zend_string_destroy, /* IS_ARRAY */ (zend_rc_dtor_func_t)zend_array_destroy_wrapper, /* IS_OBJECT */ (zend_rc_dtor_func_t)zend_object_destroy_wrapper, /* IS_RESOURCE */ (zend_rc_dtor_func_t)zend_resource_destroy_wrapper, /* IS_REFERENCE */ (zend_rc_dtor_func_t)zend_reference_destroy, /* IS_CONSTANT_AST */ (zend_rc_dtor_func_t)zend_ast_ref_destroy_wrapper }; ZEND_API void ZEND_FASTCALL _rc_dtor_func(zend_refcounted *p ZEND_FILE_LINE_DC) { ZEND_ASSERT(GC_TYPE(p) <= IS_CONSTANT_AST); zend_rc_dtor_func[GC_TYPE(p)](p ZEND_FILE_LINE_RELAY_CC); } static void ZEND_FASTCALL zend_string_destroy(zend_string *str ZEND_FILE_LINE_DC) { CHECK_ZVAL_STRING_REL(str); ZEND_ASSERT(!ZSTR_IS_INTERNED(str)); ZEND_ASSERT(GC_REFCOUNT(str) == 0); ZEND_ASSERT(!(GC_FLAGS(str) & IS_STR_PERSISTENT)); efree(str); } static void ZEND_FASTCALL zend_reference_destroy(zend_reference *ref ZEND_FILE_LINE_DC) { i_zval_ptr_dtor(&ref->val ZEND_FILE_LINE_RELAY_CC);
ZEND_API void ZEND_FASTCALL rc_dtor_func(zend_refcounted *p) { ZEND_ASSERT(GC_TYPE(p) <= IS_CONSTANT_AST); zend_rc_dtor_func[GC_TYPE(p)](p); }
static void gc_scan(zend_refcounted *ref, gc_stack *stack) { HashTable *ht = NULL; Bucket *p, *end; zval *zv; GC_STACK_DCL(stack); tail_call: if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) { if (GC_REFCOUNT(ref) > 0) { if (!GC_REF_CHECK_COLOR(ref, GC_BLACK)) { GC_REF_SET_BLACK(ref); if (UNEXPECTED(!_stack->next)) { gc_stack_next(_stack); } /* Split stack and reuse the tail */ _stack->next->prev = NULL; gc_scan_black(ref, _stack->next); _stack->next->prev = _stack; } } else { if (GC_TYPE(ref) == IS_OBJECT) { zend_object *obj = (zend_object*)ref; if (EXPECTED(!(OBJ_FLAGS(ref) & IS_OBJ_FREE_CALLED))) { int n; zval *zv, *end; ht = obj->handlers->get_gc(obj, &zv, &n); end = zv + n; if (EXPECTED(!ht)) { if (!n) goto next; while (!Z_REFCOUNTED_P(--end)) { if (zv == end) goto next; } } while (zv != end) { if (Z_REFCOUNTED_P(zv)) { ref = Z_COUNTED_P(zv); if (GC_REF_CHECK_COLOR(ref, GC_GREY)) { GC_REF_SET_COLOR(ref, GC_WHITE); GC_STACK_PUSH(ref); } } zv++; } if (EXPECTED(!ht)) { ref = Z_COUNTED_P(zv); if (GC_REF_CHECK_COLOR(ref, GC_GREY)) { GC_REF_SET_COLOR(ref, GC_WHITE); goto tail_call; } goto next; } } else { goto next; } } else if (GC_TYPE(ref) == IS_ARRAY) { if ((zend_array*)ref == &EG(symbol_table)) { GC_REF_SET_BLACK(ref); goto next; } else { ht = (zend_array*)ref; } } else if (GC_TYPE(ref) == IS_REFERENCE) { if (Z_REFCOUNTED(((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); goto tail_call; } } goto next; } else { goto next; } if (!ht->nNumUsed) goto next; p = ht->arData; end = p + ht->nNumUsed; while (1) { end--; zv = &end->val; if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } if (Z_REFCOUNTED_P(zv)) { break; } if (p == end) goto next; } while (p != end) { zv = &p->val; if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } if (Z_REFCOUNTED_P(zv)) { ref = Z_COUNTED_P(zv); if (GC_REF_CHECK_COLOR(ref, GC_GREY)) { GC_REF_SET_COLOR(ref, GC_WHITE); GC_STACK_PUSH(ref); } } p++; } zv = &p->val; if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } ref = Z_COUNTED_P(zv); if (GC_REF_CHECK_COLOR(ref, GC_GREY)) { GC_REF_SET_COLOR(ref, GC_WHITE); goto tail_call; } } } next: ref = GC_STACK_POP(); if (ref) { goto tail_call; } }
static void gc_mark_grey(zend_refcounted *ref, gc_stack *stack) { HashTable *ht = NULL; Bucket *p, *end; zval *zv; GC_STACK_DCL(stack); do { GC_BENCH_INC(zval_marked_grey); if (GC_TYPE(ref) == IS_OBJECT) { zend_object *obj = (zend_object*)ref; if (EXPECTED(!(OBJ_FLAGS(ref) & IS_OBJ_FREE_CALLED))) { int n; zval *zv, *end; ht = obj->handlers->get_gc(obj, &zv, &n); end = zv + n; if (EXPECTED(!ht)) { if (!n) goto next; while (!Z_REFCOUNTED_P(--end)) { if (zv == end) goto next; } } while (zv != end) { if (Z_REFCOUNTED_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); GC_STACK_PUSH(ref); } } zv++; } if (EXPECTED(!ht)) { ref = Z_COUNTED_P(zv); GC_DELREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) { GC_REF_SET_COLOR(ref, GC_GREY); continue; } goto next; } } else { goto next; } } else if (GC_TYPE(ref) == IS_ARRAY) { if (((zend_array*)ref) == &EG(symbol_table)) { GC_REF_SET_BLACK(ref); goto next; } else { ht = (zend_array*)ref; } } else if (GC_TYPE(ref) == IS_REFERENCE) { if (Z_REFCOUNTED(((zend_reference*)ref)->val)) { ref = Z_COUNTED(((zend_reference*)ref)->val); GC_DELREF(ref); if (!GC_REF_CHECK_COLOR(ref, GC_GREY)) { GC_REF_SET_COLOR(ref, GC_GREY); continue; } } goto next; } else { goto next; } if (!ht->nNumUsed) goto next; p = ht->arData; end = p + ht->nNumUsed; while (1) { end--; zv = &end->val; if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } if (Z_REFCOUNTED_P(zv)) { break; } if (p == end) goto next; } while (p != end) { zv = &p->val; if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } if (Z_REFCOUNTED_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); GC_STACK_PUSH(ref); } } p++; } zv = &p->val; if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_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); continue; } next: ref = GC_STACK_POP(); } while (ref); }
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; }
static void gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffer *root) { HashTable *ht = NULL; Bucket *p, *end; zval *zv; tail_call: if (root || (GC_REF_ADDRESS(ref) != 0 && GC_REF_CHECK_COLOR(ref, GC_BLACK))) { GC_TRACE_REF(ref, "removing from buffer"); if (root) { gc_remove_from_roots(root); GC_REF_SET_INFO(ref, 0); root = NULL; } else { GC_REMOVE_FROM_BUFFER(ref); } if (GC_TYPE(ref) == IS_OBJECT) { zend_object *obj = (zend_object*)ref; if (EXPECTED(!(OBJ_FLAGS(ref) & IS_OBJ_FREE_CALLED))) { int n; zval *zv, *end; ht = obj->handlers->get_gc(obj, &zv, &n); end = zv + n; if (EXPECTED(!ht)) { if (!n) return; while (!Z_REFCOUNTED_P(--end)) { if (zv == end) return; } } while (zv != end) { if (Z_REFCOUNTED_P(zv)) { ref = Z_COUNTED_P(zv); gc_remove_nested_data_from_buffer(ref, NULL); } zv++; } if (EXPECTED(!ht)) { ref = Z_COUNTED_P(zv); goto tail_call; } } else { return; } } else if (GC_TYPE(ref) == IS_ARRAY) { ht = (zend_array*)ref; } else if (GC_TYPE(ref) == IS_REFERENCE) { if (Z_REFCOUNTED(((zend_reference*)ref)->val)) { ref = Z_COUNTED(((zend_reference*)ref)->val); goto tail_call; } return; } else { return; } if (!ht->nNumUsed) return; p = ht->arData; end = p + ht->nNumUsed; while (1) { end--; zv = &end->val; if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } if (Z_REFCOUNTED_P(zv)) { break; } if (p == end) return; } while (p != end) { zv = &p->val; if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } if (Z_REFCOUNTED_P(zv)) { ref = Z_COUNTED_P(zv); gc_remove_nested_data_from_buffer(ref, NULL); } p++; } zv = &p->val; if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } ref = Z_COUNTED_P(zv); goto tail_call; } }
static int gc_collect_white(zend_refcounted *ref, uint32_t *flags, gc_stack *stack) { int count = 0; HashTable *ht = NULL; Bucket *p, *end; zval *zv; GC_STACK_DCL(stack); do { /* don't count references for compatibility ??? */ if (GC_TYPE(ref) != IS_REFERENCE) { count++; } if (GC_TYPE(ref) == IS_OBJECT) { zend_object *obj = (zend_object*)ref; if (EXPECTED(!(OBJ_FLAGS(ref) & IS_OBJ_FREE_CALLED))) { int n; zval *zv, *end; /* optimization: color is GC_BLACK (0) */ if (!GC_INFO(ref)) { gc_add_garbage(ref); } if (obj->handlers->dtor_obj != zend_objects_destroy_object || obj->ce->destructor != NULL) { *flags |= GC_HAS_DESTRUCTORS; } ht = obj->handlers->get_gc(obj, &zv, &n); end = zv + n; if (EXPECTED(!ht)) { if (!n) goto next; while (!Z_REFCOUNTED_P(--end)) { /* count non-refcounted for compatibility ??? */ if (Z_TYPE_P(zv) != IS_UNDEF) { count++; } if (zv == end) goto next; } } while (zv != end) { if (Z_REFCOUNTED_P(zv)) { ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) { GC_REF_SET_BLACK(ref); GC_STACK_PUSH(ref); } /* count non-refcounted for compatibility ??? */ } else if (Z_TYPE_P(zv) != IS_UNDEF) { count++; } zv++; } if (EXPECTED(!ht)) { ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) { GC_REF_SET_BLACK(ref); continue; } goto next; } } else { goto next; } } else if (GC_TYPE(ref) == IS_ARRAY) { /* optimization: color is GC_BLACK (0) */ if (!GC_INFO(ref)) { gc_add_garbage(ref); } ht = (zend_array*)ref; } else if (GC_TYPE(ref) == IS_REFERENCE) { if (Z_REFCOUNTED(((zend_reference*)ref)->val)) { ref = Z_COUNTED(((zend_reference*)ref)->val); GC_ADDREF(ref); if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) { GC_REF_SET_BLACK(ref); continue; } } goto next; } else { goto next; } if (!ht->nNumUsed) goto next; p = ht->arData; end = p + ht->nNumUsed; while (1) { end--; zv = &end->val; if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } if (Z_REFCOUNTED_P(zv)) { break; } /* count non-refcounted for compatibility ??? */ if (Z_TYPE_P(zv) != IS_UNDEF) { count++; } if (p == end) goto next; } while (p != end) { zv = &p->val; if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } if (Z_REFCOUNTED_P(zv)) { ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) { GC_REF_SET_BLACK(ref); GC_STACK_PUSH(ref); } /* count non-refcounted for compatibility ??? */ } else if (Z_TYPE_P(zv) != IS_UNDEF) { count++; } p++; } zv = &p->val; if (Z_TYPE_P(zv) == IS_INDIRECT) { zv = Z_INDIRECT_P(zv); } ref = Z_COUNTED_P(zv); GC_ADDREF(ref); if (GC_REF_CHECK_COLOR(ref, GC_WHITE)) { GC_REF_SET_BLACK(ref); continue; } next: ref = GC_STACK_POP(); } while (ref); return count; }
/** * Copies of internal methods from Zend/zend_execute_API.c * These are used to call internal methods (not in the function table) from the external method. * TODO: See if xdebug works */ int runkit_forward_call_user_function(zend_function *fbc, zend_function *fbc_inner, INTERNAL_FUNCTION_PARAMETERS) /* {{{ */ { uint32_t i; zend_execute_data *call, dummy_execute_data; zend_fcall_info_cache fci_cache_local = {0}; zend_function *func; /* {{{ patch for runkit */ zend_fcall_info fci = {0}; zend_fcall_info_cache *fci_cache = NULL; fci.size = sizeof(fci); fci.object = NULL; // FIXME for methods? // object ? Z_OBJ_P(object) : NULL; ZVAL_STR(&fci.function_name, fbc_inner->common.function_name); zend_string_addref(fbc_inner->common.function_name); fci.retval = return_value; fci.param_count = ZEND_CALL_NUM_ARGS(EG(current_execute_data)); fci.params = ZEND_CALL_ARG(EG(current_execute_data), 1); // params and param_count From zend_API.c fci.no_separation = (zend_bool)1; // ??? /* end patch for runkit }}} */ ZVAL_UNDEF(fci.retval); if (!EG(active)) { return FAILURE; /* executor is already inactive */ } if (EG(exception)) { return FAILURE; /* we would result in an unstable executor otherwise */ } /* Initialize execute_data */ if (!EG(current_execute_data)) { /* This only happens when we're called outside any execute()'s * It shouldn't be strictly necessary to NULL execute_data out, * but it may make bugs easier to spot */ memset(&dummy_execute_data, 0, sizeof(zend_execute_data)); EG(current_execute_data) = &dummy_execute_data; } else if (EG(current_execute_data)->func && ZEND_USER_CODE(EG(current_execute_data)->func->common.type) && EG(current_execute_data)->opline->opcode != ZEND_DO_FCALL && EG(current_execute_data)->opline->opcode != ZEND_DO_ICALL && EG(current_execute_data)->opline->opcode != ZEND_DO_UCALL && EG(current_execute_data)->opline->opcode != ZEND_DO_FCALL_BY_NAME) { /* Insert fake frame in case of include or magic calls */ dummy_execute_data = *EG(current_execute_data); dummy_execute_data.prev_execute_data = EG(current_execute_data); dummy_execute_data.call = NULL; dummy_execute_data.opline = NULL; dummy_execute_data.func = NULL; EG(current_execute_data) = &dummy_execute_data; } if (!fci_cache || !RUNKIT_IS_FCI_CACHE_INITIALIZED(fci_cache)) { zend_string *callable_name; char *error = NULL; if (!fci_cache) { fci_cache = &fci_cache_local; } if (!zend_is_callable_ex(&fci.function_name, fci.object, IS_CALLABLE_CHECK_SILENT, &callable_name, fci_cache, &error)) { if (error) { zend_error(E_WARNING, "Invalid callback %s, %s", ZSTR_VAL(callable_name), error); efree(error); } if (callable_name) { zend_string_release(callable_name); } if (EG(current_execute_data) == &dummy_execute_data) { EG(current_execute_data) = dummy_execute_data.prev_execute_data; } return FAILURE; } else if (error) { /* Capitalize the first latter of the error message */ if (error[0] >= 'a' && error[0] <= 'z') { error[0] += ('A' - 'a'); } zend_error(E_DEPRECATED, "%s", error); efree(error); } zend_string_release(callable_name); } func = fbc_inner; fci.object = (func->common.fn_flags & ZEND_ACC_STATIC) ? NULL : fci_cache->object; call = zend_vm_stack_push_call_frame(ZEND_CALL_TOP_FUNCTION | ZEND_CALL_DYNAMIC, func, fci.param_count, fci_cache->called_scope, fci.object); if (fci.object && (!EG(objects_store).object_buckets || !IS_OBJ_VALID(EG(objects_store).object_buckets[fci.object->handle]))) { if (EG(current_execute_data) == &dummy_execute_data) { EG(current_execute_data) = dummy_execute_data.prev_execute_data; } return FAILURE; } if (func->common.fn_flags & (ZEND_ACC_ABSTRACT | ZEND_ACC_DEPRECATED)) { if (func->common.fn_flags & ZEND_ACC_ABSTRACT) { zend_throw_error(NULL, "Cannot call abstract method %s::%s()", ZSTR_VAL(func->common.scope->name), ZSTR_VAL(func->common.function_name)); if (EG(current_execute_data) == &dummy_execute_data) { EG(current_execute_data) = dummy_execute_data.prev_execute_data; } return FAILURE; } if (func->common.fn_flags & ZEND_ACC_DEPRECATED) { zend_error(E_DEPRECATED, "Function %s%s%s() is deprecated", func->common.scope ? ZSTR_VAL(func->common.scope->name) : "", func->common.scope ? "::" : "", ZSTR_VAL(func->common.function_name)); } } for (i = 0; i < fci.param_count; i++) { zval *param; zval *arg = &fci.params[i]; if (ARG_SHOULD_BE_SENT_BY_REF(func, i + 1)) { if (UNEXPECTED(!Z_ISREF_P(arg))) { if (!fci.no_separation) { /* Separation is enabled -- create a ref */ ZVAL_NEW_REF(arg, arg); } else if (!ARG_MAY_BE_SENT_BY_REF(func, i + 1)) { /* By-value send is not allowed -- emit a warning, * but still perform the call with a by-value send. */ zend_error(E_WARNING, "Parameter %d to %s%s%s() expected to be a reference, value given", i + 1, func->common.scope ? ZSTR_VAL(func->common.scope->name) : "", func->common.scope ? "::" : "", ZSTR_VAL(func->common.function_name)); } } } else { if (Z_ISREF_P(arg) && !(func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { /* don't separate references for __call */ arg = Z_REFVAL_P(arg); } } param = ZEND_CALL_ARG(call, i + 1); ZVAL_COPY(param, arg); } if (UNEXPECTED(func->op_array.fn_flags & ZEND_ACC_CLOSURE)) { ZEND_ASSERT(GC_TYPE((zend_object *)func->op_array.prototype) == IS_OBJECT); GC_ADDREF((zend_object *)func->op_array.prototype); ZEND_ADD_CALL_FLAG(call, ZEND_CALL_CLOSURE); } if (func->type == ZEND_USER_FUNCTION) { int call_via_handler = (func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) != 0; zend_init_execute_data(call, &func->op_array, fci.retval); zend_execute_ex(call); if (call_via_handler) { /* We must re-initialize function again */ RUNKIT_CLEAR_FCI_CACHE(fci_cache); } } else if (func->type == ZEND_INTERNAL_FUNCTION) { int call_via_handler = (func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) != 0; ZVAL_NULL(fci.retval); call->prev_execute_data = EG(current_execute_data); call->return_value = NULL; /* this is not a constructor call */ EG(current_execute_data) = call; if (EXPECTED(zend_execute_internal == NULL)) { /* saves one function call if zend_execute_internal is not used */ func->internal_function.handler(call, fci.retval); } else { zend_execute_internal(call, fci.retval); } EG(current_execute_data) = call->prev_execute_data; zend_vm_stack_free_args(call); /* We shouldn't fix bad extensions here, because it can break proper ones (Bug #34045) if (!EX(function_state).function->common.return_reference) { INIT_PZVAL(f->retval); }*/ if (EG(exception)) { zval_ptr_dtor(fci.retval); ZVAL_UNDEF(fci.retval); } if (call_via_handler) { /* We must re-initialize function again */ RUNKIT_CLEAR_FCI_CACHE(fci_cache); } } else { /* ZEND_OVERLOADED_FUNCTION */ ZVAL_NULL(fci.retval); /* Not sure what should be done here if it's a static method */ if (fci.object) { call->prev_execute_data = EG(current_execute_data); EG(current_execute_data) = call; fci.object->handlers->call_method(func->common.function_name, fci.object, call, fci.retval); EG(current_execute_data) = call->prev_execute_data; } else { zend_throw_error(NULL, "Cannot call overloaded function for non-object"); } zend_vm_stack_free_args(call); if (func->type == ZEND_OVERLOADED_FUNCTION_TEMPORARY) { zend_string_release(func->common.function_name); } efree(func); if (EG(exception)) { zval_ptr_dtor(fci.retval); ZVAL_UNDEF(fci.retval); } } zend_vm_stack_free_call_frame(call); if (EG(current_execute_data) == &dummy_execute_data) { EG(current_execute_data) = dummy_execute_data.prev_execute_data; } if (EG(exception)) { zend_throw_exception_internal(NULL); } return SUCCESS; }