Skip to content

Commit 4bc16fb

Browse files
committed
Make exit() unwind properly
1 parent 7309c23 commit 4bc16fb

File tree

12 files changed

+155
-21
lines changed

12 files changed

+155
-21
lines changed

Zend/tests/exit_finally_1.phpt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
exit() and finally (1)
3+
--FILE--
4+
<?php
5+
6+
try {
7+
exit("Exit\n");
8+
} catch (Throwable $e) {
9+
echo "Not caught\n";
10+
} finally {
11+
echo "Finally\n";
12+
}
13+
echo "Not executed\n";
14+
15+
?>
16+
--EXPECT--
17+
Finally
18+
Exit

Zend/tests/exit_finally_2.phpt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
exit() and finally (2)
3+
--FILE--
4+
<?php
5+
6+
try {
7+
try {
8+
exit("Exit\n");
9+
} catch (Throwable $e) {
10+
echo "Not caught\n";
11+
} finally {
12+
throw new Exception("Finally exception");
13+
}
14+
echo "Not executed\n";
15+
} catch (Exception $e) {
16+
echo "Caught {$e->getMessage()}\n";
17+
}
18+
19+
?>
20+
--EXPECT--
21+
Caught Finally exception

Zend/tests/exit_finally_3.phpt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
exit() and finally (3)
3+
--FILE--
4+
<?php
5+
6+
function test() {
7+
try {
8+
exit("Exit\n");
9+
} finally {
10+
return 42;
11+
}
12+
}
13+
var_dump(test());
14+
15+
?>
16+
--EXPECT--
17+
int(42)

Zend/tests/generators/bug75396.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ $gen->send("x");
1919
?>
2020
--EXPECT--
2121
Try
22+
Finally
2223
Exit

Zend/zend.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,8 +1665,7 @@ ZEND_API int zend_execute_scripts(int type, zval *retval, int file_count, ...) /
16651665
zend_user_exception_handler();
16661666
}
16671667
if (EG(exception)) {
1668-
zend_exception_error(EG(exception), E_ERROR);
1669-
ret = FAILURE;
1668+
ret = zend_exception_error(EG(exception), E_ERROR);
16701669
}
16711670
}
16721671
destroy_op_array(op_array);

Zend/zend_exceptions.c

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ ZEND_API zend_class_entry *zend_ce_value_error;
4141
ZEND_API zend_class_entry *zend_ce_arithmetic_error;
4242
ZEND_API zend_class_entry *zend_ce_division_by_zero_error;
4343

44+
/* Internal pseudo-exception that is not exposed to userland. */
45+
static zend_class_entry zend_ce_unwind_exit;
46+
static zend_object_handlers zend_unwind_exit_handlers;
47+
4448
ZEND_API void (*zend_throw_exception_hook)(zval *ex);
4549

