diff --git a/Zend/tests/exit_exception_handler.phpt b/Zend/tests/exit_exception_handler.phpt new file mode 100644 index 0000000000000..eabd0fb906e8a --- /dev/null +++ b/Zend/tests/exit_exception_handler.phpt @@ -0,0 +1,14 @@ +--TEST-- +Exception handler should not be invoked for exit() +--FILE-- + +--EXPECT-- +Exit diff --git a/Zend/tests/exit_finally_1.phpt b/Zend/tests/exit_finally_1.phpt new file mode 100644 index 0000000000000..034095f74e5df --- /dev/null +++ b/Zend/tests/exit_finally_1.phpt @@ -0,0 +1,18 @@ +--TEST-- +exit() and finally (1) +--FILE-- + +--EXPECT-- +Finally +Exit diff --git a/Zend/tests/exit_finally_2.phpt b/Zend/tests/exit_finally_2.phpt new file mode 100644 index 0000000000000..412568452a1bb --- /dev/null +++ b/Zend/tests/exit_finally_2.phpt @@ -0,0 +1,21 @@ +--TEST-- +exit() and finally (2) +--FILE-- +getMessage()}\n"; +} + +?> +--EXPECT-- +Caught Finally exception diff --git a/Zend/tests/exit_finally_3.phpt b/Zend/tests/exit_finally_3.phpt new file mode 100644 index 0000000000000..83857c7747cc6 --- /dev/null +++ b/Zend/tests/exit_finally_3.phpt @@ -0,0 +1,17 @@ +--TEST-- +exit() and finally (3) +--FILE-- + +--EXPECT-- +int(42) diff --git a/Zend/tests/generators/bug75396.phpt b/Zend/tests/generators/bug75396.phpt index 6d5abf518f01a..e351fe25cee21 100644 --- a/Zend/tests/generators/bug75396.phpt +++ b/Zend/tests/generators/bug75396.phpt @@ -19,4 +19,5 @@ $gen->send("x"); ?> --EXPECT-- Try +Finally Exit diff --git a/Zend/zend.c b/Zend/zend.c index e855f46cda0e9..2765f8c3ead22 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1621,6 +1621,11 @@ ZEND_API ZEND_COLD void zend_user_exception_handler(void) /* {{{ */ zval orig_user_exception_handler; zval params[1], retval2; zend_object *old_exception; + + if (zend_is_unwind_exit(EG(exception))) { + return; + } + old_exception = EG(exception); EG(exception) = NULL; ZVAL_OBJ(¶ms[0], old_exception); @@ -1666,8 +1671,7 @@ ZEND_API int zend_execute_scripts(int type, zval *retval, int file_count, ...) / zend_user_exception_handler(); } if (EG(exception)) { - zend_exception_error(EG(exception), E_ERROR); - ret = FAILURE; + ret = zend_exception_error(EG(exception), E_ERROR); } } destroy_op_array(op_array); diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index 594186682b89e..bf972b4eeb222 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -41,6 +41,10 @@ ZEND_API zend_class_entry *zend_ce_value_error; ZEND_API zend_class_entry *zend_ce_arithmetic_error; ZEND_API zend_class_entry *zend_ce_division_by_zero_error; +/* Internal pseudo-exception that is not exposed to userland. */ +static zend_class_entry zend_ce_unwind_exit; +static zend_object_handlers zend_unwind_exit_handlers; + ZEND_API void (*zend_throw_exception_hook)(zval *ex); static zend_object_handlers default_exception_handlers; @@ -81,6 +85,12 @@ void zend_exception_set_previous(zend_object *exception, zend_object *add_previo if (exception == add_previous || !add_previous || !exception) { return; } + + if (zend_is_unwind_exit(add_previous)) { + OBJ_RELEASE(add_previous); + return; + } + ZVAL_OBJ(&pv, add_previous); if (!instanceof_function(Z_OBJCE(pv), zend_ce_throwable)) { zend_error_noreturn(E_CORE_ERROR, "Previous exception must implement Throwable"); @@ -728,6 +738,42 @@ ZEND_METHOD(Exception, __toString) } /* }}} */ +typedef struct { + zend_object std; + zend_string *message; + int exit_status; +} zend_unwind_exit_object; + +static zend_unwind_exit_object *zend_create_unwind_exit(zend_string *message, int exit_status) { + zend_unwind_exit_object *intern = emalloc(sizeof(zend_unwind_exit_object)); + zend_object_std_init(&intern->std, &zend_ce_unwind_exit); + intern->std.handlers = &zend_unwind_exit_handlers; + intern->message = message; + intern->exit_status = exit_status; + return intern; +} + +static void zend_destroy_unwind_exit(zend_object *obj) { + zend_unwind_exit_object *intern = (zend_unwind_exit_object *) obj; + if (intern->message) { + zend_string_release(intern->message); + } + zend_object_std_dtor(&intern->std); +} + +/** {{{ Throwable method definition */ +static const zend_function_entry zend_funcs_throwable[] = { + ZEND_ABSTRACT_ME(throwable, getMessage, arginfo_class_Throwable_getMessage) + ZEND_ABSTRACT_ME(throwable, getCode, arginfo_class_Throwable_getCode) + ZEND_ABSTRACT_ME(throwable, getFile, arginfo_class_Throwable_getFile) + ZEND_ABSTRACT_ME(throwable, getLine, arginfo_class_Throwable_getLine) + ZEND_ABSTRACT_ME(throwable, getTrace, arginfo_class_Throwable_getTrace) + ZEND_ABSTRACT_ME(throwable, getPrevious, arginfo_class_Throwable_getPrevious) + ZEND_ABSTRACT_ME(throwable, getTraceAsString, arginfo_class_Throwable_getTraceAsString) + ZEND_FE_END +}; +/* }}} */ + static void declare_exception_properties(zend_class_entry *ce) { zval val; @@ -803,6 +849,10 @@ void zend_register_default_exception(void) /* {{{ */ INIT_CLASS_ENTRY(ce, "DivisionByZeroError", class_DivisionByZeroError_methods); zend_ce_division_by_zero_error = zend_register_internal_class_ex(&ce, zend_ce_arithmetic_error); zend_ce_division_by_zero_error->create_object = zend_default_exception_new; + + INIT_CLASS_ENTRY(zend_ce_unwind_exit, "UnwindExit", NULL); + memcpy(&zend_unwind_exit_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + zend_unwind_exit_handlers.free_obj = zend_destroy_unwind_exit; } /* }}} */ @@ -898,10 +948,11 @@ static void zend_error_va(int type, const char *file, uint32_t lineno, const cha /* }}} */ /* This function doesn't return if it uses E_ERROR */ -ZEND_API ZEND_COLD void zend_exception_error(zend_object *ex, int severity) /* {{{ */ +ZEND_API ZEND_COLD int zend_exception_error(zend_object *ex, int severity) /* {{{ */ { zval exception, rv; zend_class_entry *ce_exception; + int result = FAILURE; ZVAL_OBJ(&exception, ex); ce_exception = ex->ce; @@ -961,11 +1012,20 @@ ZEND_API ZEND_COLD void zend_exception_error(zend_object *ex, int severity) /* { zend_string_release_ex(str, 0); zend_string_release_ex(file, 0); + } else if (ce_exception == &zend_ce_unwind_exit) { + zend_unwind_exit_object *intern = (zend_unwind_exit_object *) ex; + if (intern->message) { + zend_write(ZSTR_VAL(intern->message), ZSTR_LEN(intern->message)); + } else { + EG(exit_status) = intern->exit_status; + } + result = SUCCESS; } else { zend_error(severity, "Uncaught exception '%s'", ZSTR_VAL(ce_exception->name)); } OBJ_RELEASE(ex); + return result; } /* }}} */ @@ -987,3 +1047,18 @@ ZEND_API ZEND_COLD void zend_throw_exception_object(zval *exception) /* {{{ */ zend_throw_exception_internal(exception); } /* }}} */ + +ZEND_API ZEND_COLD void zend_throw_unwind_exit(zend_string *message, int exit_status) +{ + zend_unwind_exit_object *intern = zend_create_unwind_exit(message, exit_status); + + ZEND_ASSERT(!EG(exception)); + EG(exception) = &intern->std; + EG(opline_before_exception) = EG(current_execute_data)->opline; + EG(current_execute_data)->opline = EG(exception_op); +} + +ZEND_API zend_bool zend_is_unwind_exit(zend_object *ex) +{ + return ex->ce == &zend_ce_unwind_exit; +} diff --git a/Zend/zend_exceptions.h b/Zend/zend_exceptions.h index ced74bf9f163c..ca2b3c4fe3ade 100644 --- a/Zend/zend_exceptions.h +++ b/Zend/zend_exceptions.h @@ -66,7 +66,10 @@ ZEND_API zend_object *zend_throw_error_exception(zend_class_entry *exception_ce, extern ZEND_API void (*zend_throw_exception_hook)(zval *ex); /* show an exception using zend_error(severity,...), severity should be E_ERROR */ -ZEND_API ZEND_COLD void zend_exception_error(zend_object *exception, int severity); +ZEND_API ZEND_COLD int zend_exception_error(zend_object *exception, int severity); + +ZEND_API ZEND_COLD void zend_throw_unwind_exit(zend_string *message, int exit_status); +ZEND_API zend_bool zend_is_unwind_exit(zend_object *ex); #include "zend_globals.h" diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index f1babc3d021ef..d62f104f229a4 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1141,8 +1141,7 @@ ZEND_API int zend_eval_stringl_ex(const char *str, size_t str_len, zval *retval_ result = zend_eval_stringl(str, str_len, retval_ptr, string_name); if (handle_exceptions && EG(exception)) { - zend_exception_error(EG(exception), E_ERROR); - result = FAILURE; + result = zend_exception_error(EG(exception), E_ERROR); } return result; } diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 768101f9b3a75..0a5031343031b 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -6874,27 +6874,31 @@ ZEND_VM_COLD_HANDLER(79, ZEND_EXIT, ANY, ANY) USE_OPLINE SAVE_OPLINE(); + + zend_string *message = NULL; + int exit_status = 0; if (OP1_TYPE != IS_UNUSED) { zval *ptr = GET_OP1_ZVAL_PTR(BP_VAR_R); do { if (Z_TYPE_P(ptr) == IS_LONG) { - EG(exit_status) = Z_LVAL_P(ptr); + exit_status = Z_LVAL_P(ptr); } else { if ((OP1_TYPE & (IS_VAR|IS_CV)) && Z_ISREF_P(ptr)) { ptr = Z_REFVAL_P(ptr); if (Z_TYPE_P(ptr) == IS_LONG) { - EG(exit_status) = Z_LVAL_P(ptr); + exit_status = Z_LVAL_P(ptr); break; } } - zend_print_zval(ptr, 0); + message = zval_get_string(ptr); } } while (0); FREE_OP1(); } - zend_bailout(); - ZEND_VM_NEXT_OPCODE(); /* Never reached */ + + zend_throw_unwind_exit(message, exit_status); + HANDLE_EXCEPTION(); } ZEND_VM_HANDLER(57, ZEND_BEGIN_SILENCE, ANY, ANY) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index a6f2ed827e404..577a291a8693c 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -2303,27 +2303,31 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_EXIT_SPEC_HANDLER USE_OPLINE SAVE_OPLINE(); + + zend_string *message = NULL; + int exit_status = 0; if (opline->op1_type != IS_UNUSED) { zval *ptr = get_zval_ptr(opline->op1_type, opline->op1, BP_VAR_R); do { if (Z_TYPE_P(ptr) == IS_LONG) { - EG(exit_status) = Z_LVAL_P(ptr); + exit_status = Z_LVAL_P(ptr); } else { if ((opline->op1_type & (IS_VAR|IS_CV)) && Z_ISREF_P(ptr)) { ptr = Z_REFVAL_P(ptr); if (Z_TYPE_P(ptr) == IS_LONG) { - EG(exit_status) = Z_LVAL_P(ptr); + exit_status = Z_LVAL_P(ptr); break; } } - zend_print_zval(ptr, 0); + message = zval_get_string(ptr); } } while (0); FREE_OP(opline->op1_type, opline->op1.var); } - zend_bailout(); - ZEND_VM_NEXT_OPCODE(); /* Never reached */ + + zend_throw_unwind_exit(message, exit_status); + HANDLE_EXCEPTION(); } static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BEGIN_SILENCE_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index ae4d31518809f..23889327a7a14 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -4484,9 +4484,10 @@ static int accel_preload(const char *config) zend_user_exception_handler(); } if (EG(exception)) { - zend_exception_error(EG(exception), E_ERROR); - CG(unclean_shutdown) = 1; - ret = FAILURE; + ret = zend_exception_error(EG(exception), E_ERROR); + if (ret == FAILURE) { + CG(unclean_shutdown) = 1; + } } } destroy_op_array(op_array); diff --git a/ext/session/tests/bug60634.phpt b/ext/session/tests/bug60634.phpt index b303134e5bce4..09e64ad4bfe3d 100644 --- a/ext/session/tests/bug60634.phpt +++ b/ext/session/tests/bug60634.phpt @@ -40,20 +40,6 @@ session_start(); session_write_close(); echo "um, hi\n"; -/* - * write() calls die(). This results in calling session_flush() which calls - * write() in request shutdown. The code is inside save handler still and - * calls save close handler. - * - * Because session_write_close() fails by die(), write() is called twice. - * close() is still called at request shutdown since session is active. - */ - ?> --EXPECT-- write: goodbye cruel world - -Warning: Unknown: Cannot call session save handler in a recursive manner in Unknown on line 0 - -Warning: Unknown: Failed to write session data using user defined save handler. (session.save_path: ) in Unknown on line 0 -close: goodbye cruel world diff --git a/ext/soap/soap.c b/ext/soap/soap.c index 6356ac73790c1..1cc6d31ec0a28 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -1382,8 +1382,10 @@ PHP_METHOD(SoapServer, handle) xmlFreeDoc(doc_request); if (EG(exception)) { - php_output_discard(); - _soap_server_exception(service, function, ZEND_THIS); + if (!zend_is_unwind_exit(EG(exception))) { + php_output_discard(); + _soap_server_exception(service, function, ZEND_THIS); + } goto fail; } @@ -1576,15 +1578,17 @@ PHP_METHOD(SoapServer, handle) efree(fn_name); if (EG(exception)) { - php_output_discard(); - _soap_server_exception(service, function, ZEND_THIS); - if (service->type == SOAP_CLASS) { + if (!zend_is_unwind_exit(EG(exception))) { + php_output_discard(); + _soap_server_exception(service, function, ZEND_THIS); + if (service->type == SOAP_CLASS) { #if defined(HAVE_PHP_SESSION) && !defined(COMPILE_DL_SESSION) - if (soap_obj && service->soap_class.persistence != SOAP_PERSISTENCE_SESSION) { + if (soap_obj && service->soap_class.persistence != SOAP_PERSISTENCE_SESSION) { #else - if (soap_obj) { + if (soap_obj) { #endif - zval_ptr_dtor(soap_obj); + zval_ptr_dtor(soap_obj); + } } } goto fail; diff --git a/sapi/phpdbg/phpdbg_prompt.c b/sapi/phpdbg/phpdbg_prompt.c index 2f0e8e2440b60..f6b5ef41e5315 100644 --- a/sapi/phpdbg/phpdbg_prompt.c +++ b/sapi/phpdbg/phpdbg_prompt.c @@ -1711,6 +1711,11 @@ void phpdbg_execute_ex(zend_execute_data *execute_data) /* {{{ */ } #endif + if (exception && zend_is_unwind_exit(exception)) { + /* Restore bailout based exit. */ + zend_bailout(); + } + if (PHPDBG_G(flags) & PHPDBG_PREVENT_INTERACTIVE) { phpdbg_print_opline_ex(execute_data, 0); goto next;