diff --git a/Zend/tests/gc/bug70805.phpt b/Zend/tests/gc/bug70805.phpt index c62fe1fe84b4f..68a11ae4dedb4 100644 --- a/Zend/tests/gc/bug70805.phpt +++ b/Zend/tests/gc/bug70805.phpt @@ -28,6 +28,7 @@ $i = 0; $c = new A; $array = array($c); //This is used to leave a room for $GLOBALS["a"] unset($c); +flush(); // handle interrupts while ($i++ < 9998) { $t = []; @@ -37,11 +38,13 @@ while ($i++ < 9998) { $t = [new C]; $t[] = &$t; unset($t); // This is used to trigger C::__destruct while doing gc_collect_roots +flush(); // handle interrupts $e = $a; unset($a); // This one cannot be put into roots buf because it's full, thus gc_collect_roots will be called, // but C::__destructor which is called in gc_collect_roots will put $a into buf // which will make $a be put into gc roots buf twice +flush(); // handle interrupts var_dump(gc_collect_cycles()); ?> --EXPECT-- diff --git a/Zend/tests/gc/bug70805_1.phpt b/Zend/tests/gc/bug70805_1.phpt index 517f731498bd8..df0c3a04c80e3 100644 --- a/Zend/tests/gc/bug70805_1.phpt +++ b/Zend/tests/gc/bug70805_1.phpt @@ -30,6 +30,7 @@ $i = 0; $c = new A; $array = array($c); unset($c); +flush(); // handle interrupts while ($i++ < 9998) { $t = []; @@ -39,9 +40,11 @@ while ($i++ < 9998) { $t = [new C]; $t[] = &$t; unset($t); +flush(); // handle interrupts unset($a); +flush(); // handle interrupts var_dump(gc_collect_cycles()); ?> --EXPECT-- -int(2) +int(0) diff --git a/Zend/tests/gc/bug70805_2.phpt b/Zend/tests/gc/bug70805_2.phpt index cfee85de0df7e..592d97025c6e3 100644 --- a/Zend/tests/gc/bug70805_2.phpt +++ b/Zend/tests/gc/bug70805_2.phpt @@ -31,12 +31,15 @@ while ($i++ < 9999) { $t[] = &$t; unset($t); } +flush(); // handle interrupts $t = [new C]; $t[] = &$t; unset($t); +flush(); // handle interrupts unset($a); +flush(); // handle interrupts var_dump(gc_collect_cycles()); ?> --EXPECT-- -int(2) +int(0) diff --git a/Zend/tests/gc/gc_023.phpt b/Zend/tests/gc/gc_023.phpt index 2a5d0206558aa..1b2628214eedb 100644 --- a/Zend/tests/gc/gc_023.phpt +++ b/Zend/tests/gc/gc_023.phpt @@ -11,14 +11,17 @@ for ($i=0; $i < 9999; $i++) { } var_dump(gc_collect_cycles()); unset($a); +flush(); // handle interrupts var_dump(gc_collect_cycles()); $a=array(); for ($i=0; $i < 10001; $i++) { $a[$i] = array(array()); $a[$i][0] = & $a[$i]; } +flush(); // handle interrupts var_dump(gc_collect_cycles()); -unset($a); // 10000 zvals collected automatic +unset($a); +flush(); // handle interrupts. 10000 zvals collected automatic var_dump(gc_collect_cycles()); echo "ok\n"; ?> @@ -26,5 +29,5 @@ echo "ok\n"; int(0) int(9999) int(0) -int(1) +int(0) ok diff --git a/Zend/tests/gc/gc_045.phpt b/Zend/tests/gc/gc_045.phpt index 1762be5db1ad9..6fb8640e2ae31 100644 --- a/Zend/tests/gc/gc_045.phpt +++ b/Zend/tests/gc/gc_045.phpt @@ -56,13 +56,13 @@ array(12) { ["runs"]=> int(10) ["collected"]=> - int(25000) + int(25005) ["threshold"]=> int(10001) ["buffer_size"]=> - int(16384) + int(%d) ["roots"]=> - int(10000) + int(9990) ["application_time"]=> float(%f) ["collector_time"]=> diff --git a/Zend/tests/gc/gh13687-001.phpt b/Zend/tests/gc/gh13687-001.phpt new file mode 100644 index 0000000000000..fe76158c08ae7 --- /dev/null +++ b/Zend/tests/gc/gh13687-001.phpt @@ -0,0 +1,31 @@ +--TEST-- +GH-13687 001: Result operand may leak if GC is triggered before consumption +--FILE-- +cycle = $this; } +} +class B { + public function get() { + return new A(); + } +} + +$c = new B(); +$objs = []; + +while (gc_status()['roots']+2 < gc_status()['threshold']) { + $obj = new stdClass; + $objs[] = $obj; +} + +var_dump($c->get()); + +?> +--EXPECTF-- +object(A)#%d (1) { + ["cycle"]=> + *RECURSION* +} diff --git a/Zend/tests/gc/gh13687-002.phpt b/Zend/tests/gc/gh13687-002.phpt new file mode 100644 index 0000000000000..4f46c1bb49f40 --- /dev/null +++ b/Zend/tests/gc/gh13687-002.phpt @@ -0,0 +1,31 @@ +--TEST-- +GH-13687 002: Result operand may leak if GC is triggered before consumption +--FILE-- +cycle = $this; } + public function __toString() { return __CLASS__; } +} +class B { + public function get() { + return new A(); + } +} + +$root = new stdClass; +gc_collect_cycles(); + +$objs = []; +while (gc_status()['roots']+2 < gc_status()['threshold']) { + $obj = new stdClass; + $objs[] = $obj; +} + +$a = [new A, $root][0]::class; + +?> +==DONE== +--EXPECT-- +==DONE== diff --git a/Zend/tests/gc/gh13687-003.phpt b/Zend/tests/gc/gh13687-003.phpt new file mode 100644 index 0000000000000..5ece73c849cb7 --- /dev/null +++ b/Zend/tests/gc/gh13687-003.phpt @@ -0,0 +1,34 @@ +--TEST-- +GH-13687 003: Result operand may leak if GC is triggered before consumption +--FILE-- +cycle = $this; } +} +class B { + public function get() { + return new A(); + } +} +class Debug { + public function __debugInfo() { + gc_collect_cycles(); + return []; + } +} + +$c = new B(); +var_dump($c->get(), new Debug); + +?> +==DONE== +--EXPECTF-- +object(A)#%d (1) { + ["cycle"]=> + *RECURSION* +} +object(Debug)#%d (0) { +} +==DONE== diff --git a/Zend/tests/gc/gh13687-004.phpt b/Zend/tests/gc/gh13687-004.phpt new file mode 100644 index 0000000000000..a48b68625137b --- /dev/null +++ b/Zend/tests/gc/gh13687-004.phpt @@ -0,0 +1,30 @@ +--TEST-- +GH-13687 004: Result operand may leak if GC is triggered before consumption +--ENV-- +func=call_user_func +--FILE-- +cycle = $this; } +} +class B { + public function get() { + return new A(); + } +} +class Debug { + public function __debugInfo() { + gc_collect_cycles(); + return []; + } +} + +$c = new B(); +getenv('func')(fn (...$args) => gc_collect_cycles(), a: $c->get()); + +?> +==DONE== +--EXPECT-- +==DONE== diff --git a/Zend/tests/gc/gh13687-005.phpt b/Zend/tests/gc/gh13687-005.phpt new file mode 100644 index 0000000000000..94f81e4ed90c6 --- /dev/null +++ b/Zend/tests/gc/gh13687-005.phpt @@ -0,0 +1,37 @@ +--TEST-- +GH-13687 005: Result operand may leak if GC is triggered before consumption +--FILE-- +cycle = $this; + $a = $this; + unset($a); + } +} +class B { + public function get() { + return new A(); + } +} + +$c = new B(); +$objs = []; + +$rc = new ReflectionClass(A::class); + +while (gc_status()['roots']+1 < gc_status()['threshold']) { + $obj = new stdClass; + $objs[] = $obj; +} + +var_dump($rc->newInstance()); + +?> +--EXPECTF-- +object(A)#%d (1) { + ["cycle"]=> + *RECURSION* +} diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index b25925b89f7f9..b265c05a9e1f5 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -167,7 +167,7 @@ ZEND_FUNCTION(gc_collect_cycles) { ZEND_PARSE_PARAMETERS_NONE(); - RETURN_LONG(gc_collect_cycles()); + RETURN_LONG(gc_collect_cycles(0)); } /* }}} */ diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index ba9a7fa7e528f..e6ebbb51acb60 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -4269,6 +4269,8 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_fcall_interrupt(zend_execute_data *ca zend_atomic_bool_store_ex(&EG(vm_interrupt), false); if (zend_atomic_bool_load_ex(&EG(timed_out))) { zend_timeout(); + } else if (zend_atomic_bool_load_ex(&EG(gc_requested))) { + gc_run_from_fcall_interrupt(); } else if (zend_interrupt_function) { zend_interrupt_function(call); } diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 06618b3a9ded2..95e6c6a58b3f5 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -422,7 +422,7 @@ ZEND_API void zend_shutdown_executor_values(bool fast_shutdown) #if ZEND_DEBUG if (!CG(unclean_shutdown)) { - gc_collect_cycles(); + gc_collect_cycles(0); } #endif } else { diff --git a/Zend/zend_gc.c b/Zend/zend_gc.c index e15f97ecfe802..29f3823e5ebef 100644 --- a/Zend/zend_gc.c +++ b/Zend/zend_gc.c @@ -68,6 +68,7 @@ */ #include "zend.h" #include "zend_API.h" +#include "zend_atomic.h" #include "zend_compile.h" #include "zend_errors.h" #include "zend_fibers.h" @@ -76,6 +77,7 @@ #include "zend_types.h" #include "zend_weakrefs.h" #include "zend_string.h" +#include "zend_exceptions.h" #ifndef GC_BENCH # define GC_BENCH 0 @@ -250,7 +252,16 @@ #define GC_FETCH_NEXT_UNUSED() \ gc_fetch_next_unused() -ZEND_API int (*gc_collect_cycles)(void); +/* Collect flags */ + +#define GC_RUN_FROM_INTERRUPT (1<<0) /* GC is executed from a VM interrupt */ +#define GC_RUN_FROM_FCALL_INTERRUPT (1<<1) /* GC is executed from a VM internal + function call interrupt (after the + function has returned, while the + internal frame is still on the + stack) */ + +ZEND_API int (*gc_collect_cycles)(int run_flags); /* The type of a root buffer entry. * @@ -687,7 +698,25 @@ static void gc_adjust_threshold(int count) } } -/* Perform a GC run and then add a node as a possible root. */ +static void gc_request_run(void) +{ + zend_atomic_bool_store_ex(&EG(gc_requested), true); + zend_atomic_bool_store_ex(&EG(vm_interrupt), true); +} + +void gc_run_from_interrupt(void) +{ + zend_atomic_bool_store_ex(&EG(gc_requested), false); + gc_adjust_threshold(gc_collect_cycles(GC_RUN_FROM_INTERRUPT)); +} + +void gc_run_from_fcall_interrupt(void) +{ + zend_atomic_bool_store_ex(&EG(gc_requested), false); + gc_adjust_threshold(gc_collect_cycles(GC_RUN_FROM_INTERRUPT|GC_RUN_FROM_FCALL_INTERRUPT)); +} + +/* Add a node as a possible root and request a GC run. */ static zend_never_inline void ZEND_FASTCALL gc_possible_root_when_full(zend_refcounted *ref) { uint32_t idx; @@ -696,20 +725,8 @@ static zend_never_inline void ZEND_FASTCALL gc_possible_root_when_full(zend_refc 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())) { + ZEND_ASSERT(!GC_HAS_UNUSED()); + if (EXPECTED(GC_HAS_NEXT_UNUSED())) { idx = GC_FETCH_NEXT_UNUSED(); } else { gc_grow_root_buffer(); @@ -730,6 +747,10 @@ static zend_never_inline void ZEND_FASTCALL gc_possible_root_when_full(zend_refc GC_BENCH_INC(zval_buffered); GC_BENCH_INC(root_buf_length); GC_BENCH_PEAK(root_buf_peak, root_buf_length); + + if (GC_G(gc_enabled) && !GC_G(gc_active)) { + gc_request_run(); + } } /* Add a possible root node to the buffer. @@ -1852,8 +1873,8 @@ static int gc_remove_nested_data_from_buffer(zend_refcounted *ref, gc_root_buffe } static void zend_get_gc_buffer_release(void); -static void zend_gc_check_root_tmpvars(void); -static void zend_gc_remove_root_tmpvars(void); +static void zend_gc_check_root_tmpvars(int run_flags); +static void zend_gc_remove_root_tmpvars(int run_flags); static zend_internal_function gc_destructor_fiber; @@ -1965,13 +1986,16 @@ static zend_never_inline void gc_call_destructors_in_fiber(uint32_t end) } else { /* Fiber suspended itself after calling all destructors */ GC_TRACE("destructor fiber suspended itself"); + if (EG(exception)) { + zend_rethrow_exception(EG(current_execute_data)); + } break; } } } /* Perform a garbage collection run. The default implementation of gc_collect_cycles. */ -ZEND_API int zend_gc_collect_cycles(void) +ZEND_API int zend_gc_collect_cycles(int run_flags) { int total_count = 0; bool should_rerun_gc = 0; @@ -1979,7 +2003,7 @@ ZEND_API int zend_gc_collect_cycles(void) zend_hrtime_t start_time = zend_hrtime(); if (GC_G(num_roots) && !GC_G(gc_active)) { - zend_gc_remove_root_tmpvars(); + zend_gc_remove_root_tmpvars(run_flags); } rerun_gc: @@ -2165,7 +2189,7 @@ ZEND_API int zend_gc_collect_cycles(void) /* Prevent GC from running during zend_gc_check_root_tmpvars, before * gc_threshold is adjusted, as this may result in unbounded recursion */ GC_G(gc_active) = 1; - zend_gc_check_root_tmpvars(); + zend_gc_check_root_tmpvars(run_flags); GC_G(gc_active) = 0; GC_G(collector_time) += zend_hrtime() - start_time; @@ -2214,41 +2238,84 @@ 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_check_root_tmpvars(void) { +static zend_always_inline void zend_gc_check_root_tmpvars_ex(int run_flags, void (*check)(zend_refcounted*)) { zend_execute_data *ex = EG(current_execute_data); + + if (!ex) { + return; + } + + /* The result of the last executed op must be rooted explicitly as it won't + * appear in live vars if it's consumed by the following op. */ + if (run_flags & GC_RUN_FROM_FCALL_INTERRUPT) { + /* GC is called from an internal function call interrupt. The function + * has returned, but EG(current_execute_data) still points to the + * internal frame. The op result is those of the parent frame's saved + * opline. */ + ZEND_ASSERT((!ex->func || !ZEND_USER_CODE(ex->func->type)) + && ex->prev_execute_data && ex->prev_execute_data->func + && ZEND_USER_CODE(ex->prev_execute_data->func->type)); + zend_execute_data *result_ex = ex->prev_execute_data; + const zend_op *op = result_ex->opline; + if (op->result_type & (IS_VAR | IS_TMP_VAR)) { + zval *var = ZEND_CALL_VAR(result_ex, op->result.var); + if (Z_COLLECTABLE_P(var)) { + check(Z_COUNTED_P(var)); + } + } + } else if (run_flags & GC_RUN_FROM_INTERRUPT) { + /* GC is called from the interrupt handler. EX(opline) is the next + * opline to be executed. The result of the last executed op may be one + * of EX(opline)'s inputs. */ + ZEND_ASSERT(ex->func && ZEND_USER_CODE(ex->func->type)); + const zend_op *op = ex->opline; + if (op->op1_type & (IS_VAR | IS_TMP_VAR)) { + zval *var = ZEND_CALL_VAR(ex, op->op1.var); + if (Z_COLLECTABLE_P(var)) { + check(Z_COUNTED_P(var)); + } + } + if (op->op2_type & (IS_VAR | IS_TMP_VAR)) { + zval *var = ZEND_CALL_VAR(ex, op->op2.var); + if (Z_COLLECTABLE_P(var)) { + check(Z_COUNTED_P(var)); + } + } + if (op + 1 - ex->func->op_array.opcodes < ex->func->op_array.last + && (op + 1)->opcode == ZEND_OP_DATA + && (op + 1)->opcode & (IS_VAR | IS_TMP_VAR)) { + zval *var = ZEND_CALL_VAR(ex, (op + 1)->op1.var); + if (Z_COLLECTABLE_P(var)) { + check(Z_COUNTED_P(var)); + } + } + } + for (; ex; ex = ex->prev_execute_data) { zend_function *func = ex->func; - if (!func || !ZEND_USER_CODE(func->type)) { + if (!func) { 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) { + if (!ZEND_USER_CODE(func->type)) { + /* Internal frames indicate that the parent frame is in the middle + * of a DO_FCALL op. Arguments are not consumed yet. */ + zend_execute_data *caller = ex->prev_execute_data; + if (!caller || !caller->func || !ZEND_USER_CODE(caller->func->type)) { 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_COLLECTABLE_P(var)) { - gc_check_possible_root(Z_COUNTED_P(var)); - } + uint32_t num_args = ZEND_CALL_NUM_ARGS(ex); + if (EXPECTED(num_args> 0)) { + zval *p = ZEND_CALL_ARG(ex, 1); + do { + if (Z_COLLECTABLE_P(p)) { + check(Z_COUNTED_P(p)); + } + p++; + } while (--num_args); } - } - } -} -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; } @@ -2267,13 +2334,25 @@ static void zend_gc_remove_root_tmpvars(void) { uint32_t var_num = range->var & ~ZEND_LIVE_MASK; zval *var = ZEND_CALL_VAR(ex, var_num); if (Z_COLLECTABLE_P(var)) { - GC_REMOVE_FROM_BUFFER(Z_COUNTED_P(var)); + check(Z_COUNTED_P(var)); } } } } } +static void zend_gc_check_root_tmpvars(int run_flags) { + zend_gc_check_root_tmpvars_ex(run_flags, gc_check_possible_root); +} + +static void gc_remove_from_buffer_cb(zend_refcounted *p) { + GC_REMOVE_FROM_BUFFER(p); +} + +static void zend_gc_remove_root_tmpvars(int run_flags) { + zend_gc_check_root_tmpvars_ex(run_flags, gc_remove_from_buffer_cb); +} + #if GC_BENCH void gc_bench_print(void) { diff --git a/Zend/zend_gc.h b/Zend/zend_gc.h index 06f550647bd79..62f56cfa401e6 100644 --- a/Zend/zend_gc.h +++ b/Zend/zend_gc.h @@ -43,7 +43,7 @@ typedef struct _zend_gc_status { zend_hrtime_t free_time; } zend_gc_status; -ZEND_API extern int (*gc_collect_cycles)(void); +ZEND_API extern int (*gc_collect_cycles)(int run_flags); ZEND_API void ZEND_FASTCALL gc_possible_root(zend_refcounted *ref); ZEND_API void ZEND_FASTCALL gc_remove_from_buffer(zend_refcounted *ref); @@ -61,7 +61,7 @@ void gc_bench_print(void); #endif /* The default implementation of the gc_collect_cycles callback. */ -ZEND_API int zend_gc_collect_cycles(void); +ZEND_API int zend_gc_collect_cycles(int run_flags); ZEND_API void zend_gc_get_status(zend_gc_status *status); @@ -70,6 +70,9 @@ void gc_globals_ctor(void); void gc_globals_dtor(void); void gc_reset(void); +void gc_run_from_interrupt(void); +void gc_run_from_fcall_interrupt(void); + #ifdef ZTS size_t zend_gc_globals_size(void); #endif diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 48b978b535014..f81ffe4f609ae 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -219,6 +219,7 @@ struct _zend_executor_globals { zend_atomic_bool vm_interrupt; zend_atomic_bool timed_out; + zend_atomic_bool gc_requested; HashTable *in_autoload; diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 3d6463d064873..a7c2ae4cd3dd7 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -10506,23 +10506,27 @@ ZEND_VM_HELPER(zend_interrupt_helper, ANY, ANY) SAVE_OPLINE(); if (zend_atomic_bool_load_ex(&EG(timed_out))) { zend_timeout(); + ZEND_VM_CONTINUE(); + } else if (zend_atomic_bool_load_ex(&EG(gc_requested))) { + gc_run_from_interrupt(); } else if (zend_interrupt_function) { zend_interrupt_function(execute_data); - if (EG(exception)) { - /* We have to UNDEF result, because ZEND_HANDLE_EXCEPTION is going to free it */ - const zend_op *throw_op = EG(opline_before_exception); + } else { + ZEND_VM_CONTINUE(); + } + if (EG(exception)) { + /* We have to UNDEF result, because ZEND_HANDLE_EXCEPTION is going to free it */ + const zend_op *throw_op = EG(opline_before_exception); - if (throw_op - && throw_op->result_type & (IS_TMP_VAR|IS_VAR) - && throw_op->opcode != ZEND_ADD_ARRAY_ELEMENT - && throw_op->opcode != ZEND_ADD_ARRAY_UNPACK - && throw_op->opcode != ZEND_ROPE_INIT - && throw_op->opcode != ZEND_ROPE_ADD) { - ZVAL_UNDEF(ZEND_CALL_VAR(EG(current_execute_data), throw_op->result.var)); + if (throw_op + && throw_op->result_type & (IS_TMP_VAR|IS_VAR) + && throw_op->opcode != ZEND_ADD_ARRAY_ELEMENT + && throw_op->opcode != ZEND_ADD_ARRAY_UNPACK + && throw_op->opcode != ZEND_ROPE_INIT + && throw_op->opcode != ZEND_ROPE_ADD) { + ZVAL_UNDEF(ZEND_CALL_VAR(EG(current_execute_data), throw_op->result.var)); - } } - ZEND_VM_ENTER(); } - ZEND_VM_CONTINUE(); + ZEND_VM_ENTER(); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index e1a94fd07a11a..8d319baf12339 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3984,25 +3984,29 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV SAVE_OPLINE(); if (zend_atomic_bool_load_ex(&EG(timed_out))) { zend_timeout(); + ZEND_VM_CONTINUE(); + } else if (zend_atomic_bool_load_ex(&EG(gc_requested))) { + gc_run_from_interrupt(); } else if (zend_interrupt_function) { zend_interrupt_function(execute_data); - if (EG(exception)) { - /* We have to UNDEF result, because ZEND_HANDLE_EXCEPTION is going to free it */ - const zend_op *throw_op = EG(opline_before_exception); + } else { + ZEND_VM_CONTINUE(); + } + if (EG(exception)) { + /* We have to UNDEF result, because ZEND_HANDLE_EXCEPTION is going to free it */ + const zend_op *throw_op = EG(opline_before_exception); - if (throw_op - && throw_op->result_type & (IS_TMP_VAR|IS_VAR) - && throw_op->opcode != ZEND_ADD_ARRAY_ELEMENT - && throw_op->opcode != ZEND_ADD_ARRAY_UNPACK - && throw_op->opcode != ZEND_ROPE_INIT - && throw_op->opcode != ZEND_ROPE_ADD) { - ZVAL_UNDEF(ZEND_CALL_VAR(EG(current_execute_data), throw_op->result.var)); + if (throw_op + && throw_op->result_type & (IS_TMP_VAR|IS_VAR) + && throw_op->opcode != ZEND_ADD_ARRAY_ELEMENT + && throw_op->opcode != ZEND_ADD_ARRAY_UNPACK + && throw_op->opcode != ZEND_ROPE_INIT + && throw_op->opcode != ZEND_ROPE_ADD) { + ZVAL_UNDEF(ZEND_CALL_VAR(EG(current_execute_data), throw_op->result.var)); - } } - ZEND_VM_ENTER(); } - ZEND_VM_CONTINUE(); + ZEND_VM_ENTER(); } static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_FCALL_BY_NAME_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { @@ -58275,25 +58279,29 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend SAVE_OPLINE(); if (zend_atomic_bool_load_ex(&EG(timed_out))) { zend_timeout(); + ZEND_VM_CONTINUE(); + } else if (zend_atomic_bool_load_ex(&EG(gc_requested))) { + gc_run_from_interrupt(); } else if (zend_interrupt_function) { zend_interrupt_function(execute_data); - if (EG(exception)) { - /* We have to UNDEF result, because ZEND_HANDLE_EXCEPTION is going to free it */ - const zend_op *throw_op = EG(opline_before_exception); + } else { + ZEND_VM_CONTINUE(); + } + if (EG(exception)) { + /* We have to UNDEF result, because ZEND_HANDLE_EXCEPTION is going to free it */ + const zend_op *throw_op = EG(opline_before_exception); - if (throw_op - && throw_op->result_type & (IS_TMP_VAR|IS_VAR) - && throw_op->opcode != ZEND_ADD_ARRAY_ELEMENT - && throw_op->opcode != ZEND_ADD_ARRAY_UNPACK - && throw_op->opcode != ZEND_ROPE_INIT - && throw_op->opcode != ZEND_ROPE_ADD) { - ZVAL_UNDEF(ZEND_CALL_VAR(EG(current_execute_data), throw_op->result.var)); + if (throw_op + && throw_op->result_type & (IS_TMP_VAR|IS_VAR) + && throw_op->opcode != ZEND_ADD_ARRAY_ELEMENT + && throw_op->opcode != ZEND_ADD_ARRAY_UNPACK + && throw_op->opcode != ZEND_ROPE_INIT + && throw_op->opcode != ZEND_ROPE_ADD) { + ZVAL_UNDEF(ZEND_CALL_VAR(EG(current_execute_data), throw_op->result.var)); - } } - ZEND_VM_ENTER(); } - ZEND_VM_CONTINUE(); + ZEND_VM_ENTER(); } static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_FCALL_BY_NAME_SPEC_CONST_TAILCALL_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 57f7e189e6c41..82fcf828facb2 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -2062,27 +2062,36 @@ static int zend_jit_exception_handler_free_op2_stub(zend_jit_ctx *jit) static int zend_jit_interrupt_handler_stub(zend_jit_ctx *jit) { - ir_ref if_timeout, if_exception; + ir_ref if_timeout, if_exception, if_gc_requested, if_gc_requested_end; // EX(opline) = opline ir_STORE(jit_EX(opline), jit_IP(jit)); ir_STORE(jit_EG(vm_interrupt), ir_CONST_U8(0)); + if_timeout = ir_IF(ir_EQ(ir_LOAD_U8(jit_EG(timed_out)), ir_CONST_U8(0))); ir_IF_FALSE(if_timeout); ir_CALL(IR_VOID, ir_CONST_FUNC(zend_timeout)); ir_MERGE_WITH_EMPTY_TRUE(if_timeout); + if_gc_requested = ir_IF(ir_EQ(ir_LOAD_U8(jit_EG(gc_requested)), ir_CONST_U8(1))); + ir_IF_TRUE(if_gc_requested); + ir_CALL(IR_VOID, ir_CONST_FUNC(gc_run_from_interrupt)); + if_gc_requested_end = ir_END(); + + ir_IF_FALSE(if_gc_requested); if (zend_interrupt_function) { ir_CALL_1(IR_VOID, ir_CONST_FUNC(zend_interrupt_function), jit_FP(jit)); - if_exception = ir_IF(ir_LOAD_A(jit_EG(exception))); - ir_IF_TRUE(if_exception); - ir_CALL(IR_VOID, ir_CONST_FUNC(zend_jit_exception_in_interrupt_handler_helper)); - ir_MERGE_WITH_EMPTY_FALSE(if_exception); - - jit_STORE_FP(jit, ir_LOAD_A(jit_EG(current_execute_data))); - jit_STORE_IP(jit, ir_LOAD_A(jit_EX(opline))); } + ir_MERGE_WITH(if_gc_requested_end); + + if_exception = ir_IF(ir_LOAD_A(jit_EG(exception))); + ir_IF_TRUE(if_exception); + ir_CALL(IR_VOID, ir_CONST_FUNC(zend_jit_exception_in_interrupt_handler_helper)); + ir_MERGE_WITH_EMPTY_FALSE(if_exception); + + jit_STORE_FP(jit, ir_LOAD_A(jit_EG(current_execute_data))); + jit_STORE_IP(jit, ir_LOAD_A(jit_EX(opline))); if (GCC_GLOBAL_REGS || ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL) { zend_jit_tailcall_handler(jit, ir_LOAD_A(jit_IP(jit)));

AltStyle によって変換されたページ (->オリジナル) /