diff --git a/Zend/zend.c b/Zend/zend.c index d921c9f74b6d..103dc3c3a5d4 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1651,7 +1651,7 @@ static ZEND_COLD void zend_error_va_list( static ZEND_COLD void get_filename_lineno(int type, zend_string **filename, uint32_t *lineno) { /* Obtain relevant filename and lineno */ - switch (type) { + switch (type & E_ALL) { case E_CORE_ERROR: case E_CORE_WARNING: *filename = NULL; 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..e8f30cd39154 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,25 @@ 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)); + + /* Exception has been handled by zend_error() */ + ZEND_ASSERT(!EG(exception)); + + EG(exception) = zend_create_unwind_exit(); + + zend_execute_data *execute_data = EG(current_execute_data); + if (execute_data + && execute_data->func + && ZEND_USER_CODE(execute_data->func->common.type) + && execute_data->opline->opcode != ZEND_HANDLE_EXCEPTION) { + 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/ext/zend_test/test.c b/ext/zend_test/test.c index 0ca19544d022..5b42228d77c4 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -2793,3 +2793,34 @@ static PHP_FUNCTION(zend_test_gh19792) zend_error(E_WARNING, "a warning"); zend_throw_error(NULL, "an exception"); } + +/* Wait until max_execution_time is triggered. */ +static PHP_FUNCTION(zend_test_timeout) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + /* Buzy wait, as some max_execution_time implementations use CPU time. */ + while (true) { + if (atomic_load(&EG(vm_interrupt)) && EG(timed_out)) { + break; + } + } + + RETURN_NULL(); +} + +/* Wait until max_execution_time is triggered, then throw. */ +static PHP_FUNCTION(zend_test_timeout_throw) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + /* Buzy wait, as some max_execution_time implementations use CPU time. */ + while (true) { + if (atomic_load(&EG(vm_interrupt)) && EG(timed_out)) { + break; + } + } + + zend_throw_exception(NULL, "Timeout", 0); + RETURN_THROWS(); +} diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 62cc78cbe241..0e62669a4d5e 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -478,6 +478,9 @@ function zend_test_handle_post_data(mixed $stream, string $content_type, int $co function zend_new_singleton_array(): array {} function zend_test_set_local_var(string $name, mixed $value): bool {} + + function zend_test_timeout(): void {} + function zend_test_timeout_throw(): void {} } namespace ZendTestNS { diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 4d7bced1402d..f09a811807ac 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: a541ec4d43e8dfb243636d2517436196e337047e + * Stub hash: 8d0719b075e58c63b011c0930ad45188f77b2672 * Has decl header: yes */ #include "zend_attributes.h" @@ -375,6 +375,10 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_set_local_var, 0, 2, _ ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0) ZEND_END_ARG_INFO() +#define arginfo_zend_test_timeout arginfo_zend_test_void_return + +#define arginfo_zend_test_timeout_throw arginfo_zend_test_void_return + #define arginfo_ZendTestNS2_namespaced_func arginfo_zend_test_is_pcre_bundled #define arginfo_ZendTestNS2_namespaced_deprecated_func arginfo_zend_test_void_return @@ -576,6 +580,8 @@ static ZEND_FUNCTION(zend_test_gh19792); static ZEND_FUNCTION(zend_test_handle_post_data); static ZEND_FUNCTION(zend_new_singleton_array); static ZEND_FUNCTION(zend_test_set_local_var); +static ZEND_FUNCTION(zend_test_timeout); +static ZEND_FUNCTION(zend_test_timeout_throw); static ZEND_FUNCTION(ZendTestNS2_namespaced_func); static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func); @@ -770,6 +776,8 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_test_handle_post_data, arginfo_zend_test_handle_post_data) ZEND_FE(zend_new_singleton_array, arginfo_zend_new_singleton_array) ZEND_FE(zend_test_set_local_var, arginfo_zend_test_set_local_var) + ZEND_FE(zend_test_timeout, arginfo_zend_test_timeout) + ZEND_FE(zend_test_timeout_throw, arginfo_zend_test_timeout_throw) #if (PHP_VERSION_ID >= 80400) ZEND_RAW_FENTRY(ZEND_NS_NAME("ZendTestNS2", "namespaced_func"), zif_ZendTestNS2_namespaced_func, arginfo_ZendTestNS2_namespaced_func, 0, NULL, NULL) #else diff --git a/ext/zend_test/test_decl.h b/ext/zend_test/test_decl.h index 8fc579dd6879..45b3a596a47b 100644 --- a/ext/zend_test/test_decl.h +++ b/ext/zend_test/test_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: a541ec4d43e8dfb243636d2517436196e337047e */ + * Stub hash: 8d0719b075e58c63b011c0930ad45188f77b2672 */ -#ifndef ZEND_TEST_DECL_a541ec4d43e8dfb243636d2517436196e337047e_H -#define ZEND_TEST_DECL_a541ec4d43e8dfb243636d2517436196e337047e_H +#ifndef ZEND_TEST_DECL_8d0719b075e58c63b011c0930ad45188f77b2672_H +#define ZEND_TEST_DECL_8d0719b075e58c63b011c0930ad45188f77b2672_H typedef enum zend_enum_ZendTestUnitEnum { ZEND_ENUM_ZendTestUnitEnum_Foo = 1, @@ -27,4 +27,4 @@ typedef enum zend_enum_ZendTestEnumWithInterface { ZEND_ENUM_ZendTestEnumWithInterface_Bar = 2, } zend_enum_ZendTestEnumWithInterface; -#endif /* ZEND_TEST_DECL_a541ec4d43e8dfb243636d2517436196e337047e_H */ +#endif /* ZEND_TEST_DECL_8d0719b075e58c63b011c0930ad45188f77b2672_H */ diff --git a/ext/zend_test/test_legacy_arginfo.h b/ext/zend_test/test_legacy_arginfo.h index f654203e2a60..b526fcb19769 100644 --- a/ext/zend_test/test_legacy_arginfo.h +++ b/ext/zend_test/test_legacy_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit test.stub.php instead. - * Stub hash: a541ec4d43e8dfb243636d2517436196e337047e + * Stub hash: 8d0719b075e58c63b011c0930ad45188f77b2672 * Has decl header: yes */ #include "zend_constants.h" @@ -312,6 +312,10 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_test_set_local_var, 0, 0, 2) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() +#define arginfo_zend_test_timeout arginfo_zend_trigger_bailout + +#define arginfo_zend_test_timeout_throw arginfo_zend_trigger_bailout + #define arginfo_ZendTestNS2_namespaced_func arginfo_zend_trigger_bailout #define arginfo_ZendTestNS2_namespaced_deprecated_func arginfo_zend_trigger_bailout @@ -500,6 +504,8 @@ static ZEND_FUNCTION(zend_test_gh19792); static ZEND_FUNCTION(zend_test_handle_post_data); static ZEND_FUNCTION(zend_new_singleton_array); static ZEND_FUNCTION(zend_test_set_local_var); +static ZEND_FUNCTION(zend_test_timeout); +static ZEND_FUNCTION(zend_test_timeout_throw); static ZEND_FUNCTION(ZendTestNS2_namespaced_func); static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func); @@ -658,6 +664,8 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_test_handle_post_data, arginfo_zend_test_handle_post_data) ZEND_FE(zend_new_singleton_array, arginfo_zend_new_singleton_array) ZEND_FE(zend_test_set_local_var, arginfo_zend_test_set_local_var) + ZEND_FE(zend_test_timeout, arginfo_zend_test_timeout) + ZEND_FE(zend_test_timeout_throw, arginfo_zend_test_timeout_throw) #if (PHP_VERSION_ID >= 80400) ZEND_RAW_FENTRY(ZEND_NS_NAME("ZendTestNS2", "namespaced_func"), zif_ZendTestNS2_namespaced_func, arginfo_ZendTestNS2_namespaced_func, 0, NULL, NULL) #else diff --git a/tests/basic/timeout_variation_11.phpt b/tests/basic/timeout_variation_11.phpt new file mode 100644 index 000000000000..3c2e3f331baf --- /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 in %stimeout_variation_11.php on line %d diff --git a/tests/basic/timeout_variation_12.phpt b/tests/basic/timeout_variation_12.phpt new file mode 100644 index 000000000000..6a89b090003e --- /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 in %stimeout_variation_12.php on line %d +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..fafba452111d --- /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 in %stimeout_variation_13.php on line %d diff --git a/tests/basic/timeout_variation_14.phpt b/tests/basic/timeout_variation_14.phpt new file mode 100644 index 000000000000..15027098197e --- /dev/null +++ b/tests/basic/timeout_variation_14.phpt @@ -0,0 +1,16 @@ +--TEST-- +Timeout within internal function +--SKIPIF-- + +--INI-- +max_execution_time=1 +--FILE-- + +--EXPECTF-- +Fatal error: Maximum execution time of 1 second exceeded in %stimeout_variation_14.php on line 3 diff --git a/tests/basic/timeout_variation_15.phpt b/tests/basic/timeout_variation_15.phpt new file mode 100644 index 000000000000..6c8c87632353 --- /dev/null +++ b/tests/basic/timeout_variation_15.phpt @@ -0,0 +1,22 @@ +--TEST-- +Timeout while EG(exception) is already set +--SKIPIF-- + +--INI-- +max_execution_time=1 +--FILE-- + +--EXPECTF-- +Warning: Uncaught Exception: Timeout in %s:%d +Stack trace: +#0 %s(%d): zend_test_timeout_throw() +#1 {main} + thrown in %s on line %d + +Fatal error: Maximum execution time of 1 second exceeded in %stimeout_variation_15.php on line 3