diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 3e750e8cf30a..2b538281ffed 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -526,7 +526,7 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_cannot_pass_by_reference_ex(const zen ZEND_API void zend_set_timeout(zend_long seconds, bool reset_signals); ZEND_API void zend_unset_timeout(void); -ZEND_NORETURN ZEND_API void ZEND_FASTCALL zend_timeout(void); +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_timeout(void); ZEND_API zend_class_entry *zend_fetch_class(zend_string *class_name, uint32_t fetch_type); ZEND_API zend_class_entry *zend_fetch_class_with_scope(zend_string *class_name, uint32_t fetch_type, zend_class_entry *scope); ZEND_API zend_class_entry *zend_fetch_class_by_name(zend_string *class_name, zend_string *lcname, uint32_t fetch_type); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index d8cf2d016ee8..3a9b5f707503 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1494,7 +1494,8 @@ ZEND_API zend_result zend_eval_string_ex(const char *str, zval *retval_ptr, cons static void zend_set_timeout_ex(zend_long seconds, bool reset_signals); -ZEND_NORETURN ZEND_API void ZEND_FASTCALL zend_timeout(void) /* {{{ */ +/* zend_timeout() is called by the VM interrupt handler or by zend_call_function() when EG(timed_out) is set */ +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_timeout(void) /* {{{ */ { #if defined(PHP_WIN32) # ifndef ZTS @@ -1514,7 +1515,23 @@ ZEND_NORETURN ZEND_API void ZEND_FASTCALL zend_timeout(void) /* {{{ */ zend_set_timeout_ex(0, true); #endif - zend_error_noreturn(E_ERROR, "Maximum execution time of " ZEND_LONG_FMT " second%s exceeded", EG(timeout_seconds), EG(timeout_seconds) == 1 ? "" : "s"); + /* Trigger error but do not bail */ + zend_error(E_ERROR | E_DONT_BAIL, "Maximum execution time of " ZEND_LONG_FMT " second%s exceeded", EG(timeout_seconds), EG(timeout_seconds) == 1 ? "" : "s"); + + /* Do not call destructors */ + zend_objects_store_mark_destructed(&EG(objects_store)); + + if (EG(exception)) { + OBJ_RELEASE(EG(exception)); + } + EG(exception) = zend_create_unwind_exit(); + + if (EG(current_execute_data)) { + /* zend_call_function() may call zend_timeout() without EG(current_execute_data) + * when executing shutdown functions */ + EG(opline_before_exception) = EG(current_execute_data)->opline; + EG(current_execute_data)->opline = EG(exception_op); + } } /* }}} */ diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 6d6053022f58..43b770060847 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -10743,6 +10743,7 @@ ZEND_VM_HELPER(zend_interrupt_helper, ANY, ANY) #endif if (atomic_load(&EG(timed_out))) { zend_timeout(); + HANDLE_EXCEPTION(); } else if (ZEND_HAS_OOM()) { zend_handle_delayed_oom(); HANDLE_EXCEPTION(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index f6deb7be7e69..8af9a8d18a8a 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -4055,6 +4055,7 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV #endif if (atomic_load(&EG(timed_out))) { zend_timeout(); + HANDLE_EXCEPTION(); } else if (ZEND_HAS_OOM()) { zend_handle_delayed_oom(); HANDLE_EXCEPTION(); @@ -56934,6 +56935,7 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend #endif if (atomic_load(&EG(timed_out))) { zend_timeout(); + HANDLE_EXCEPTION(); } else if (ZEND_HAS_OOM()) { zend_handle_delayed_oom(); HANDLE_EXCEPTION(); diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index b8f838134328..8bf93a1ac27a 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -2052,23 +2052,30 @@ 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 end_inputs = IR_UNUSED; // 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))); + + // if (EG(timed_out)) { + if_timeout = ir_IF(ir_LOAD_U8(jit_EG(timed_out))); + ir_IF_TRUE(if_timeout); + ir_CALL(IR_VOID, ir_CONST_FUNC(zend_timeout)); + ir_CALL(IR_VOID, ir_CONST_FUNC(zend_jit_exception_in_interrupt_handler_helper)); + // LOAD_OPLINE() + jit_STORE_IP(jit, ir_LOAD_A(jit_EX(opline))); + ir_END_list(end_inputs); ir_IF_FALSE(if_timeout); - ir_CALL(IR_VOID, ir_CONST_FUNC(zend_timeout)); // noreturn - ir_MERGE_WITH_EMPTY_TRUE(if_timeout); - // if (ZEND_HAS_OOM()) { + // } else if (ZEND_HAS_OOM()) { ir_ref if_delayed_oom = ir_IF(ir_LOAD_U8(ir_ADD_OFFSET(jit_EG(delayed_oom), offsetof(zend_oom_info, state)))); ir_IF_TRUE(if_delayed_oom); ir_CALL(IR_VOID, ir_CONST_FC_FUNC(zend_handle_delayed_oom)); // LOAD_OPLINE() jit_STORE_IP(jit, ir_LOAD_A(jit_EX(opline))); - ir_ref if_delayed_oom_end = ir_END(); + ir_END_list(end_inputs); ir_IF_FALSE(if_delayed_oom); if (zend_interrupt_function) { @@ -2082,7 +2089,8 @@ static int zend_jit_interrupt_handler_stub(zend_jit_ctx *jit) jit_STORE_IP(jit, ir_LOAD_A(jit_EX(opline))); } - ir_MERGE_WITH(if_delayed_oom_end); + ir_END_list(end_inputs); + ir_MERGE_list(end_inputs); if (GCC_GLOBAL_REGS || ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL) { zend_jit_tailcall_handler(jit, ir_LOAD_A(jit_IP(jit))); diff --git a/tests/basic/timeout_variation_11.phpt b/tests/basic/timeout_variation_11.phpt new file mode 100644 index 000000000000..29adf06bb459 --- /dev/null +++ b/tests/basic/timeout_variation_11.phpt @@ -0,0 +1,22 @@ +--TEST-- +Timeout within shutdown function, user function variation +--SKIPIF-- + +--INI-- +max_execution_time=1 +--FILE-- + +--EXPECTF-- +Fatal error: Maximum execution time of 1 second exceeded %s diff --git a/tests/basic/timeout_variation_12.phpt b/tests/basic/timeout_variation_12.phpt new file mode 100644 index 000000000000..af58b3d6049a --- /dev/null +++ b/tests/basic/timeout_variation_12.phpt @@ -0,0 +1,27 @@ +--TEST-- +Timeout does not call destructors +--SKIPIF-- + +--INI-- +max_execution_time=1 +--FILE-- + +--EXPECTF-- +Fatal error: Maximum execution time of 1 second exceeded %s +shutdown function called diff --git a/tests/basic/timeout_variation_13.phpt b/tests/basic/timeout_variation_13.phpt new file mode 100644 index 000000000000..54a9c25332ba --- /dev/null +++ b/tests/basic/timeout_variation_13.phpt @@ -0,0 +1,38 @@ +--TEST-- +Timeout does not execute finally blocks +--SKIPIF-- + +--INI-- +max_execution_time=1 +--FILE-- +current(); + +$gen2 = (function () { + try { + while (true) {} + } finally { + echo "finally executed (2)\n"; + } + yield; +})(); + +try { + foreach ($gen2 as $value) {} +} finally { + echo "finally executed (3)\n"; +} + +?> +--EXPECTF-- +Fatal error: Maximum execution time of 1 second exceeded %s