4650
static zend_object_handlers default_exception_handlers;
@@ -81,6 +85,12 @@ void zend_exception_set_previous(zend_object *exception, zend_object *add_previo
8185
if (exception == add_previous || !add_previous || !exception) {
8286
return;
8387
}
88+
89+
if (zend_is_unwind_exit(add_previous)) {
90+
OBJ_RELEASE(add_previous);
91+
return;
92+
}
93+
8494
ZVAL_OBJ(&pv, add_previous);
8595
if (!instanceof_function(Z_OBJCE(pv), zend_ce_throwable)) {
8696
zend_error_noreturn(E_CORE_ERROR, "Previous exception must implement Throwable");
@@ -754,6 +764,29 @@ ZEND_METHOD(exception, __toString)
754764
}
755765
/* }}} */
756766

767+
typedef struct {
768+
zend_object std;
769+
zend_string *message;
770+
int exit_status;
771+
} zend_unwind_exit_object;
772+
773+
static zend_unwind_exit_object *zend_create_unwind_exit(zend_string *message, int exit_status) {
774+
zend_unwind_exit_object *intern = emalloc(sizeof(zend_unwind_exit_object));
775+
zend_object_std_init(&intern->std, &zend_ce_unwind_exit);
776+
intern->std.handlers = &zend_unwind_exit_handlers;
777+
intern->message = message;
778+
intern->exit_status = exit_status;
779+
return intern;
780+
}
781+
782+
static void zend_destroy_unwind_exit(zend_object *obj) {
783+
zend_unwind_exit_object *intern = (zend_unwind_exit_object *) obj;
784+
if (intern->message) {
785+
zend_string_release(intern->message);
786+
}
787+
zend_object_std_dtor(&intern->std);
788+
}
789+
757790
/** {{{ Throwable method definition */
758791
static const zend_function_entry zend_funcs_throwable[] = {
759792
ZEND_ABSTRACT_ME(throwable, getMessage, arginfo_class_Throwable_getMessage)
@@ -867,6 +900,10 @@ void zend_register_default_exception(void) /* {{{ */
867900
INIT_CLASS_ENTRY(ce, "DivisionByZeroError", NULL);
868901
zend_ce_division_by_zero_error = zend_register_internal_class_ex(&ce, zend_ce_arithmetic_error);
869902
zend_ce_division_by_zero_error->create_object = zend_default_exception_new;
903+
904+
INIT_CLASS_ENTRY(zend_ce_unwind_exit, "UnwindExit", NULL);
905+
memcpy(&zend_unwind_exit_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
906+
zend_unwind_exit_handlers.free_obj = zend_destroy_unwind_exit;
870907
}
871908
/* }}} */
872909

@@ -961,10 +998,11 @@ static void zend_error_helper(int type, const char *filename, const uint32_t lin
961998
/* }}} */
962999

9631000
/* This function doesn't return if it uses E_ERROR */
964-
ZEND_API ZEND_COLD void zend_exception_error(zend_object *ex, int severity) /* {{{ */
1001+
ZEND_API ZEND_COLD int zend_exception_error(zend_object *ex, int severity) /* {{{ */
9651002
{
9661003
zval exception, rv;
9671004
zend_class_entry *ce_exception;
1005+
int result = FAILURE;
9681006

9691007
ZVAL_OBJ(&exception, ex);
9701008
ce_exception = ex->ce;
@@ -1024,11 +1062,20 @@ ZEND_API ZEND_COLD void zend_exception_error(zend_object *ex, int severity) /* {
10241062

10251063
zend_string_release_ex(str, 0);
10261064
zend_string_release_ex(file, 0);
1065+
} else if (ce_exception == &zend_ce_unwind_exit) {
1066+
zend_unwind_exit_object *intern = (zend_unwind_exit_object *) ex;
1067+
if (intern->message) {
1068+
zend_write(ZSTR_VAL(intern->message), ZSTR_LEN(intern->message));
1069+
} else {
1070+
EG(exit_status) = intern->exit_status;
1071+
}
1072+
result = SUCCESS;
10271073
} else {
10281074
zend_error(severity, "Uncaught exception '%s'", ZSTR_VAL(ce_exception->name));
10291075
}
10301076

10311077
OBJ_RELEASE(ex);
1078+
return result;
10321079
}
10331080
/* }}} */
10341081

@@ -1050,3 +1097,18 @@ ZEND_API ZEND_COLD void zend_throw_exception_object(zval *exception) /* {{{ */
10501097
zend_throw_exception_internal(exception);
10511098
}
10521099
/* }}} */
1100+
1101+
ZEND_API ZEND_COLD void zend_throw_unwind_exit(zend_string *message, int exit_status)
1102+
{
1103+
zend_unwind_exit_object *intern = zend_create_unwind_exit(message, exit_status);
1104+
1105+
ZEND_ASSERT(!EG(exception));
1106+
EG(exception) = &intern->std;
1107+
EG(opline_before_exception) = EG(current_execute_data)->opline;
1108+
EG(current_execute_data)->opline = EG(exception_op);
1109+
}
1110+
1111+
ZEND_API zend_bool zend_is_unwind_exit(zend_object *ex)
1112+
{
1113+
return ex->ce == &zend_ce_unwind_exit;
1114+
}

Zend/zend_exceptions.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ ZEND_API zend_object *zend_throw_error_exception(zend_class_entry *exception_ce,
6666
extern ZEND_API void (*zend_throw_exception_hook)(zval *ex);
6767

6868
/* show an exception using zend_error(severity,...), severity should be E_ERROR */
69-
ZEND_API ZEND_COLD void zend_exception_error(zend_object *exception, int severity);
69+
ZEND_API ZEND_COLD int zend_exception_error(zend_object *exception, int severity);
70+
71+
ZEND_API ZEND_COLD void zend_throw_unwind_exit(zend_string *message, int exit_status);
72+
ZEND_API zend_bool zend_is_unwind_exit(zend_object *ex);
7073

7174
#include "zend_globals.h"
7275

Zend/zend_execute_API.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,8 +1089,7 @@ ZEND_API int zend_eval_stringl_ex(const char *str, size_t str_len, zval *retval_
10891089

10901090
result = zend_eval_stringl(str, str_len, retval_ptr, string_name);
10911091
if (handle_exceptions && EG(exception)) {
1092-
zend_exception_error(EG(exception), E_ERROR);
1093-
result = FAILURE;
1092+
result = zend_exception_error(EG(exception), E_ERROR);
10941093
}
10951094
return result;
10961095
}

Zend/zend_vm_def.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6876,27 +6876,31 @@ ZEND_VM_COLD_HANDLER(79, ZEND_EXIT, ANY, ANY)
68766876
USE_OPLINE
68776877

68786878
SAVE_OPLINE();
6879+
6880+
zend_string *message = NULL;
6881+
int exit_status = 0;
68796882
if (OP1_TYPE != IS_UNUSED) {
68806883
zval *ptr = GET_OP1_ZVAL_PTR(BP_VAR_R);
68816884

68826885
do {
68836886
if (Z_TYPE_P(ptr) == IS_LONG) {
6884-
EG(exit_status) = Z_LVAL_P(ptr);
6887+
exit_status = Z_LVAL_P(ptr);
68856888
} else {
68866889
if ((OP1_TYPE & (IS_VAR|IS_CV)) && Z_ISREF_P(ptr)) {
68876890
ptr = Z_REFVAL_P(ptr);
68886891
if (Z_TYPE_P(ptr) == IS_LONG) {
6889-
EG(exit_status) = Z_LVAL_P(ptr);
6892+
exit_status = Z_LVAL_P(ptr);
68906893
break;
68916894
}
68926895
}
6893-
zend_print_zval(ptr, 0);
6896+
message = zval_get_string(ptr);
68946897
}
68956898
} while (0);
68966899
FREE_OP1();
68976900
}
6898-
zend_bailout();
6899-
ZEND_VM_NEXT_OPCODE(); /* Never reached */
6901+
6902+
zend_throw_unwind_exit(message, exit_status);
6903+
HANDLE_EXCEPTION();
69006904
}
69016905

69026906
ZEND_VM_HANDLER(57, ZEND_BEGIN_SILENCE, ANY, ANY)

Zend/zend_vm_execute.h

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2307,27 +2307,31 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_EXIT_SPEC_HANDLER
23072307
USE_OPLINE
23082308

23092309
SAVE_OPLINE();
2310+
2311+
zend_string *message = NULL;
2312+
int exit_status = 0;
23102313
if (opline->op1_type != IS_UNUSED) {
23112314
zval *ptr = get_zval_ptr(opline->op1_type, opline->op1, BP_VAR_R);
23122315

23132316
do {
23142317
if (Z_TYPE_P(ptr) == IS_LONG) {
2315-
EG(exit_status) = Z_LVAL_P(ptr);
2318+
exit_status = Z_LVAL_P(ptr);
23162319
} else {
23172320
if ((opline->op1_type & (IS_VAR|IS_CV)) && Z_ISREF_P(ptr)) {
23182321
ptr = Z_REFVAL_P(ptr);
23192322
if (Z_TYPE_P(ptr) == IS_LONG) {
2320-
EG(exit_status) = Z_LVAL_P(ptr);
2323+
exit_status = Z_LVAL_P(ptr);
23212324
break;
23222325
}
23232326
}
2324-
zend_print_zval(ptr, 0);
2327+
message = zval_get_string(ptr);
23252328
}
23262329
} while (0);
23272330
FREE_OP(opline->op1_type, opline->op1.var);
23282331
}
2329-
zend_bailout();
2330-
ZEND_VM_NEXT_OPCODE(); /* Never reached */
2332+
2333+
zend_throw_unwind_exit(message, exit_status);
2334+
HANDLE_EXCEPTION();
23312335
}
23322336

23332337
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_BEGIN_SILENCE_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
@@ -2504,7 +2508,7 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_dispatch_try
25042508

25052509
/* Chain potential exception from wrapping finally block */
25062510
if (Z_OBJ_P(fast_call)) {
2507-
if (ex) {
2511+
if (ex && !zend_is_unwind_exit(ex)) {
25082512
zend_exception_set_previous(ex, Z_OBJ_P(fast_call));
25092513
} else {
25102514
EG(exception) = Z_OBJ_P(fast_call);
@@ -33308,7 +33312,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_GET_CLASS_SPEC_UNUSED_UNUSED_H
3330833312
if (IS_UNUSED == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) {
3330933313
ZVAL_UNDEFINED_OP1();
3331033314
}
33311-
zend_type_error("Argument 1 ($object) passed to get_class() must be of type object, %s given", zend_get_type_by_const(Z_TYPE_P(op1)));
33315+
zend_type_error("get_class(): Argument #1 ($object) must be of type object, %s given", zend_get_type_by_const(Z_TYPE_P(op1)));
3331233316
ZVAL_UNDEF(EX_VAR(opline->result.var));
3331333317
}
3331433318
break;

ext/opcache/ZendAccelerator.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4352,9 +4352,10 @@ static int accel_preload(const char *config)
43524352
zend_user_exception_handler();
43534353
}
43544354
if (EG(exception)) {
4355-
zend_exception_error(EG(exception), E_ERROR);
4356-
CG(unclean_shutdown) = 1;
4357-
ret = FAILURE;
4355+
ret = zend_exception_error(EG(exception), E_ERROR);
4356+
if (ret == FAILURE) {
4357+
CG(unclean_shutdown) = 1;
4358+
}
43584359
}
43594360
}
43604361
destroy_op_array(op_array);

sapi/phpdbg/phpdbg_prompt.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1712,6 +1712,11 @@ void phpdbg_execute_ex(zend_execute_data *execute_data) /* {{{ */
17121712
}
17131713
#endif
17141714

1715+
if (exception && zend_is_unwind_exit(exception)) {
1716+
/* Restore bailout based exit. */
1717+
zend_bailout();
1718+
}
1719+
17151720
if (PHPDBG_G(flags) & PHPDBG_PREVENT_INTERACTIVE) {
17161721
phpdbg_print_opline_ex(execute_data, 0);
17171722
goto next;

0 commit comments

Comments
 (0)