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
This commit is contained in:
Ilija Tovilo 2024-03-12 16:32:25 +01:00
parent f2ec6e4806
commit 3301d9602a
No known key found for this signature in database
GPG Key ID: A4F5D403F118200A
6 changed files with 91 additions and 1 deletions

1
NEWS
View File

@ -7,6 +7,7 @@ PHP NEWS
scanning WeakMaps). (Arnaud)
. Fixed bug GH-13612 (Corrupted memory in destructor with weak references).
(nielsdos)
. Fixed bug GH-13446 (Restore exception handler after it finishes). (ilutov)
- DOM:
. Add some missing ZPP checks. (nielsdos)

19
Zend/tests/gh13446_1.phpt Normal file
View File

@ -0,0 +1,19 @@
--TEST--
GH-13446: Exception handler is restored after is has finished
--FILE--
<?php
function exception_handler($ex) {
echo 'Exception caught: ', $ex->getMessage(), "\n";
}
set_exception_handler('exception_handler');
register_shutdown_function(function () {
echo set_exception_handler(null), "\n";
restore_exception_handler();
});
throw new Exception('Test');
?>
--EXPECT--
Exception caught: Test
exception_handler

16
Zend/tests/gh13446_2.phpt Normal file
View File

@ -0,0 +1,16 @@
--TEST--
GH-13446: Exception handler attempting to free itself
--FILE--
<?php
$x = new \stdClass();
$handler = function ($ex) use (&$handler, $x) {
$handler = null;
var_dump($x);
};
unset($x);
set_exception_handler($handler);
throw new Exception('Unhandled');
?>
--EXPECT--
object(stdClass)#1 (0) {
}

25
Zend/tests/gh13446_3.phpt Normal file
View File

@ -0,0 +1,25 @@
--TEST--
GH-13446: Exception handler isn't restored if it is previously modified
--FILE--
<?php
function exception_handler_1($ex) {
echo "Handler 1\n";
set_exception_handler('exception_handler_2');
}
function exception_handler_2($ex) {
echo "Handler 2\n";
}
set_exception_handler('exception_handler_1');
register_shutdown_function(function () {
echo set_exception_handler(null), "\n";
restore_exception_handler();
});
throw new Exception();
?>
--EXPECT--
Handler 1
exception_handler_2

21
Zend/tests/gh13446_4.phpt Normal file
View File

@ -0,0 +1,21 @@
--TEST--
GH-13446: Exception handler isn't restored if stack is empty
--FILE--
<?php
function exception_handler() {
echo "Handler\n";
restore_exception_handler();
restore_exception_handler();
}
set_exception_handler('exception_handler');
register_shutdown_function(function () {
var_dump(set_exception_handler(null));
restore_exception_handler();
});
throw new Exception();
?>
--EXPECT--
Handler
NULL

View File

@ -1839,7 +1839,9 @@ ZEND_API ZEND_COLD void zend_user_exception_handler(void) /* {{{ */
old_exception = EG(exception);
EG(exception) = NULL;
ZVAL_OBJ(&params[0], old_exception);
ZVAL_COPY_VALUE(&orig_user_exception_handler, &EG(user_exception_handler));
zend_stack_push(&EG(user_exception_handlers), &orig_user_exception_handler);
ZVAL_UNDEF(&EG(user_exception_handler));
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) /* {{{ */
EG(exception) = old_exception;
}
zval_ptr_dtor(&orig_user_exception_handler);
if (Z_TYPE(EG(user_exception_handler)) == IS_UNDEF) {
zval *tmp = zend_stack_top(&EG(user_exception_handlers));
if (tmp) {
ZVAL_COPY_VALUE(&EG(user_exception_handler), tmp);
zend_stack_del_top(&EG(user_exception_handlers));
}
}
} /* }}} */
ZEND_API zend_result zend_execute_scripts(int type, zval *retval, int file_count, ...) /* {{{ */