Skip to content

Make exit() unwind properly (minimal version) #5768

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Zend/tests/exit_exception_handler.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
Exception handler should not be invoked for exit()
--FILE--
<?php

set_exception_handler(function($e) {
var_dump($e);
});

exit("Exit\n");

?>
--EXPECT--
Exit
19 changes: 19 additions & 0 deletions Zend/tests/exit_finally_1.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
exit() and finally (1)
--FILE--
<?php

// TODO: In the future, we should execute the finally block.

try {
exit("Exit\n");
} catch (Throwable $e) {
echo "Not caught\n";
} finally {
echo "Finally\n";
}
echo "Not executed\n";

?>
--EXPECT--
Exit
23 changes: 23 additions & 0 deletions Zend/tests/exit_finally_2.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
exit() and finally (2)
--FILE--
<?php

// TODO: In the future, we should execute the finally block.

try {
try {
exit("Exit\n");
} catch (Throwable $e) {
echo "Not caught\n";
} finally {
throw new Exception("Finally exception");
}
echo "Not executed\n";
} catch (Exception $e) {
echo "Caught {$e->getMessage()}\n";
}

?>
--EXPECT--
Exit
19 changes: 19 additions & 0 deletions Zend/tests/exit_finally_3.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
exit() and finally (3)
--FILE--
<?php

// TODO: In the future, we should execute the finally block.

function test() {
try {
exit("Exit\n");
} finally {
return 42;
}
}
var_dump(test());

?>
--EXPECT--
Exit
8 changes: 6 additions & 2 deletions Zend/zend.c
Original file line number Diff line number Diff line change
Expand Up @@ -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(&params[0], old_exception);
Expand Down Expand Up @@ -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);
Expand Down
31 changes: 30 additions & 1 deletion Zend/zend_exceptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ 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;

ZEND_API void (*zend_throw_exception_hook)(zval *ex);

static zend_object_handlers default_exception_handlers;
Expand Down Expand Up @@ -81,6 +84,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");
Expand Down Expand Up @@ -803,6 +812,8 @@ 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);
}
/* }}} */

Expand Down Expand Up @@ -898,10 +909,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;
Expand Down Expand Up @@ -961,11 +973,15 @@ 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) {
/* We successfully unwound, nothing more to do */
result = SUCCESS;
} else {
zend_error(severity, "Uncaught exception '%s'", ZSTR_VAL(ce_exception->name));
}

OBJ_RELEASE(ex);
return result;
}
/* }}} */

Expand All @@ -987,3 +1003,16 @@ 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_ASSERT(!EG(exception));
EG(exception) = zend_objects_new(&zend_ce_unwind_exit);
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;
}
5 changes: 4 additions & 1 deletion Zend/zend_exceptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(void);
ZEND_API zend_bool zend_is_unwind_exit(zend_object *ex);

#include "zend_globals.h"

Expand Down
3 changes: 1 addition & 2 deletions Zend/zend_execute_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
13 changes: 8 additions & 5 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -6893,8 +6893,8 @@ ZEND_VM_COLD_HANDLER(79, ZEND_EXIT, ANY, ANY)
} while (0);
FREE_OP1();
}
zend_bailout();
ZEND_VM_NEXT_OPCODE(); /* Never reached */
zend_throw_unwind_exit();
HANDLE_EXCEPTION();
}

