diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index b92ead02eb4..4051dd82f8a 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -1773,7 +1773,8 @@ next: } static void zend_get_gc_buffer_release(void); -static void zend_gc_root_tmpvars(void); +static void zend_gc_check_root_tmpvars(void); +static void zend_gc_remove_root_tmpvars(void); ZEND_API int zend_gc_collect_cycles(void) { @@ -1782,6 +1783,9 @@ ZEND_API int zend_gc_collect_cycles(void) bool did_rerun_gc = 0; zend_hrtime_t start_time = zend_hrtime(); + if (GC_G(num_roots) && GC_G(gc_active)) { + zend_gc_remove_root_tmpvars(); + } rerun_gc: if (GC_G(num_roots)) { @@ -1986,7 +1990,7 @@ rerun_gc: finish: zend_get_gc_buffer_release(); - zend_gc_root_tmpvars(); + zend_gc_check_root_tmpvars(); GC_G(collector_time) += zend_hrtime() - start_time; return total_count; } @@ -2033,7 +2037,7 @@ static void zend_get_gc_buffer_release(void) { * cycles. However, there are some rare exceptions where this is possible, in which case we rely * on the producing code to root the value. If a GC run occurs between the rooting and consumption * of the value, we would end up leaking it. To avoid this, root all live TMPVAR values here. */ -static void zend_gc_root_tmpvars(void) { +static void zend_gc_check_root_tmpvars(void) { zend_execute_data *ex = EG(current_execute_data); for (; ex; ex = ex->prev_execute_data) { zend_function *func = ex->func; @@ -2063,6 +2067,36 @@ static void zend_gc_root_tmpvars(void) { } } +static void zend_gc_remove_root_tmpvars(void) { + zend_execute_data *ex = EG(current_execute_data); + for (; ex; ex = ex->prev_execute_data) { + zend_function *func = ex->func; + if (!func || !ZEND_USER_CODE(func->type)) { + continue; + } + + uint32_t op_num = ex->opline - ex->func->op_array.opcodes; + for (uint32_t i = 0; i < func->op_array.last_live_range; i++) { + const zend_live_range *range = &func->op_array.live_range[i]; + if (range->start > op_num) { + break; + } + if (range->end <= op_num) { + continue; + } + + uint32_t kind = range->var & ZEND_LIVE_MASK; + 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)) { + GC_REMOVE_FROM_BUFFER(Z_COUNTED_P(var)); + } + } + } + } +} + #if GC_BENCH void gc_bench_print(void) {