Skip to content

Commit 3301d96

Browse files
committed
Restore error handler after running it
Symfony relies on finding the exception handler in the handler stack. There's currently no clean API to find it, so they pop all the handlers, and push them again once the stack is empty. This PR attempts to minimize the BC break by pushing the current handler onto the stack and clearing the current handler, and restoring it once it has finished. This is essentially equivalent to set_exception_handler(null) and restore_exception_handler(). restore_exception_handler() however is only called if the exception handler is still unset. If the handler has pushed a new handler in the meantime, we assume it knows what it's doing. Fixes GH-13446 Closes GH-13686
1 parent f2ec6e4 commit 3301d96

File tree

6 files changed

+91
-1
lines changed

6 files changed

+91
-1
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ PHP NEWS
77
scanning WeakMaps). (Arnaud)
88
. Fixed bug GH-13612 (Corrupted memory in destructor with weak references).
99
(nielsdos)
10+
. Fixed bug GH-13446 (Restore exception handler after it finishes). (ilutov)
1011

1112
- DOM:
1213
. Add some missing ZPP checks. (nielsdos)

Zend/tests/gh13446_1.phpt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
GH-13446: Exception handler is restored after is has finished
3+
--FILE--
4+
<?php
5+
function exception_handler($ex) {
6+
echo 'Exception caught: ', $ex->getMessage(), "\n";
7+
}
8+
set_exception_handler('exception_handler');
9+
10+
register_shutdown_function(function () {
11+
echo set_exception_handler(null), "\n";
12+
restore_exception_handler();
13+
});
14+
15+
throw new Exception('Test');
16+
?>
17+
--EXPECT--
18+
Exception caught: Test
19+
exception_handler

Zend/tests/gh13446_2.phpt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
GH-13446: Exception handler attempting to free itself
3+
--FILE--
4+
<?php
5+
$x = new \stdClass();
6+
$handler = function ($ex) use (&$handler, $x) {
7+
$handler = null;
8+
var_dump($x);
9+
};
10+
unset($x);
11+
set_exception_handler($handler);
12+
throw new Exception('Unhandled');
13+
?>
14+
--EXPECT--
15+
object(stdClass)#1 (0) {
16+
}

Zend/tests/gh13446_3.phpt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
GH-13446: Exception handler isn't restored if it is previously modified
3+
--FILE--
4+
<?php
5+
function exception_handler_1($ex) {
6+
echo "Handler 1\n";
7+
set_exception_handler('exception_handler_2');
8+
}
9+
10+
function exception_handler_2($ex) {
11+
echo "Handler 2\n";
12+
}
13+
14+
set_exception_handler('exception_handler_1');
15+
16+
register_shutdown_function(function () {
17+
echo set_exception_handler(null), "\n";
18+
restore_exception_handler();
19+
});
20+
21+
throw new Exception();
22+
?>
23+
--EXPECT--
24+
Handler 1
25+
exception_handler_2

Zend/tests/gh13446_4.phpt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
GH-13446: Exception handler isn't restored if stack is empty
3+
--FILE--
4+
<?php
5+
function exception_handler() {
6+
echo "Handler\n";
7+
restore_exception_handler();
8+
restore_exception_handler();
9+
}
10+
set_exception_handler('exception_handler');
11+
12+
register_shutdown_function(function () {
13+
var_dump(set_exception_handler(null));
14+
restore_exception_handler();
15+
});
16+
17+
throw new Exception();
18+
?>
19+
--EXPECT--
20+
Handler
21+
NULL

Zend/zend.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1839,7 +1839,9 @@ ZEND_API ZEND_COLD void zend_user_exception_handler(void) /* {{{ */
18391839
old_exception = EG(exception);
18401840
EG(exception) = NULL;
18411841
ZVAL_OBJ(&params[0], old_exception);
1842+
18421843
ZVAL_COPY_VALUE(&orig_user_exception_handler, &EG(user_exception_handler));
1844+
zend_stack_push(&EG(user_exception_handlers), &orig_user_exception_handler);
18431845
ZVAL_UNDEF(&EG(user_exception_handler));
18441846

18451847
if (call_user_function(CG(function_table), NULL, &orig_user_exception_handler, &retval2, 1, params) == SUCCESS) {
@@ -1853,7 +1855,13 @@ ZEND_API ZEND_COLD void zend_user_exception_handler(void) /* {{{ */
18531855
EG(exception) = old_exception;
18541856
}
18551857

1856-
zval_ptr_dtor(&orig_user_exception_handler);
1858+
if (Z_TYPE(EG(user_exception_handler)) == IS_UNDEF) {
1859+
zval *tmp = zend_stack_top(&EG(user_exception_handlers));
1860+
if (tmp) {
1861+
ZVAL_COPY_VALUE(&EG(user_exception_handler), tmp);
1862+
zend_stack_del_top(&EG(user_exception_handlers));
1863+
}
1864+
}
18571865
} /* }}} */
18581866

18591867
ZEND_API zend_result zend_execute_scripts(int type, zval *retval, int file_count, ...) /* {{{ */

0 commit comments

Comments
 (0)