ZEND_VM_HANDLER(57, ZEND_BEGIN_SILENCE, ANY, ANY)
Expand Down Expand Up @@ -7271,7 +7271,7 @@ ZEND_VM_HELPER(zend_dispatch_try_catch_finally_helper, ANY, ANY, uint32_t try_ca
zend_object *ex = EG(exception);

/* Walk try/catch/finally structures upwards, performing the necessary actions */
while (try_catch_offset != (uint32_t) -1) {
for (; try_catch_offset != (uint32_t) -1; try_catch_offset--) {
zend_try_catch_element *try_catch =
&EX(func)->op_array.try_catch_array[try_catch_offset];

Expand All @@ -7281,6 +7281,11 @@ ZEND_VM_HELPER(zend_dispatch_try_catch_finally_helper, ANY, ANY, uint32_t try_ca
ZEND_VM_JMP_EX(&EX(func)->op_array.opcodes[try_catch->catch_op], 0);

} else if (op_num < try_catch->finally_op) {
if (ex && zend_is_unwind_exit(ex)) {
/* Don't execute finally blocks on exit (for now) */
continue;
}

/* Go to finally block */
zval *fast_call = EX_VAR(EX(func)->op_array.opcodes[try_catch->finally_end].op1.var);
cleanup_live_vars(execute_data, op_num, try_catch->finally_op);
Expand Down Expand Up @@ -7310,8 +7315,6 @@ ZEND_VM_HELPER(zend_dispatch_try_catch_finally_helper, ANY, ANY, uint32_t try_ca
ex = Z_OBJ_P(fast_call);
}
}

try_catch_offset--;
}

/* Uncaught exception */
Expand Down
14 changes: 9 additions & 5 deletions Zend/zend_vm_execute.h
Original file line number Diff line number Diff line change
Expand Up @@ -2322,8 +2322,8 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_EXIT_SPEC_HANDLER
} while (0);
FREE_OP(opline->op1_type, opline->op1.var);
}
zend_bailout();
ZEND_VM_NEXT_OPCODE(); /* Never reached */
zend_throw_unwind_exit();
HANDLE_EXCEPTION();
}

static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BEGIN_SILENCE_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
Expand Down Expand Up @@ -2476,9 +2476,10 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_dispatch_try
{
/* May be NULL during generator closing (only finally blocks are executed) */
zend_object *ex = EG(exception);
zend_bool is_unwind_exit = ex && zend_is_unwind_exit(ex);

/* Walk try/catch/finally structures upwards, performing the necessary actions */
while (try_catch_offset != (uint32_t) -1) {
for (; try_catch_offset != (uint32_t) -1; try_catch_offset--) {
zend_try_catch_element *try_catch =
&EX(func)->op_array.try_catch_array[try_catch_offset];

Expand All @@ -2488,6 +2489,11 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_dispatch_try
ZEND_VM_JMP_EX(&EX(func)->op_array.opcodes[try_catch->catch_op], 0);

} else if (op_num < try_catch->finally_op) {
if (is_unwind_exit) {
/* Don't execute finally blocks on exit (for now) */
continue;
}

/* Go to finally block */
zval *fast_call = EX_VAR(EX(func)->op_array.opcodes[try_catch->finally_end].op1.var);
cleanup_live_vars(execute_data, op_num, try_catch->finally_op);
Expand Down Expand Up @@ -2517,8 +2523,6 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_dispatch_try
ex = Z_OBJ_P(fast_call);
}
}

try_catch_offset--;
}

/* Uncaught exception */
Expand Down
1 change: 0 additions & 1 deletion ext/curl/tests/bug68937.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ Bug # #68937 (Segfault in curl_multi_exec)
--SKIPIF--
<?php
include 'skipif.inc';
if (getenv('SKIP_ASAN')) die('skip some curl versions leak on longjmp');
?>
--FILE--
<?php
Expand Down
1 change: 0 additions & 1 deletion ext/curl/tests/bug68937_2.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ Bug # #68937 (Segfault in curl_multi_exec)
--SKIPIF--
<?php
include 'skipif.inc';
if (getenv('SKIP_ASAN')) die('skip some curl versions leak on longjmp');
?>
--FILE--
<?php
Expand Down
7 changes: 4 additions & 3 deletions ext/opcache/ZendAccelerator.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 0 additions & 14 deletions ext/session/tests/bug60634.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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
20 changes: 12 additions & 8 deletions ext/soap/soap.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down
1 change: 0 additions & 1 deletion ext/xsl/tests/bug33853.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ Bug #33853 (php:function call __autoload with lowercase param)
--SKIPIF--
<?php
if (!extension_loaded('xsl')) die('skip xsl not loaded');
if (getenv('SKIP_ASAN')) die('xfail bailing out across foreign C code');
?>
--FILE--
<?php
Expand Down
5 changes: 5 additions & 0 deletions sapi/phpdbg/phpdbg_prompt.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down